I'm creating an app for Android. Part of the desired app functionality is that the user can select a special printer (let's just call it Transfer Printer) which will pass on the document-to-be-printed to a process running on an external server.
What steps do I need to take to add a custom printer to the list of printers in the Android print panel, accessible from the Print option of the Overflow menu?
It is desirable to use the existing Android print panel functionality rather than, for example, an additional Share option in the App Selector because of user experience considerations; it won't be intuitive to the user to click Share rather than Print for the desired functionality.
There is an existing similar question which has gathered little interest since it was posted some time ago. The asker has identified the PrintManager class as a lead but I believe that the PrintService class is likely to be more fruitful:
A print service is responsible for discovering printers, adding discovered printers, removing added printers, and updating added printers.
The same page details Declaration and Configuration of the print service. I've done so as below.
In AndroidManifest.xml:
...
<application
... >
...
<service
android:name=".TransferPrintService"
android:permission="android.permission.BIND_PRINT_SERVICE"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.printservice.PrintService" />
</intent-filter>
<meta-data
android:name="android.printservice"
android:resource="@xml/transfer_print_service" />
</service>
</application>
It's unclear to me exactly where the meta-data is supposed to be specified. From SERVICE_META_DATA section of the PrintService page:
This meta-data must reference a XML resource containing a print-service tag.
In res/xml/transfer_print_service.xml:
<print-service
android:label="TransferPrintService"
android:vendor="Company Ltd." />
This creates a custom PrinterDiscoverySession. My goal at this stage is to just get a printer appearing in the print panel and work from there.
public class TransferPrintService extends PrintService {
public TransferPrintService() {
}
@Override
public void onPrintJobQueued(PrintJob printJob) {
printJob.start();
printJob.complete();
}
@Override
public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
return new TransferPrinterDiscoverySession(this);
}
@Override
public void onRequestCancelPrintJob(PrintJob printJob) {
}
}
The service is started in a BroadcastReceiver on an ACTION_BOOT_COMPLETED intent.
This actually creates the custom printer.
public class TransferPrinterDiscoverySession extends PrinterDiscoverySession {
private transferPrintService printService;
private static final String PRINTER = "Transfer Printer";
public transferPrinterDiscoverySession(TransferPrintService printService) {
this.printService = printService;
}
@Override
public void onStartPrinterDiscovery(List<PrinterId> printerList) {
PrinterId id = printService.generatePrinterId(PRINTER);
PrinterInfo.Builder builder =
new PrinterInfo.Builder(id, PRINTER, PrinterInfo.STATUS_IDLE);
PrinterInfo info = builder.build();
List<PrinterInfo> infos = new ArrayList<>();
infos.add(info);
addPrinters(infos);
}
@Override
public void onStopPrinterDiscovery() {
}
@Override
public void onValidatePrinters(List<PrinterId> printerIds) {
}
@Override
public void onStartPrinterStateTracking(PrinterId printerId) {
PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId,
PRINTER, PrinterInfo.STATUS_IDLE);
PrinterCapabilitiesInfo.Builder capBuilder =
new PrinterCapabilitiesInfo.Builder(printerId);
capBuilder.addMediaSize(PrintAttributes.MediaSize.ISO_A4, true);
capBuilder.addResolution(new PrintAttributes.Resolution(
"Default", "Default", 360, 360), true);
capBuilder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
+ PrintAttributes.COLOR_MODE_MONOCHROME,
PrintAttributes.COLOR_MODE_COLOR);
capBuilder.setMinMargins(PrintAttributes.Margins.NO_MARGINS);
PrinterCapabilitiesInfo caps = capBuilder.build();
builder.setCapabilities(caps);
PrinterInfo info = builder.build();
List<PrinterInfo> infos = new ArrayList<PrinterInfo>();
infos.add(info);
addPrinters(infos);
}
@Override
public void onStopPrinterStateTracking(PrinterId printerId) {
}
@Override
public void onDestroy() {
}
}
<print-service>
tag in a separate XML document under res? Trying to place the tag anywhere in the AndroidManfiest.xml document produces IDE errors.The trick I was missing was actually enabling the Print Service via the Android Settings menu. Actually doing this wasn't as straightforward as I would have hoped as the device manufacturer had removed the setting from the menu. It should be right under Accessibility in the System section of the menu.
I ended up installing the Cloud Print app by Google, which gave me access to the Print Service settings temporarily (to enable the Cloud Print service). Once in here I noticed that my own service was, in fact, present.
For posterity: To avoid un-installing and re-installing Cloud Print every time you want to change the Print Service settings, use the following SQLite3 commands, either with adb shell or from Terminal Emulator (or similar):
sqlite3 data/data/com.android.providers.settings/databases/settings.db
You should now have access to the Settings database and be using the SQLite3 command line shell. The settings of interest are located in the secure table and are enabled_print_services and enabled_on_first_boot_system_print_services. You can check if these settings already exist by using:
.dump secure
If they don't, then use the following commands:
INSERT INTO secure VALUES(<id>, 'enabled_on_first_boot_system_print_services', 'com.companyname.appservice/com.companyname.appservice.TransferPrintService');
INSERT INTO secure VALUES(<id>, 'enabled_print_services', 'com.companyname.appservice/com.companyname.appservice.TransferPrintService');
You should, of course, replace 'com.companyname.appservice' with your own package and 'TransferPrintService' with your own print service. If these setting names do already exist, and your print service isn't listed, then you'll need to UPDATE instead of INSERT INTO:
UPDATE secure SET value = '<existing print services>:<new print service>' WHERE name = 'enabled_on_first_boot_system_print_services';
UPDATE secure SET value = '<existing print services>:<new print service>' WHERE name = 'enabled_print_services';
You'll need to make sure to include any existing print services as part of the UPDATE command; listed print services are separated by a colon ":".
Reboot the device to apply the updates to the settings database.