Printing PDFs from Android on Link-OS Printers
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:
- Link-OS Printer
- An Android Mobile Device
- Link-OS SDK
- 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();
}
}
}
James Swinton-Bland