In this first blog, we'll focus on developing a simple app in Android by using our new Android SDK for ZC100/300 card printers.

This basic app will have two activities, one to control and manage the TCP communication, and the second one to control the USB communication.

 

After the ZC100/300 series card printer was launched in May, we wanted to promote the usage of the new SDKs developed (Android, C#) for these devices. In this new blog series, we will be going from the simplest app you can build in Android to the one a little more advanced you can implement with SmartCards in Windows by using our new C# SDK for .NET environment.

The blog series will be covering the following use cases:

 

  1. ) Android App to print one sided card through TCP/USB
  2. ) Android App to print double sided card with 3 layers through TCP/USB
  3. ) XML Templates for ZC100/300 printers
  4. ) C# App to print one sided card and to encode with Elatec encoder

 

 

 

The user will need to enter the TCP address manually, so here, you will need to be sure that both, the printers and the devices, are in the same network. The test was done with a printer connected with RJ45 cable through a small Netgear WiFi/router and a TC51 connected through the WiFi.

 

The app will allow you to select a simple image from the gallery or any other location on your Android device. The sample code has a sample picture (DriversLicense_Name.bmp) that you can utilize for testing purposes.

 

The app has some utility classes to control the printing, path file of the file selected and the printing portion for one layer (one image) in this case.

 

public class NetworkHelper {

   public NetworkHelper() {}

   protected void getPrinterStatusOverTcp(String theIpAddress, String port) throws ConnectionException, SettingsException, ZebraCardException {
  Connection connection = new TcpConnection(theIpAddress, Integer.parseInt(port));
  ZebraCardPrinter zebraCardPrinter = null;

   try {
  connection.open();
  zebraCardPrinter = ZebraCardPrinterFactory.getInstance(connection);

  PrinterStatusInfo printerStatusInfo = zebraCardPrinter.getPrinterStatus();
  System.out.format("Status: %s%n", printerStatusInfo.status);
  System.out.format("Alarm: %s (%s)%n", printerStatusInfo.alarmInfo.value, printerStatusInfo.alarmInfo.description);
  System.out.format("Error: %s (%s)%n", printerStatusInfo.errorInfo.value, printerStatusInfo.errorInfo.description);
  System.out.format("Total jobs: %s%n", printerStatusInfo.jobsTotal);
  System.out.format("Pending jobs: %s%n", printerStatusInfo.jobsPending);
  System.out.format("Active jobs: %s%n", printerStatusInfo.jobsActive);
  System.out.format("Completed jobs: %s%n%n", printerStatusInfo.jobsComplete);

  PrinterInfo printerInfo = zebraCardPrinter.getPrinterInformation();
  System.out.format("Vendor: %s%n", printerInfo.vendor);
  System.out.format("Model: %s%n", printerInfo.model);
  System.out.format("SerialNumber: %s%n", printerInfo.serialNumber);
  System.out.format("OEM Code: %s%n", printerInfo.oemCode);
  System.out.format("Firmware Version: %s%n%n", printerInfo.firmwareVersion);

  } catch (ConnectionException e) {
   // Handle communications error here.
   e.printStackTrace();
  } finally {
   // Release resources and close the connection
   zebraCardPrinter.destroy();
  connection.close();
  }
  }
}

The Network Helper and USB helper are just simple helper classes that will allow you to capture printer status information (printerStatusInfo) and printer information (printerInfo), and this information would be useful to build more complex logic in your future apps where you want to add some intelligence to automatize processes of recognition of printers or ribbons, or to monitor/control the supplies consumption with the ribbon odometers etc.

 

 

The use case of this simple app is summarized with the template shown on the image above. The first step is you need to define the devices to be used, and, in this case, we decided to use a ZC300 printer with a Zebra TC51 mobile computer working in Android 7.1.2. The communication will be conducted through WiFi. Remember that the printer is connected physically to the router with a RJ45 connector.  The formatting language is determined with a scheme in XML with data.

 

buttonCardNetwork.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
  Thread threadmac = new Thread(new Runnable() {
   @Override
   public void run() {

   try  {
  Connection connection = null;
  ZebraCardPrinter printer = null;
   try {
   ip = ipAddressEditText.getText().toString();
  System.out.format("Status: %s%n", ip);
   port = portNumberEditText.getText().toString();
  System.out.format("Status: %s%n", port);
   tcpCardHandler.getPrinterStatusOverTcp(ip, port);
  connection = new TcpConnection(ip,Integer.parseInt(port));
  connection.open();
  printer = ZebraCardPrinterFactory.getInstance(connection);
   if (printer != null) {
   printthisCard.printCard(printer, uriTransferPath, context);
  }
  } catch (Exception e) {
  System.out.format("Status: %s%n", e.getMessage());
  }
   finally {
   try {
   if (printer != null) {
  printer.destroy();
  printer = null;
  }
  } catch (ZebraCardException e) {
  e.printStackTrace();
  }
   if (connection != null) {
   try {
  connection.close();
  connection = null;
  } catch (ConnectionException e) {
  e.printStackTrace();
  }
  }
  }
  } catch (Exception e) {
  e.printStackTrace();
  }
  }
  });
  threadmac.start();
  }
});

 

Once you select the image to be printed, the app will allow a preview of the image as seen in the pic above. When the button “TCP Card Print” is clicked, then the Main Activity will call PrintCardHelper.printCard. This method transfers the printer object created, and the reference path of the image selected to the printing helper class.

 

