Audience

Intro

This blog walks through the process of printing PDFs from an Android mobile device to a Link-OS printer using PDF direct emulation which is free & pre-enabled on printers running Link-OS V6.3 or later.

Pre-requisites

To implement PDF Printing on an Android device, you’ll need a few things:

  1. Link-OS Printer
  2. An Android Mobile Device
  3. Link-OS SDK
  4. Basic Android Programming Knowledge

Demo App

We have a demo app available which showcases this functionality on our ZebraDevs GitHub

Step-By-Step Guide

The following section breaks down each step required to print PDFs onto an Android Mobile Device.

Note: If your printer is running a Link-OS version below 6.3, please follow the steps listed in this guide: https://developer.zebra.com/blog/announcement-printers-include-free-pdf-direct-link-os-63 before following the steps outlined in this guide.

Step 1 – Download, Install & Configure SDK

If you are already familiar with adding the Link-OS SDK to an Android project, please skip to step 2.

The Link-OS SDK is available from our zebra.com support & downloads section here. You’ll need to select the installer for your OS (Windows, Linux or OS X) & run the executable. The installer will prompt you for an installation location (default: C:\Program Files\Zebra Technologies\link_os_sdk) which is where the SDK will be saved to, so remember this location as we’ll need it shortly. After selecting an installation location, follow the prompts on the installer, after-which you should be able to locate the ZSDK_ANDROID_API.jar file here:

\android\v2.14.5198\lib\ZSDK_ANDROID_API.jar

Next, we need to add this SDK into our application. To do this in Android Studio, right click your application folder & select “Open Module Settings”, or use the keyboard shortcut F4. Once the Module Settings dialog is open, hit the little “+” icon, select Android Library from the list & navigate to the ZSDK_ANDROID_API.jar file we just installed. Lastly, we need to update our Gradle file to include this .jar, so open your application level build.gradle file and add the following line:

implementation project(path: ':ZSDK_ANDROID_API')

At this point, we can sync our project & we’ll now have access to the Link-OS SDK APIs in our application.

Step 2 – Select PDF for Printing

This step isn’t Zebra specific and there are a few different ways to achieve this depending on the Android version your device is running. Let’s assume you’re running Android 11 & are subject to Scope Storage restrictions, in which case, we’ll use the Storage Access Framework API to allow the user to select a PDF, ready for printing. The following code will launch the file-picker & allow the user to select a PDF. Note: the setType() API has been used to filter for pdf files specifically

Intent selectPdfIntent = new Intent();
selectPdfIntent.setType("application/pdf");
selectPdfIntent.setAction(Intent.ACTION_GET_CONTENT);
selectPdfIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(Intent.createChooser(selectPdfIntent, "Select PDF"), SELECT_PDF_INTENT);

Once the user has selected a PDF, we'll be notified in the onActivityResult() callback where we can get the file data:

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // Verify Result
    if (resultCode == Activity.RESULT_OK && requestCode == SELECT_PDF_INTENT && data != null && data.getData() != null) {
        // PDF Uri is available via data.getData() API
        Uri pdfFileUri = data.getData();
        Log.i(TAG, "PDF Selected: " + pdfFileUri);

        // TODO: Connect to Printer & Send PDF
    } else {
        Log.e(TAG, "No PDF Selected");
    }
}

Once we have the Uri, we need to extract a File object using the following method:

public static File getFileFromUri(Context context, Uri contentUri) throws IOException {
    // Get Input Stream && Init File
    File pdfFile = null;
    InputStream inputStream = context.getContentResolver().openInputStream(contentUri);
    if (inputStream != null) {
      try {
          pdfFile = File.createTempFile(getFileNameFromContentUri(context, contentUri), ".pdf", context.getCacheDir());
          try (OutputStream output = new FileOutputStream(pdfFile)) {
              byte[] buffer = new byte[4 * 1024]; // or other buffer size
              int read;
              while ((read = inputStream.read(buffer)) != -1) {
                  output.write(buffer, 0, read);
              }
              output.flush();
          }
      } finally {
          inputStream.close();
      }
  } return pdfFile;
}

Step 3 - Connect to the Printer

Now we have the PDF we'd like to print, we need to connect to the Printer. Assuming we're using a BluetoothConnection, we can open the connection with the following code by using the Printers Mac Address: Note: its recommended to perform connection code on a background thread

