PrintService.java revision fd90651cfcc7e2b75254666fd6861038b72fb4ac
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.print.PrinterInfo;
31import android.util.Log;
32
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.List;
36
37/**
38 * <p>
39 * This is the base class for implementing print services. A print service
40 * knows how to discover and interact one or more printers via one or more
41 * protocols.
42 * </p>
43 * <h3>Printer discovery</h3>
44 * <p>
45 * A print service is responsible for discovering and reporting printers.
46 * A printer discovery period starts with a call to
47 * {@link #onStartPrinterDiscovery()} and ends with a call to
48 * {@link #onStopPrinterDiscovery()}. During a printer discovery
49 * period the print service reports newly discovered printers by
50 * calling {@link #addDiscoveredPrinters(List)} and added printers
51 * that disappeared by calling {@link #removeDiscoveredPrinters(List)}.
52 * Calls to {@link #addDiscoveredPrinters(List)} and
53 * {@link #removeDiscoveredPrinters(List)} before a call to
54 * {@link #onStartPrinterDiscovery()} and after a call to
55 * {@link #onStopPrinterDiscovery()} are a no-op.
56 * </p>
57 * <p>
58 * For every printer discovery period all printers have to be added. Each
59 * printer known to this print service should be added only once during a
60 * discovery period, unless it was added and then removed before that.
61 * Only an already added printer can be removed.
62 * </p>
63 * <h3>Print jobs</h3>
64 * <p>
65 * When a new print job targeted to the printers managed by this print
66 * service is queued, i.e. ready for processing by the print service,
67 * a call to {@link #onPrintJobQueued(PrintJob)} is made and the print
68 * service may handle it immediately or schedule that for an appropriate
69 * time in the future. The list of all print jobs for this service
70 * are be available by calling {@link #getPrintJobs()}. A queued print
71 * job is one whose {@link PrintJob#isQueued()} return true.
72 * </p>
73 * <p>
74 * A print service is responsible for setting the print job state as
75 * appropriate while processing it. Initially, a print job is in a
76 * {@link PrintJobInfo#STATE_QUEUED} state which means that the data to
77 * be printed is spooled by the system and the print service can obtain
78 * that data by calling {@link PrintJob#getData()}. After the print
79 * service starts printing the data it should set the print job state
80 * to {@link PrintJobInfo#STATE_STARTED}. Upon successful completion, the
81 * print job state has to be set to {@link PrintJobInfo#STATE_COMPLETED}.
82 * In a case of a failure, the print job state should be set to
83 * {@link PrintJobInfo#STATE_FAILED}. If a print job is in a
84 * {@link PrintJobInfo#STATE_STARTED} state and the user requests to
85 * cancel it, the print service will receive a call to
86 * {@link #onRequestCancelPrintJob(PrintJob)} which requests from the
87 * service to do a best effort in canceling the job. In case the job
88 * is successfully canceled, its state has to be set to
89 * {@link PrintJobInfo#STATE_CANCELED}.
90 * </p>
91 * <h3>Lifecycle</h3>
92 * <p>
93 * The lifecycle of a print service is managed exclusively by the system
94 * and follows the established service lifecycle. Additionally, starting
95 * or stopping a print service is triggered exclusively by an explicit
96 * user action through enabling or disabling it in the device settings.
97 * After the system binds to a print service, it calls {@link #onConnected()}.
98 * This method can be overriden by clients to perform post binding setup.
99 * Also after the system unbinds from a print service, it calls
100 * {@link #onDisconnected()}. This method can be overriden by clients to
101 * perform post unbinding cleanup.
102 * </p>
103 * <h3>Declaration</h3>
104 * <p>
105 * A print service is declared as any other service in an AndroidManifest.xml
106 * but it must also specify that it handles the {@link android.content.Intent}
107 * with action {@link #SERVICE_INTERFACE}. Failure to declare this intent
108 * will cause the system to ignore the print service. Additionally, a print
109 * service must request the {@link android.Manifest.permission#BIND_PRINT_SERVICE}
110 * permission to ensure that only the system can bind to it. Failure to
111 * declare this intent will cause the system to ignore the print service.
112 * Following is an example declaration:
113 * </p>
114 * <pre>
115 * &lt;service android:name=".MyPrintService"
116 *         android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
117 *     &lt;intent-filter&gt;
118 *         &lt;action android:name="android.printservice.PrintService" /&gt;
119 *     &lt;/intent-filter&gt;
120 *     . . .
121 * &lt;/service&gt;
122 * </pre>
123 * <h3>Configuration</h3>
124 * <p>
125 * A print service can be configured by specifying an optional settings
126 * activity which exposes service specific options, an optional add
127 * prints activity which is used for manual addition of printers, etc.
128 * It is a responsibility of the system to launch the settings and add
129 * printers activities when appropriate.
130 * </p>
131 * <p>
132 * A print service is configured by providing a
133 * {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
134 * the service. A service declaration with a meta-data tag is presented
135 * 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 refer to {@link #SERVICE_META_DATA} and
146 * <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>.
147 * </p>
148 */
149public abstract class PrintService extends Service {
150
151    private static final String LOG_TAG = PrintService.class.getSimpleName();
152
153    /**
154     * The {@link Intent} action that must be declared as handled by a service
155     * in its manifest to allow the system to recognize it as a print service.
156     */
157    public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
158
159    /**
160     * Name under which a PrintService component publishes additional information
161     * about itself. This meta-data must reference an XML resource containing a
162     * <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>
163     * tag. This is a a sample XML file configuring a print service:
164     * <pre> &lt;print-service
165     *     android:settingsActivity="foo.bar.MySettingsActivity"
166     *     andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
167     *     . . .
168     * /&gt;</pre>
169     */
170    public static final String SERVICE_META_DATA = "android.printservice";
171
172    private final Object mLock = new Object();
173
174    private Handler mHandler;
175
176    private IPrintServiceClient mClient;
177
178    private boolean mDiscoveringPrinters;
179
180    @Override
181    protected void attachBaseContext(Context base) {
182        super.attachBaseContext(base);
183        mHandler = new MyHandler(base.getMainLooper());
184    }
185
186    /**
187     * The system has connected to this service.
188     */
189    protected void onConnected() {
190        /* do nothing */
191    }
192
193    /**
194     * The system has disconnected from this service.
195     */
196    protected void onDisconnected() {
197        /* do nothing */
198    }
199
200    /**
201     * Callback requesting from this service to start printer discovery.
202     * At the end of the printer discovery period the system will call
203     * {@link #onStopPrinterDiscovery()}. Discovered printers should be
204     * reported by calling #addDiscoveredPrinters(List) and reported ones
205     * that disappear should be reported by calling
206     * {@link #removeDiscoveredPrinters(List)}.
207     *
208     * @see #onStopPrinterDiscovery()
209     * @see #addDiscoveredPrinters(List)
210     * @see #removeDiscoveredPrinters(List)
211     */
212    protected abstract void onStartPrinterDiscovery();
213
214    /**
215     * Callback requesting from this service to stop printer discovery.
216     *
217     * @see #onStartPrinterDiscovery()
218     * @see #addDiscoveredPrinters(List)
219     * @see #removeDiscoveredPrinters(List)
220     */
221    protected abstract void onStopPrinterDiscovery();
222
223    /**
224     * Adds discovered printers. This method should be called during a
225     * printer discovery period, i.e. after a call to
226     * {@link #onStartPrinterDiscovery()} and before the corresponding
227     * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
228     * <p>
229     * <strong>Note:</strong> For every printer discovery period all
230     * printers have to be added. You can call this method as many times as
231     * necessary during the discovery period but should not pass in already
232     * added printers. If a printer is already added in the same printer
233     * discovery period, it will be ignored.
234     * </p>
235     *
236     * @param printers A list with discovered printers.
237     *
238     * @throws IllegalStateException If this service is not connected.
239     *
240     * @see #removeDiscoveredPrinters(List)
241     * @see #onStartPrinterDiscovery()
242     * @see #onStopPrinterDiscovery()
243     */
244    public final void addDiscoveredPrinters(List<PrinterInfo> printers) {
245        synchronized (mLock) {
246            if (mClient == null) {
247                throw new IllegalStateException("Print serivice not connected!");
248            }
249            if (mDiscoveringPrinters) {
250                try {
251                    // Calling with a lock into the system is fine.
252                    mClient.addDiscoveredPrinters(printers);
253                } catch (RemoteException re) {
254                    Log.e(LOG_TAG, "Error adding discovered printers!", re);
255                }
256            }
257        }
258    }
259
260    /**
261     * Removes discovered printers given their ids. This method should be called
262     * during a printer discovery period, i.e. after a call to
263     * {@link #onStartPrinterDiscovery()} and before the corresponding
264     * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
265     * <p>
266     * For every printer discovery period all printers have to be added. You
267     * should remove only printers that were added in this printer discovery
268     * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
269     * this method as many times as necessary during the discovery period
270     * but should not pass in already removed printer ids. If a printer with
271     * a given id is already removed in the same discovery period, it will
272     * be ignored.
273     * </p>
274     *
275     * @param printerIds A list with disappeared printer ids.
276     *
277     * @throws IllegalStateException If this service is not connected.
278     *
279     * @see #addDiscoveredPrinters(List)
280     * @see #onStartPrinterDiscovery()
281     * @see #onStopPrinterDiscovery()
282     */
283    public final void removeDiscoveredPrinters(List<PrinterId> printerIds) {
284        synchronized (mLock) {
285            if (mClient == null) {
286                throw new IllegalStateException("Print serivice not connected!");
287            }
288            if (mDiscoveringPrinters) {
289                try {
290                    // Calling with a lock into the system is fine.
291                    mClient.removeDiscoveredPrinters(printerIds);
292                } catch (RemoteException re) {
293                    Log.e(LOG_TAG, "Error removing discovered printers!", re);
294                }
295            }
296        }
297    }
298
299    /**
300     * Called when canceling of a print job is requested. The service
301     * should do best effort to fulfill the request. After the print
302     * job is canceled by calling {@link PrintJob#cancel()}.
303     *
304     * @param printJob The print job to be canceled.
305     */
306    protected void onRequestCancelPrintJob(PrintJob printJob) {
307    }
308
309    /**
310     * Called when there is a queued print job for one of the printers
311     * managed by this print service. A queued print job is ready for
312     * processing by a print service which can get the data to be printed
313     * by calling {@link PrintJob#getData()}. This service may start
314     * processing the passed in print job or schedule handling of queued
315     * print jobs at a convenient time. The service can get the print
316     * jobs by a call to {@link #getPrintJobs()} and examine their state
317     * to find the ones with state {@link PrintJobInfo#STATE_QUEUED}.
318     *
319     * @param printJob The new queued print job.
320     *
321     * @see #getPrintJobs()
322     */
323    protected abstract void onPrintJobQueued(PrintJob printJob);
324
325    /**
326     * Gets the print jobs for the printers managed by this service.
327     *
328     * @return The print jobs.
329     *
330     * @throws IllegalStateException If this service is not connected.
331     */
332    public final List<PrintJob> getPrintJobs() {
333        synchronized (mLock) {
334            if (mClient == null) {
335                throw new IllegalStateException("Print serivice not connected!");
336            }
337            try {
338                List<PrintJob> printJobs = null;
339                List<PrintJobInfo> printJobInfos = mClient.getPrintJobs();
340                if (printJobInfos != null) {
341                    final int printJobInfoCount = printJobInfos.size();
342                    printJobs = new ArrayList<PrintJob>(printJobInfoCount);
343                    for (int i = 0; i < printJobInfoCount; i++) {
344                        printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
345                    }
346                }
347                if (printJobs != null) {
348                    return printJobs;
349                }
350            } catch (RemoteException re) {
351                Log.e(LOG_TAG, "Error calling getPrintJobs()", re);
352            }
353            return Collections.emptyList();
354        }
355    }
356
357    /**
358     * Generates a global printer id from a local id. The local id is unique
359     * only within this print service.
360     *
361     * @param localId The local id.
362     * @return Global printer id.
363     */
364    public final PrinterId generatePrinterId(String localId) {
365        return new PrinterId(new ComponentName(getPackageName(),
366                getClass().getName()), localId);
367    }
368
369    @Override
370    public final IBinder onBind(Intent intent) {
371        return new IPrintService.Stub() {
372            @Override
373            public void setClient(IPrintServiceClient client) {
374                mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLEINT, client).sendToTarget();
375            }
376
377            @Override
378            public void startPrinterDiscovery() {
379                mHandler.sendEmptyMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY);
380            }
381
382            @Override
383            public void stopPrinterDiscovery() {
384                mHandler.sendEmptyMessage(MyHandler.MESSAGE_STOP_PRINTER_DISCOVERY);
385            }
386
387            @Override
388            public void requestCancelPrintJob(PrintJobInfo printJob) {
389                mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB, printJob).sendToTarget();
390            }
391
392            @Override
393            public void onPrintJobQueued(PrintJobInfo printJob) {
394                mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED,
395                        printJob).sendToTarget();
396            }
397        };
398    }
399
400    private final class MyHandler extends Handler {
401        public static final int MESSAGE_START_PRINTER_DISCOVERY = 1;
402        public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2;
403        public static final int MESSAGE_CANCEL_PRINTJOB = 3;
404        public static final int MESSAGE_ON_PRINTJOB_QUEUED = 4;
405        public static final int MESSAGE_SET_CLEINT = 5;
406
407        public MyHandler(Looper looper) {
408            super(looper, null, true);
409        }
410
411        @Override
412        public void handleMessage(Message message) {
413            final int action = message.what;
414            switch (action) {
415                case MESSAGE_START_PRINTER_DISCOVERY: {
416                    synchronized (mLock) {
417                        mDiscoveringPrinters = true;
418                    }
419                    onStartPrinterDiscovery();
420                } break;
421
422                case MESSAGE_STOP_PRINTER_DISCOVERY: {
423                    synchronized (mLock) {
424                        mDiscoveringPrinters = false;
425                    }
426                    onStopPrinterDiscovery();
427                } break;
428
429                case MESSAGE_CANCEL_PRINTJOB: {
430                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
431                    onRequestCancelPrintJob(new PrintJob(printJob, mClient));
432                } break;
433
434                case MESSAGE_ON_PRINTJOB_QUEUED: {
435                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
436                    onPrintJobQueued(new PrintJob(printJob, mClient));
437                } break;
438
439                case MESSAGE_SET_CLEINT: {
440                    IPrintServiceClient client = (IPrintServiceClient) message.obj;
441                    synchronized (mLock) {
442                        mClient = client;
443                        if (client == null) {
444                            mDiscoveringPrinters = false;
445                        }
446                    }
447                    if (client != null) {
448                        onConnected();
449                     } else {
450                        onStopPrinterDiscovery();
451                        onDisconnected();
452                    }
453                } break;
454
455                default: {
456                    throw new IllegalArgumentException("Unknown message: " + action);
457                }
458            }
459        }
460    }
461}
462