protected void printCard(ZebraCardPrinter printer, String pathImage, final Context context) throws ZebraCardException, ConnectionException,
  IOException, SettingsException, ZebraIllegalArgumentException {
  ZebraGraphics graphics = null;
   try {
  List<GraphicsInfo> graphicsData = new ArrayList<GraphicsInfo>();
  graphics = new ZebraCardGraphics(printer);

  generatePrintJobImage(graphics, graphicsData, pathImage);

   int jobId = printer.print(1, graphicsData);
  pollJobStatus(printer, jobId, context);

   final JobStatusInfo jStatus = printer.getJobStatus(jobId);
   this.runOnUiThread(new Runnable() {
   public void run() {
  Toast.makeText(context.getApplicationContext(), "Job completed: " + jStatus.printStatus, Toast.LENGTH_LONG).show();
  System.out.format("Job Completed: %s%n", jStatus.printStatus);
  }
  });


  } finally {
   if (graphics != null) {
  graphics.clear();
  graphics.close();
  }
  }
}

 

In the PrintCard method, the first method called is the generatedPrintJobImage that builds the layers, and in this case, it is only one layer. The second method that is called is “printer.print” and it will send the print job to the printer, and, in response, it returns an integer that is captured by JobID. The final method called is the “pollJobStatus” which will be monitoring the print job until this is finished correctly, or, stopped by any error that is generated by the printer during the process (No cards, ribbon exhausted, etc).

 

@Override
protected Void doInBackground(Void... voids) {
  TcpConnection connection = null;

   try {
  connection = getTcpConnection(ipAddress);
  connection.open();

  Map<String, String> discoveryDataMap = DiscoveryUtilCard.getDiscoveryDataMap(connection);

  String model = discoveryDataMap.get("MODEL");
   if (model != null) {
   if(!model.toLowerCase().contains("zxp1") && !model.toLowerCase().contains("zxp3")) {
   printer = new DiscoveredCardPrinterNetwork(discoveryDataMap);
  } else {
   throw new ConnectionException(weakContext.get().getString(R.string.printer_model_not_supported));
  }
  } else {
   throw new SettingsException(weakContext.get().getString(R.string.no_printer_model_found));
  }
  } catch (Exception e) {
   exception = e;
  } finally {
  ConnectionHelper.cleanUpQuietly(null, connection);
  }

   return null;
}

 

For this testing purposes, the printing portion was executed in a thread directly from the MainActivity and USBDiscoveryAndPrintExample which is useful if you want to quickly proof  the concept or demo. However, it's recommended that your production apps implement the recommended best practice that executes the printing portion on the background of the app as shown in the above image (Screenshot taken from our SDK sample code that come with  Multiplatform SDK).

 

// Catches intent indicating if the user grants permission to use the USB device
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
   public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();
   if (ACTION_USB_PERMISSION.equals(action)) {
   synchronized (this) {
  UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
   if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
   if (device != null) {
   hasPermissionToCommunicate = true;
  }
  }
  }
  }
  }
};

 

For the USB communication to the printer, this sample code was tested with a Samsung Galaxy 5 and Android 6.0.1. USB printing requires that you implement a BroadcastReceiver, so the USB permission is granted when requested, you can implement without asking the user to click on the button.

 

Once the permission is granted, you can connect the USB port (please, remember that it was tested only with a OTG cable with Samsung Galaxy 5 and Android 6.0.1). The sample code does not support USB-C or USB type C port for USB communication with OTG cable to communicate to ZC300 printers.

 

// Print button click
buttonPrint.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
  Thread threadmac2 = new Thread(new Runnable() {
   @Override
   public void run() {
   try {
  Connection connection = null;
  ZebraCardPrinter printer = null;
   try {
   if (hasPermissionToCommunicate) {
   try {
  connection = discoveredPrinterUsb.getConnection();
   usbHelper.getPrinterInfoOverUsb(connection);
  connection.open();
  printer = ZebraCardPrinterFactory.getInstance(connection);
   if (printer != null) {
   printthisCard.printCard(printer, transferredImage, context);
  }
  } catch (final Exception e1) {
  UsbDiscoveryAndPrintExample.this.runOnUiThread(new Runnable() {
   public void run() {
  Toast.makeText(getApplicationContext(), e1.getMessage() + e1.getLocalizedMessage(), Toast.LENGTH_LONG).show();
  System.out.format("Exception: %s%n", e1.toString());
  }
  });
  }
  } else {
  UsbDiscoveryAndPrintExample.this.runOnUiThread(new Runnable() {
   public void run() {
  Toast.makeText(getApplicationContext(), "No permission to communicate", Toast.LENGTH_LONG).show();
  System.out.format("Exception: %s%n","No permission to communicate");
  }
  });
  }
  } finally {
   try {
   if (printer != null) {
  printer.destroy();
  printer = null;
  }
  } catch (ZebraCardException e) {
  e.printStackTrace();
  }
   if (connection != null) {
   try {
  connection.close();
  connection = null;
  } catch (ConnectionException e) {
  e.printStackTrace();
  }
  }
  }
  } catch (Exception e) {
  e.printStackTrace();

  }
  }
  });
  threadmac2.start();
  }
});

The sequence for USB would be the same that was implemented with the TCP connection once the communication was created, the printer was connected, and the CardPrint class was loaded.

 

We hope you have enjoyed this new blog series that will allow you to speed up your knowledge and integration of the new card Printer SDKs with your apps.

 

The sample code can be downloaded from this GitHub link.

 

If you have any question or comments, please, post your comments below.

1.    XML Templates for ZC1/300 Card Printers