// Init Connection
private void connectToPrinter(String mPrinterToConnectToMACAddress) {
  Connection connection = new BluetoothConnection(mPrinterToConnectToMACAddress);
  try {
    // Open Connection
    connection.open();

    // Verify Printer Supports PDF
    if (zebraPrinterSupportsPDF(connection)) {
      // TODO: Send PDF to Printer
    } else {
      Log.e(TAG, "Printer does not support PDF Printing");

      // Close Connection
      connection.close();
    }
  } catch (ConnectionException e) {
    e.printStackTrace();
    Log.e(TAG, "Connection Failed: " + e.getMessage());
  }
}

The zebraPrinterSupportsPDF() method simply checks if the Printer is capable of printing PDFs using the following code:

// Checks the selected printer to see if it has the pdf virtual device installed.
private boolean zebraPrinterSupportsPDF(Connection connection) throws ConnectionException {
    // Use SGD command to check if apl.enable returns "pdf"
    String printerInfo = SGD.GET("apl.enable", connection);
    return printerInfo.equals("pdf");
}

We now have our Connection object, ready to send the PDF to the Printer.

Step 4 – Print the PDF

Using the PDF File & Connection objects obtained in the previous steps, we can now use the Link-OS APIs to send the file over to the printer. Note: Its recommended to perform these actions on a background thread

Firstly, we need to open the connection:

if (!mPrinterConnection.isConnected()) {
  mPrinterConnection.open();
}

Next, we need to create a ZebraPrinterLinkOs Object from the connection

ZebraPrinterLinkOs printer = ZebraPrinterFactory.getLinkOsPrinter(mPrinterConnection);

Finally, after checking the printer status, we're ready to send the PDF file to the printer:

PrinterStatus printerStatus = printer.getCurrentStatus();
if (printerStatus.isReadyToPrint) {
    // Send the data to printer as a byte array.
    printer.sendFileContents(mPDFFile.getAbsolutePath(), (bytesWritten, totalBytes) -> {
        // Calc Progress
        double rawProgress = bytesWritten * 100 / totalBytes;
        int progress = (int) Math.round(rawProgress);

        // TODO: Update UI with progress...
    });

    // Make sure the data got to the printer before closing the connection
    Thread.sleep(500);

    // TODO: Notify UI that print job completed successfully...
} else {
  if (printerStatus.isPaused) {
      Log.e(TAG, "Printer paused");
  } else if (printerStatus.isHeadOpen) {
      Log.e(TAG, "Printer head open");
  } else if (printerStatus.isPaperOut) {
      Log.e(TAG, "Printer is out of paper");
  } else {
      Log.e(TAG, "Unknown error occurred");
  }
}

Putting this all together, with some error catching, we have the following method:

private void sendPdfToPrinter(Connection mPrinterConnection, File pdfFile) {
    try {
	    // Open the connection - physical connection is established here.
	    if (!mPrinterConnection.isConnected()) {
	        mPrinterConnection.open();
	    }

	    // Get Instance of Printer
	    ZebraPrinterLinkOs printer = ZebraPrinterFactory.getLinkOsPrinter(mPrinterConnection);

	    // Verify Printer Status is Ready
	    PrinterStatus printerStatus = printer.getCurrentStatus();
	    if (printerStatus.isReadyToPrint) {
	        // Send the data to printer as a byte array.
	        printer.sendFileContents(mPDFFile.getAbsolutePath(), (bytesWritten, totalBytes) -> {
	            // Calc Progress
	            double rawProgress = bytesWritten * 100 / totalBytes;
	            int progress = (int) Math.round(rawProgress);

	            // TODO: Update UI with progress...
	        });

	        // Make sure the data got to the printer before closing the connection
	        Thread.sleep(500);

	        // TODO: Notify UI that print job completed successfully...
	    } else {
	       if (printerStatus.isPaused) {
	           Log.e(TAG, "Printer paused");
	       } else if (printerStatus.isHeadOpen) {
	           Log.e(TAG, "Printer head open");
	       } else if (printerStatus.isPaperOut) {
	           Log.e(TAG, "Printer is out of paper");
	       } else {
	           Log.e(TAG, "Unknown error occurred");
	       }
	    }
    } catch (ConnectionException | InterruptedException e) {
      // Pass Error Up
      Log.e(TAG, e.getMessage()));
    } finally {
      try {
          // Close Connections
          mPrinterConnection.close();
      } catch (ConnectionException e) {
          e.printStackTrace();
      }
   }
}