• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package android.printservice;
18 
19 import android.R;
20 import android.app.Service;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.print.PrintJobInfo;
30 import android.print.PrinterId;
31 import android.util.Log;
32 
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.List;
36 
37 /**
38  * <p>
39  * This is the base class for implementing print services. A print service knows
40  * how to discover and interact one or more printers via one or more protocols.
41  * </p>
42  * <h3>Printer discovery</h3>
43  * <p>
44  * A print service is responsible for discovering printers, adding discovered printers,
45  * removing added printers, and updating added printers. When the system is interested
46  * in printers managed by your service it will call {@link
47  * #onCreatePrinterDiscoverySession()} from which you must return a new {@link
48  * PrinterDiscoverySession} instance. The returned session encapsulates the interaction
49  * between the system and your service during printer discovery. For description of this
50  * interaction refer to the documentation for {@link PrinterDiscoverySession}.
51  * </p>
52  * <p>
53  * For every printer discovery session all printers have to be added since system does
54  * not retain printers across sessions. Hence, each printer known to this print service
55  * should be added only once during a discovery session. Only an already added printer
56  * can be removed or updated. Removed printers can be added again.
57  * </p>
58  * <h3>Print jobs</h3>
59  * <p>
60  * When a new print job targeted to a printer managed by this print service is is queued,
61  * i.e. ready for processing by the print service, you will receive a call to {@link
62  * #onPrintJobQueued(PrintJob)}. The print service may handle the print job immediately
63  * or schedule that for an appropriate time in the future. The list of all active print
64  * jobs for this service is obtained by calling {@link #getActivePrintJobs()}. Active
65  * print jobs are ones that are queued or started.
66  * </p>
67  * <p>
68  * A print service is responsible for setting a print job's state as appropriate
69  * while processing it. Initially, a print job is queued, i.e. {@link PrintJob#isQueued()
70  * PrintJob.isQueued()} returns true, which means that the document to be printed is
71  * spooled by the system and the print service can begin processing it. You can obtain
72  * the printed document by calling {@link PrintJob#getDocument() PrintJob.getDocument()}
73  * whose data is accessed via {@link PrintDocument#getData() PrintDocument.getData()}.
74  * After the print service starts printing the data it should set the print job's
75  * state to started by calling {@link PrintJob#start()} after which
76  * {@link PrintJob#isStarted() PrintJob.isStarted()} would return true. Upon successful
77  * completion, the print job should be marked as completed by calling {@link
78  * PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted()
79  * PrintJob.isCompleted()} would return true. In case of a failure, the print job should
80  * be marked as failed by calling {@link PrintJob#fail(String) PrintJob.fail(
81  * String)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would
82  * return true.
83  * </p>
84  * <p>
85  * If a print job is queued or started and the user requests to cancel it, the print
86  * service will receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which
87  * requests from the service to do best effort in canceling the job. In case the job
88  * is successfully canceled, its state has to be marked as cancelled by calling {@link
89  * PrintJob#cancel() PrintJob.cancel()} after which {@link PrintJob#isCancelled()
90  * PrintJob.isCacnelled()} would return true.
91  * </p>
92  * <h3>Lifecycle</h3>
93  * <p>
94  * The lifecycle of a print service is managed exclusively by the system and follows
95  * the established service lifecycle. Additionally, starting or stopping a print service
96  * is triggered exclusively by an explicit user action through enabling or disabling it
97  * in the device settings. After the system binds to a print service, it calls {@link
98  * #onConnected()}. This method can be overriden by clients to perform post binding setup.
99  * Also after the system unbinds from a print service, it calls {@link #onDisconnected()}.
100  * This method can be overriden by clients to perform post unbinding cleanup. Your should
101  * not do any work after the system disconnected from your print service since the
102  * service can be killed at any time to reclaim memory. The system will not disconnect
103  * from a print service if there are active print jobs for the printers managed by it.
104  * </p>
105  * <h3>Declaration</h3>
106  * <p>
107  * A print service is declared as any other service in an AndroidManifest.xml but it must
108  * also specify that it handles the {@link android.content.Intent} with action {@link
109  * #SERVICE_INTERFACE android.printservice.PrintService}. Failure to declare this intent
110  * will cause the system to ignore the print service. Additionally, a print service must
111  * request the {@link android.Manifest.permission#BIND_PRINT_SERVICE
112  * android.permission.BIND_PRINT_SERVICE} permission to ensure that only the system can
113  * bind to it. Failure to declare this intent will cause the system to ignore the print
114  * service. Following is an example declaration:
115  * </p>
116  * <pre>
117  * &lt;service android:name=".MyPrintService"
118  *         android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
119  *     &lt;intent-filter&gt;
120  *         &lt;action android:name="android.printservice.PrintService" /&gt;
121  *     &lt;/intent-filter&gt;
122  *     . . .
123  * &lt;/service&gt;
124  * </pre>
125  * <h3>Configuration</h3>
126  * <p>
127  * A print service can be configured by specifying an optional settings activity which
128  * exposes service specific settings, an optional add printers activity which is used for
129  * manual addition of printers, vendor name ,etc. It is a responsibility of the system
130  * to launch the settings and add printers activities when appropriate.
131  * </p>
132  * <p>
133  * A print service is configured by providing a {@link #SERVICE_META_DATA meta-data}
134  * entry in the manifest when declaring the service. A service declaration with a meta-data
135  * tag is presented below:
136  * <pre> &lt;service android:name=".MyPrintService"
137  *         android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
138  *     &lt;intent-filter&gt;
139  *         &lt;action android:name="android.printservice.PrintService" /&gt;
140  *     &lt;/intent-filter&gt;
141  *     &lt;meta-data android:name="android.printservice" android:resource="@xml/printservice" /&gt;
142  * &lt;/service&gt;</pre>
143  * </p>
144  * <p>
145  * For more details for how to configure your print service via the meta-data refer to
146  * {@link #SERVICE_META_DATA} and <code>&lt;{@link android.R.styleable#PrintService
147  * print-service}&gt;</code>.
148  * </p>
149  * <p>
150  * <strong>Note: </strong> All callbacks in this class are executed on the main
151  * application thread. You should also invoke any method of this class on the main
152  * application thread.
153  * </p>
154  */
155 public abstract class PrintService extends Service {
156 
157     private static final String LOG_TAG = "PrintService";
158 
159     private static final boolean DEBUG = false;
160 
161     /**
162      * The {@link Intent} action that must be declared as handled by a service
163      * in its manifest for the system to recognize it as a print service.
164      */
165     public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
166 
167     /**
168      * Name under which a {@link PrintService} component publishes additional information
169      * about itself. This meta-data must reference a XML resource containing a <code>
170      * &lt;{@link android.R.styleable#PrintService print-service}&gt;</code> tag. This is
171      * a sample XML file configuring a print service:
172      * <pre> &lt;print-service
173      *     android:vendor="SomeVendor"
174      *     android:settingsActivity="foo.bar.MySettingsActivity"
175      *     andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
176      *     . . .
177      * /&gt;</pre>
178      * <p>
179      * For detailed configuration options that can be specified via the meta-data
180      * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}.
181      * </p>
182      * <p>
183      * If you declare a settings or add a printers activity, they have to be exported,
184      * by setting the {@link android.R.attr#exported} activity attribute to <code>true
185      * </code>. Also in case you want only the system to be able to start any of these
186      * activities you can specify that they request the android.permission
187      * .START_PRINT_SERVICE_CONFIG_ACTIVITY permission by setting the
188      * {@link android.R.attr#permission} activity attribute.
189      * </p>
190      */
191     public static final String SERVICE_META_DATA = "android.printservice";
192 
193     /**
194      * If you declared an optional activity with advanced print options via the
195      * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
196      * attribute, this extra is used to pass in the currently constructed {@link
197      * PrintJobInfo} to your activity allowing you to modify it. After you are
198      * done, you must return the modified {@link PrintJobInfo} via the same extra.
199      * <p>
200      * You cannot modify the passed in {@link PrintJobInfo} directly, rather you
201      * should build another one using the {@link PrintJobInfo.Builder} class. You
202      * can specify any standard properties and add advanced, printer specific,
203      * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String)
204      * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link
205      * PrintJobInfo.Builder#putAdvancedOption(String, int)
206      * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options
207      * are not interpreted by the system, they will not be visible to applications,
208      * and can only be accessed by your print service via {@link
209      * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)}
210      * and {@link PrintJob#getAdvancedIntOption(String) PrintJob.getAdvancedIntOption(String)}.
211      * </p>
212      * <p>
213      * If the advanced print options activity offers changes to the standard print
214      * options, you can get the current {@link android.print.PrinterInfo} using the
215      * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user
216      * with UI options supported by the current printer. For example, if the current
217      * printer does not support a given media size, you should not offer it in the
218      * advanced print options UI.
219      * </p>
220      *
221      * @see #EXTRA_PRINTER_INFO
222      */
223     public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
224 
225     /**
226      * If you declared an optional activity with advanced print options via the
227      * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
228      * attribute, this extra is used to pass in the currently selected printer's
229      * {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
230      *
231      * @see #EXTRA_PRINT_JOB_INFO
232      */
233     public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
234 
235     private Handler mHandler;
236 
237     private IPrintServiceClient mClient;
238 
239     private int mLastSessionId = -1;
240 
241     private PrinterDiscoverySession mDiscoverySession;
242 
243     @Override
attachBaseContext(Context base)244     protected final void attachBaseContext(Context base) {
245         super.attachBaseContext(base);
246         mHandler = new ServiceHandler(base.getMainLooper());
247     }
248 
249     /**
250      * The system has connected to this service.
251      */
onConnected()252     protected void onConnected() {
253         /* do nothing */
254     }
255 
256     /**
257      * The system has disconnected from this service.
258      */
onDisconnected()259     protected void onDisconnected() {
260         /* do nothing */
261     }
262 
263     /**
264      * Callback asking you to create a new {@link PrinterDiscoverySession}.
265      *
266      * @see PrinterDiscoverySession
267      */
onCreatePrinterDiscoverySession()268     protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession();
269 
270     /**
271      * Called when cancellation of a print job is requested. The service
272      * should do best effort to fulfill the request. After the cancellation
273      * is performed, the print job should be marked as cancelled state by
274      * calling {@link PrintJob#cancel()}.
275      *
276      * @param printJob The print job to cancel.
277      *
278      * @see PrintJob#cancel() PrintJob.cancel()
279      * @see PrintJob#isCancelled() PrintJob.isCancelled()
280      */
onRequestCancelPrintJob(PrintJob printJob)281     protected abstract void onRequestCancelPrintJob(PrintJob printJob);
282 
283     /**
284      * Called when there is a queued print job for one of the printers
285      * managed by this print service.
286      *
287      * @param printJob The new queued print job.
288      *
289      * @see PrintJob#isQueued() PrintJob.isQueued()
290      * @see #getActivePrintJobs()
291      */
onPrintJobQueued(PrintJob printJob)292     protected abstract void onPrintJobQueued(PrintJob printJob);
293 
294     /**
295      * Gets the active print jobs for the printers managed by this service.
296      * Active print jobs are ones that are not in a final state, i.e. whose
297      * state is queued or started.
298      *
299      * @return The active print jobs.
300      *
301      * @see PrintJob#isQueued() PrintJob.isQueued()
302      * @see PrintJob#isStarted() PrintJob.isStarted()
303      */
getActivePrintJobs()304     public final List<PrintJob> getActivePrintJobs() {
305         throwIfNotCalledOnMainThread();
306         if (mClient == null) {
307             return Collections.emptyList();
308         }
309         try {
310             List<PrintJob> printJobs = null;
311             List<PrintJobInfo> printJobInfos = mClient.getPrintJobInfos();
312             if (printJobInfos != null) {
313                 final int printJobInfoCount = printJobInfos.size();
314                 printJobs = new ArrayList<PrintJob>(printJobInfoCount);
315                 for (int i = 0; i < printJobInfoCount; i++) {
316                     printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
317                 }
318             }
319             if (printJobs != null) {
320                 return printJobs;
321             }
322         } catch (RemoteException re) {
323             Log.e(LOG_TAG, "Error calling getPrintJobs()", re);
324         }
325         return Collections.emptyList();
326     }
327 
328     /**
329      * Generates a global printer id given the printer's locally unique one.
330      *
331      * @param localId A locally unique id in the context of your print service.
332      * @return Global printer id.
333      */
generatePrinterId(String localId)334     public final PrinterId generatePrinterId(String localId) {
335         throwIfNotCalledOnMainThread();
336         return new PrinterId(new ComponentName(getPackageName(),
337                 getClass().getName()), localId);
338     }
339 
throwIfNotCalledOnMainThread()340     static void throwIfNotCalledOnMainThread() {
341         if (!Looper.getMainLooper().isCurrentThread()) {
342             throw new IllegalAccessError("must be called from the main thread");
343         }
344     }
345 
346     @Override
onBind(Intent intent)347     public final IBinder onBind(Intent intent) {
348         return new IPrintService.Stub() {
349             @Override
350             public void createPrinterDiscoverySession() {
351                 mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
352             }
353 
354             @Override
355             public void destroyPrinterDiscoverySession() {
356                 mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
357             }
358 
359             public void startPrinterDiscovery(List<PrinterId> priorityList) {
360                 mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
361                         priorityList).sendToTarget();
362             }
363 
364             @Override
365             public void stopPrinterDiscovery() {
366                 mHandler.sendEmptyMessage(ServiceHandler.MSG_STOP_PRINTER_DISCOVERY);
367             }
368 
369             @Override
370             public void validatePrinters(List<PrinterId> printerIds) {
371                 mHandler.obtainMessage(ServiceHandler.MSG_VALIDATE_PRINTERS,
372                         printerIds).sendToTarget();
373             }
374 
375             @Override
376             public void startPrinterStateTracking(PrinterId printerId) {
377                 mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_STATE_TRACKING,
378                         printerId).sendToTarget();
379             }
380 
381             @Override
382             public void stopPrinterStateTracking(PrinterId printerId) {
383                 mHandler.obtainMessage(ServiceHandler.MSG_STOP_PRINTER_STATE_TRACKING,
384                         printerId).sendToTarget();
385             }
386 
387             @Override
388             public void setClient(IPrintServiceClient client) {
389                 mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
390                         .sendToTarget();
391             }
392 
393             @Override
394             public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
395                 mHandler.obtainMessage(ServiceHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
396                         printJobInfo).sendToTarget();
397             }
398 
399             @Override
400             public void onPrintJobQueued(PrintJobInfo printJobInfo) {
401                 mHandler.obtainMessage(ServiceHandler.MSG_ON_PRINTJOB_QUEUED,
402                         printJobInfo).sendToTarget();
403             }
404         };
405     }
406 
407     private final class ServiceHandler extends Handler {
408         public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
409         public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
410         public static final int MSG_START_PRINTER_DISCOVERY = 3;
411         public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
412         public static final int MSG_VALIDATE_PRINTERS = 5;
413         public static final int MSG_START_PRINTER_STATE_TRACKING = 6;
414         public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7;
415         public static final int MSG_ON_PRINTJOB_QUEUED = 8;
416         public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 9;
417         public static final int MSG_SET_CLEINT = 10;
418 
419         public ServiceHandler(Looper looper) {
420             super(looper, null, true);
421         }
422 
423         @Override
424         @SuppressWarnings("unchecked")
425         public void handleMessage(Message message) {
426             final int action = message.what;
427             switch (action) {
428                 case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
429                     if (DEBUG) {
430                         Log.i(LOG_TAG, "MSG_CREATE_PRINTER_DISCOVERY_SESSION "
431                                 + getPackageName());
432                     }
433                     PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
434                     if (session == null) {
435                         throw new NullPointerException("session cannot be null");
436                     }
437                     if (session.getId() == mLastSessionId) {
438                         throw new IllegalStateException("cannot reuse session instances");
439                     }
440                     mDiscoverySession = session;
441                     mLastSessionId = session.getId();
442                     session.setObserver(mClient);
443                 } break;
444 
445                 case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
446                     if (DEBUG) {
447                         Log.i(LOG_TAG, "MSG_DESTROY_PRINTER_DISCOVERY_SESSION "
448                                 + getPackageName());
449                     }
450                     if (mDiscoverySession != null) {
451                         mDiscoverySession.destroy();
452                         mDiscoverySession = null;
453                     }
454                 } break;
455 
456                 case MSG_START_PRINTER_DISCOVERY: {
457                     if (DEBUG) {
458                         Log.i(LOG_TAG, "MSG_START_PRINTER_DISCOVERY "
459                                 + getPackageName());
460                     }
461                     if (mDiscoverySession != null) {
462                         List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
463                         mDiscoverySession.startPrinterDiscovery(priorityList);
464                     }
465                 } break;
466 
467                 case MSG_STOP_PRINTER_DISCOVERY: {
468                     if (DEBUG) {
469                         Log.i(LOG_TAG, "MSG_STOP_PRINTER_DISCOVERY "
470                                 + getPackageName());
471                     }
472                     if (mDiscoverySession != null) {
473                         mDiscoverySession.stopPrinterDiscovery();
474                     }
475                 } break;
476 
477                 case MSG_VALIDATE_PRINTERS: {
478                     if (DEBUG) {
479                         Log.i(LOG_TAG, "MSG_VALIDATE_PRINTERS "
480                                 + getPackageName());
481                     }
482                     if (mDiscoverySession != null) {
483                         List<PrinterId> printerIds = (List<PrinterId>) message.obj;
484                         mDiscoverySession.validatePrinters(printerIds);
485                     }
486                 } break;
487 
488                 case MSG_START_PRINTER_STATE_TRACKING: {
489                     if (DEBUG) {
490                         Log.i(LOG_TAG, "MSG_START_PRINTER_STATE_TRACKING "
491                                 + getPackageName());
492                     }
493                     if (mDiscoverySession != null) {
494                         PrinterId printerId = (PrinterId) message.obj;
495                         mDiscoverySession.startPrinterStateTracking(printerId);
496                     }
497                 } break;
498 
499                 case MSG_STOP_PRINTER_STATE_TRACKING: {
500                     if (DEBUG) {
501                         Log.i(LOG_TAG, "MSG_STOP_PRINTER_STATE_TRACKING "
502                                 + getPackageName());
503                     }
504                     if (mDiscoverySession != null) {
505                         PrinterId printerId = (PrinterId) message.obj;
506                         mDiscoverySession.stopPrinterStateTracking(printerId);
507                     }
508                 } break;
509 
510                 case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
511                     if (DEBUG) {
512                         Log.i(LOG_TAG, "MSG_ON_REQUEST_CANCEL_PRINTJOB "
513                                 + getPackageName());
514                     }
515                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
516                     onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
517                 } break;
518 
519                 case MSG_ON_PRINTJOB_QUEUED: {
520                     if (DEBUG) {
521                         Log.i(LOG_TAG, "MSG_ON_PRINTJOB_QUEUED "
522                                 + getPackageName());
523                     }
524                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
525                     if (DEBUG) {
526                         Log.i(LOG_TAG, "Queued: " + printJobInfo);
527                     }
528                     onPrintJobQueued(new PrintJob(printJobInfo, mClient));
529                 } break;
530 
531                 case MSG_SET_CLEINT: {
532                     if (DEBUG) {
533                         Log.i(LOG_TAG, "MSG_SET_CLEINT "
534                                 + getPackageName());
535                     }
536                     mClient = (IPrintServiceClient) message.obj;
537                     if (mClient != null) {
538                         onConnected();
539                      } else {
540                         onDisconnected();
541                      }
542                 } break;
543 
544                 default: {
545                     throw new IllegalArgumentException("Unknown message: " + action);
546                 }
547             }
548         }
549     }
550 }
551