2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android
.printservice
;
19 import android
.annotation
.NonNull
;
20 import android
.annotation
.Nullable
;
21 import android
.app
.Service
;
22 import android
.content
.ComponentName
;
23 import android
.content
.Context
;
24 import android
.content
.Intent
;
25 import android
.os
.Handler
;
26 import android
.os
.IBinder
;
27 import android
.os
.Looper
;
28 import android
.os
.Message
;
29 import android
.os
.RemoteException
;
30 import android
.print
.PrintJobInfo
;
31 import android
.print
.PrinterId
;
32 import android
.util
.Log
;
34 import com
.android
.internal
.util
.Preconditions
;
36 import java
.util
.ArrayList
;
37 import java
.util
.Collections
;
38 import java
.util
.List
;
42 * This is the base class for implementing print services. A print service knows
43 * how to discover and interact one or more printers via one or more protocols.
45 * <h3>Printer discovery</h3>
47 * A print service is responsible for discovering printers, adding discovered printers,
48 * removing added printers, and updating added printers. When the system is interested
49 * in printers managed by your service it will call {@link
50 * #onCreatePrinterDiscoverySession()} from which you must return a new {@link
51 * PrinterDiscoverySession} instance. The returned session encapsulates the interaction
52 * between the system and your service during printer discovery. For description of this
53 * interaction refer to the documentation for {@link PrinterDiscoverySession}.
56 * For every printer discovery session all printers have to be added since system does
57 * not retain printers across sessions. Hence, each printer known to this print service
58 * should be added only once during a discovery session. Only an already added printer
59 * can be removed or updated. Removed printers can be added again.
63 * When a new print job targeted to a printer managed by this print service is is queued,
64 * i.e. ready for processing by the print service, you will receive a call to {@link
65 * #onPrintJobQueued(PrintJob)}. The print service may handle the print job immediately
66 * or schedule that for an appropriate time in the future. The list of all active print
67 * jobs for this service is obtained by calling {@link #getActivePrintJobs()}. Active
68 * print jobs are ones that are queued or started.
71 * A print service is responsible for setting a print job's state as appropriate
72 * while processing it. Initially, a print job is queued, i.e. {@link PrintJob#isQueued()
73 * PrintJob.isQueued()} returns true, which means that the document to be printed is
74 * spooled by the system and the print service can begin processing it. You can obtain
75 * the printed document by calling {@link PrintJob#getDocument() PrintJob.getDocument()}
76 * whose data is accessed via {@link PrintDocument#getData() PrintDocument.getData()}.
77 * After the print service starts printing the data it should set the print job's
78 * state to started by calling {@link PrintJob#start()} after which
79 * {@link PrintJob#isStarted() PrintJob.isStarted()} would return true. Upon successful
80 * completion, the print job should be marked as completed by calling {@link
81 * PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted()
82 * PrintJob.isCompleted()} would return true. In case of a failure, the print job should
83 * be marked as failed by calling {@link PrintJob#fail(String) PrintJob.fail(
84 * String)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would
88 * If a print job is queued or started and the user requests to cancel it, the print
89 * service will receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which
90 * requests from the service to do best effort in canceling the job. In case the job
91 * is successfully canceled, its state has to be marked as cancelled by calling {@link
92 * PrintJob#cancel() PrintJob.cancel()} after which {@link PrintJob#isCancelled()
93 * PrintJob.isCacnelled()} would return true.
97 * The lifecycle of a print service is managed exclusively by the system and follows
98 * the established service lifecycle. Additionally, starting or stopping a print service
99 * is triggered exclusively by an explicit user action through enabling or disabling it
100 * in the device settings. After the system binds to a print service, it calls {@link
101 * #onConnected()}. This method can be overriden by clients to perform post binding setup.
102 * Also after the system unbinds from a print service, it calls {@link #onDisconnected()}.
103 * This method can be overriden by clients to perform post unbinding cleanup. Your should
104 * not do any work after the system disconnected from your print service since the
105 * service can be killed at any time to reclaim memory. The system will not disconnect
106 * from a print service if there are active print jobs for the printers managed by it.
108 * <h3>Declaration</h3>
110 * A print service is declared as any other service in an AndroidManifest.xml but it must
111 * also specify that it handles the {@link android.content.Intent} with action {@link
112 * #SERVICE_INTERFACE android.printservice.PrintService}. Failure to declare this intent
113 * will cause the system to ignore the print service. Additionally, a print service must
114 * request the {@link android.Manifest.permission#BIND_PRINT_SERVICE
115 * android.permission.BIND_PRINT_SERVICE} permission to ensure that only the system can
116 * bind to it. Failure to declare this intent will cause the system to ignore the print
117 * service. Following is an example declaration:
120 * <service android:name=".MyPrintService"
121 * android:permission="android.permission.BIND_PRINT_SERVICE">
122 * <intent-filter>
123 * <action android:name="android.printservice.PrintService" />
124 * </intent-filter>
128 * <h3>Configuration</h3>
130 * A print service can be configured by specifying an optional settings activity which
131 * exposes service specific settings, an optional add printers activity which is used for
132 * manual addition of printers, vendor name ,etc. It is a responsibility of the system
133 * to launch the settings and add printers activities when appropriate.
136 * A print service is configured by providing a {@link #SERVICE_META_DATA meta-data}
137 * entry in the manifest when declaring the service. A service declaration with a meta-data
138 * tag is presented below:
139 * <pre> <service android:name=".MyPrintService"
140 * android:permission="android.permission.BIND_PRINT_SERVICE">
141 * <intent-filter>
142 * <action android:name="android.printservice.PrintService" />
143 * </intent-filter>
144 * <meta-data android:name="android.printservice" android:resource="@xml/printservice" />
145 * </service></pre>
148 * For more details for how to configure your print service via the meta-data refer to
149 * {@link #SERVICE_META_DATA} and <code><{@link android.R.styleable#PrintService
150 * print-service}></code>.
153 * <strong>Note: </strong> All callbacks in this class are executed on the main
154 * application thread. You should also invoke any method of this class on the main
155 * application thread.
158 public abstract class PrintService
extends Service
{
160 private static final String LOG_TAG
= "PrintService";
162 private static final boolean DEBUG
= false;
165 * The {@link Intent} action that must be declared as handled by a service
166 * in its manifest for the system to recognize it as a print service.
168 public static final String SERVICE_INTERFACE
= "android.printservice.PrintService";
171 * Name under which a {@link PrintService} component publishes additional information
172 * about itself. This meta-data must reference a XML resource containing a <code>
173 * <{@link android.R.styleable#PrintService print-service}></code> tag. This is
174 * a sample XML file configuring a print service:
175 * <pre> <print-service
176 * android:vendor="SomeVendor"
177 * android:settingsActivity="foo.bar.MySettingsActivity"
178 * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
182 * For detailed configuration options that can be specified via the meta-data
183 * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}.
186 * If you declare a settings or add a printers activity, they have to be exported,
187 * by setting the {@link android.R.attr#exported} activity attribute to <code>true
188 * </code>. Also in case you want only the system to be able to start any of these
189 * activities you can specify that they request the android.permission
190 * .START_PRINT_SERVICE_CONFIG_ACTIVITY permission by setting the
191 * {@link android.R.attr#permission} activity attribute.
194 public static final String SERVICE_META_DATA
= "android.printservice";
197 * If you declared an optional activity with advanced print options via the
198 * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity} attribute,
199 * this extra is used to pass in the currently constructed {@link PrintJobInfo} to your activity
200 * allowing you to modify it. After you are done, you must return the modified
201 * {@link PrintJobInfo} via the same extra.
203 * You cannot modify the passed in {@link PrintJobInfo} directly, rather you should build
204 * another one using the {@link android.print.PrintJobInfo.Builder PrintJobInfo.Builder} class.
205 * You can specify any standard properties and add advanced, printer specific, ones via
206 * {@link android.print.PrintJobInfo.Builder#putAdvancedOption(String, String)
207 * PrintJobInfo.Builder.putAdvancedOption(String, String)} and
208 * {@link android.print.PrintJobInfo.Builder#putAdvancedOption(String, int)
209 * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options are not
210 * interpreted by the system, they will not be visible to applications, and can only be accessed
211 * by your print service via {@link PrintJob#getAdvancedStringOption(String)
212 * PrintJob.getAdvancedStringOption(String)} and {@link PrintJob#getAdvancedIntOption(String)
213 * PrintJob.getAdvancedIntOption(String)}.
216 * If the advanced print options activity offers changes to the standard print options, you can
217 * get the current {@link android.print.PrinterInfo PrinterInfo} using the
218 * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user with UI options
219 * supported by the current printer. For example, if the current printer does not support a
220 * given media size, you should not offer it in the advanced print options UI.
223 * @see #EXTRA_PRINTER_INFO
225 public static final String EXTRA_PRINT_JOB_INFO
= "android.intent.extra.print.PRINT_JOB_INFO";
228 * If you declared an optional activity with advanced print options via the
229 * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
230 * attribute, this extra is used to pass in the currently selected printer's
231 * {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
233 * @see #EXTRA_PRINT_JOB_INFO
235 public static final String EXTRA_PRINTER_INFO
= "android.intent.extra.print.EXTRA_PRINTER_INFO";
238 * If you declared an optional activity with advanced print options via the
239 * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
240 * attribute, this extra is used to pass in the meta-data for the currently printed
241 * document as a {@link android.print.PrintDocumentInfo} to your activity allowing
244 * @see #EXTRA_PRINT_JOB_INFO
245 * @see #EXTRA_PRINTER_INFO
247 public static final String EXTRA_PRINT_DOCUMENT_INFO
=
248 "android.printservice.extra.PRINT_DOCUMENT_INFO";
250 private Handler mHandler
;
252 private IPrintServiceClient mClient
;
254 private int mLastSessionId
= -1;
256 private PrinterDiscoverySession mDiscoverySession
;
259 protected final void attachBaseContext(Context base
) {
260 super.attachBaseContext(base
);
261 mHandler
= new ServiceHandler(base
.getMainLooper());
265 * The system has connected to this service.
267 protected void onConnected() {
272 * The system has disconnected from this service.
274 protected void onDisconnected() {
279 * Callback asking you to create a new {@link PrinterDiscoverySession}.
281 * @return The created session.
282 * @see PrinterDiscoverySession
284 protected abstract @Nullable PrinterDiscoverySession
onCreatePrinterDiscoverySession();
287 * Called when cancellation of a print job is requested. The service
288 * should do best effort to fulfill the request. After the cancellation
289 * is performed, the print job should be marked as cancelled state by
290 * calling {@link PrintJob#cancel()}.
292 * @param printJob The print job to cancel.
294 * @see PrintJob#cancel() PrintJob.cancel()
295 * @see PrintJob#isCancelled() PrintJob.isCancelled()
297 protected abstract void onRequestCancelPrintJob(PrintJob printJob
);
300 * Called when there is a queued print job for one of the printers
301 * managed by this print service.
303 * @param printJob The new queued print job.
305 * @see PrintJob#isQueued() PrintJob.isQueued()
306 * @see #getActivePrintJobs()
308 protected abstract void onPrintJobQueued(PrintJob printJob
);
311 * Gets the active print jobs for the printers managed by this service.
312 * Active print jobs are ones that are not in a final state, i.e. whose
313 * state is queued or started.
315 * @return The active print jobs.
317 * @see PrintJob#isQueued() PrintJob.isQueued()
318 * @see PrintJob#isStarted() PrintJob.isStarted()
320 public final List
<PrintJob
> getActivePrintJobs() {
321 throwIfNotCalledOnMainThread();
322 if (mClient
== null) {
323 return Collections
.emptyList();
326 List
<PrintJob
> printJobs
= null;
327 List
<PrintJobInfo
> printJobInfos
= mClient
.getPrintJobInfos();
328 if (printJobInfos
!= null) {
329 final int printJobInfoCount
= printJobInfos
.size();
330 printJobs
= new ArrayList
<PrintJob
>(printJobInfoCount
);
331 for (int i
= 0; i
< printJobInfoCount
; i
++) {
332 printJobs
.add(new PrintJob(this, printJobInfos
.get(i
), mClient
));
335 if (printJobs
!= null) {
338 } catch (RemoteException re
) {
339 Log
.e(LOG_TAG
, "Error calling getPrintJobs()", re
);
341 return Collections
.emptyList();
345 * Generates a global printer id given the printer's locally unique one.
347 * @param localId A locally unique id in the context of your print service.
348 * @return Global printer id.
350 public @NonNull final PrinterId
generatePrinterId(String localId
) {
351 throwIfNotCalledOnMainThread();
352 localId
= Preconditions
.checkNotNull(localId
, "localId cannot be null");
353 return new PrinterId(new ComponentName(getPackageName(),
354 getClass().getName()), localId
);
357 static void throwIfNotCalledOnMainThread() {
358 if (!Looper
.getMainLooper().isCurrentThread()) {
359 throw new IllegalAccessError("must be called from the main thread");
364 public final IBinder
onBind(Intent intent
) {
365 return new IPrintService
.Stub() {
367 public void createPrinterDiscoverySession() {
368 mHandler
.sendEmptyMessage(ServiceHandler
.MSG_CREATE_PRINTER_DISCOVERY_SESSION
);
372 public void destroyPrinterDiscoverySession() {
373 mHandler
.sendEmptyMessage(ServiceHandler
.MSG_DESTROY_PRINTER_DISCOVERY_SESSION
);
377 public void startPrinterDiscovery(List
<PrinterId
> priorityList
) {
378 mHandler
.obtainMessage(ServiceHandler
.MSG_START_PRINTER_DISCOVERY
,
379 priorityList
).sendToTarget();
383 public void stopPrinterDiscovery() {
384 mHandler
.sendEmptyMessage(ServiceHandler
.MSG_STOP_PRINTER_DISCOVERY
);
388 public void validatePrinters(List
<PrinterId
> printerIds
) {
389 mHandler
.obtainMessage(ServiceHandler
.MSG_VALIDATE_PRINTERS
,
390 printerIds
).sendToTarget();
394 public void startPrinterStateTracking(PrinterId printerId
) {
395 mHandler
.obtainMessage(ServiceHandler
.MSG_START_PRINTER_STATE_TRACKING
,
396 printerId
).sendToTarget();
400 public void requestCustomPrinterIcon(PrinterId printerId
) {
401 mHandler
.obtainMessage(ServiceHandler
.MSG_REQUEST_CUSTOM_PRINTER_ICON
,
402 printerId
).sendToTarget();
406 public void stopPrinterStateTracking(PrinterId printerId
) {
407 mHandler
.obtainMessage(ServiceHandler
.MSG_STOP_PRINTER_STATE_TRACKING
,
408 printerId
).sendToTarget();
412 public void setClient(IPrintServiceClient client
) {
413 mHandler
.obtainMessage(ServiceHandler
.MSG_SET_CLIENT
, client
)
418 public void requestCancelPrintJob(PrintJobInfo printJobInfo
) {
419 mHandler
.obtainMessage(ServiceHandler
.MSG_ON_REQUEST_CANCEL_PRINTJOB
,
420 printJobInfo
).sendToTarget();
424 public void onPrintJobQueued(PrintJobInfo printJobInfo
) {
425 mHandler
.obtainMessage(ServiceHandler
.MSG_ON_PRINTJOB_QUEUED
,
426 printJobInfo
).sendToTarget();
431 private final class ServiceHandler
extends Handler
{
432 public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION
= 1;
433 public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION
= 2;
434 public static final int MSG_START_PRINTER_DISCOVERY
= 3;
435 public static final int MSG_STOP_PRINTER_DISCOVERY
= 4;
436 public static final int MSG_VALIDATE_PRINTERS
= 5;
437 public static final int MSG_START_PRINTER_STATE_TRACKING
= 6;
438 public static final int MSG_REQUEST_CUSTOM_PRINTER_ICON
= 7;
439 public static final int MSG_STOP_PRINTER_STATE_TRACKING
= 8;
440 public static final int MSG_ON_PRINTJOB_QUEUED
= 9;
441 public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB
= 10;
442 public static final int MSG_SET_CLIENT
= 11;
444 public ServiceHandler(Looper looper
) {
445 super(looper
, null, true);
449 @SuppressWarnings("unchecked")
450 public void handleMessage(Message message
) {
451 final int action
= message
.what
;
453 case MSG_CREATE_PRINTER_DISCOVERY_SESSION
: {
455 Log
.i(LOG_TAG
, "MSG_CREATE_PRINTER_DISCOVERY_SESSION "
458 PrinterDiscoverySession session
= onCreatePrinterDiscoverySession();
459 if (session
== null) {
460 throw new NullPointerException("session cannot be null");
462 if (session
.getId() == mLastSessionId
) {
463 throw new IllegalStateException("cannot reuse session instances");
465 mDiscoverySession
= session
;
466 mLastSessionId
= session
.getId();
467 session
.setObserver(mClient
);
470 case MSG_DESTROY_PRINTER_DISCOVERY_SESSION
: {
472 Log
.i(LOG_TAG
, "MSG_DESTROY_PRINTER_DISCOVERY_SESSION "
475 if (mDiscoverySession
!= null) {
476 mDiscoverySession
.destroy();
477 mDiscoverySession
= null;
481 case MSG_START_PRINTER_DISCOVERY
: {
483 Log
.i(LOG_TAG
, "MSG_START_PRINTER_DISCOVERY "
486 if (mDiscoverySession
!= null) {
487 List
<PrinterId
> priorityList
= (ArrayList
<PrinterId
>) message
.obj
;
488 mDiscoverySession
.startPrinterDiscovery(priorityList
);
492 case MSG_STOP_PRINTER_DISCOVERY
: {
494 Log
.i(LOG_TAG
, "MSG_STOP_PRINTER_DISCOVERY "
497 if (mDiscoverySession
!= null) {
498 mDiscoverySession
.stopPrinterDiscovery();
502 case MSG_VALIDATE_PRINTERS
: {
504 Log
.i(LOG_TAG
, "MSG_VALIDATE_PRINTERS "
507 if (mDiscoverySession
!= null) {
508 List
<PrinterId
> printerIds
= (List
<PrinterId
>) message
.obj
;
509 mDiscoverySession
.validatePrinters(printerIds
);
513 case MSG_START_PRINTER_STATE_TRACKING
: {
515 Log
.i(LOG_TAG
, "MSG_START_PRINTER_STATE_TRACKING "
518 if (mDiscoverySession
!= null) {
519 PrinterId printerId
= (PrinterId
) message
.obj
;
520 mDiscoverySession
.startPrinterStateTracking(printerId
);
524 case MSG_REQUEST_CUSTOM_PRINTER_ICON
: {
526 Log
.i(LOG_TAG
, "MSG_REQUEST_CUSTOM_PRINTER_ICON "
529 if (mDiscoverySession
!= null) {
530 PrinterId printerId
= (PrinterId
) message
.obj
;
531 mDiscoverySession
.requestCustomPrinterIcon(printerId
);
535 case MSG_STOP_PRINTER_STATE_TRACKING
: {
537 Log
.i(LOG_TAG
, "MSG_STOP_PRINTER_STATE_TRACKING "
540 if (mDiscoverySession
!= null) {
541 PrinterId printerId
= (PrinterId
) message
.obj
;
542 mDiscoverySession
.stopPrinterStateTracking(printerId
);
546 case MSG_ON_REQUEST_CANCEL_PRINTJOB
: {
548 Log
.i(LOG_TAG
, "MSG_ON_REQUEST_CANCEL_PRINTJOB "
551 PrintJobInfo printJobInfo
= (PrintJobInfo
) message
.obj
;
552 onRequestCancelPrintJob(new PrintJob(PrintService
.this, printJobInfo
, mClient
));
555 case MSG_ON_PRINTJOB_QUEUED
: {
557 Log
.i(LOG_TAG
, "MSG_ON_PRINTJOB_QUEUED "
560 PrintJobInfo printJobInfo
= (PrintJobInfo
) message
.obj
;
562 Log
.i(LOG_TAG
, "Queued: " + printJobInfo
);
564 onPrintJobQueued(new PrintJob(PrintService
.this, printJobInfo
, mClient
));
567 case MSG_SET_CLIENT
: {
569 Log
.i(LOG_TAG
, "MSG_SET_CLIENT "
572 mClient
= (IPrintServiceClient
) message
.obj
;
573 if (mClient
!= null) {
581 throw new IllegalArgumentException("Unknown message: " + action
);