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