PrintService.java revision 798bed6cc7d273e72b0253288605db9cd2b57740
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.IPrinterDiscoverySessionObserver;
29import android.print.PrintJobInfo;
30import android.print.PrinterId;
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 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(CharSequence) PrintJob.fail(
81 * CharSequence)} 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 */
150public abstract class PrintService extends Service {
151
152    private static final String LOG_TAG = "PrintService";
153
154    /**
155     * The {@link Intent} action that must be declared as handled by a service
156     * in its manifest for the system to recognize it as a print service.
157     */
158    public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
159
160    /**
161     * Name under which a {@link PrintService} component publishes additional information
162     * about itself. This meta-data must reference a XML resource containing a <code>
163     * &lt;{@link android.R.styleable#PrintService print-service}&gt;</code> tag. This is
164     * a sample XML file configuring a print service:
165     * <pre> &lt;print-service
166     *     android:vendor="SomeVendor"
167     *     android:settingsActivity="foo.bar.MySettingsActivity"
168     *     andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
169     *     . . .
170     * /&gt;</pre>
171     * <p>
172     * For detailed configuration options that can be specified via the meta-data
173     * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}.
174     * </p>
175     */
176    public static final String SERVICE_META_DATA = "android.printservice";
177
178    private final Object mLock = new Object();
179
180    private Handler mHandler;
181
182    private IPrintServiceClient mClient;
183
184    private int mLastSessionId = -1;
185
186    @Override
187    protected final void attachBaseContext(Context base) {
188        super.attachBaseContext(base);
189        mHandler = new ServiceHandler(base.getMainLooper());
190    }
191
192    /**
193     * The system has connected to this service.
194     */
195    protected void onConnected() {
196        /* do nothing */
197    }
198
199    /**
200     * The system has disconnected from this service.
201     */
202    protected void onDisconnected() {
203        /* do nothing */
204    }
205
206    /**
207     * Callback asking you to create a new {@link PrinterDiscoverySession}.
208     *
209     * @see PrinterDiscoverySession
210     */
211    protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession();
212
213    /**
214     * Called when cancellation of a print job is requested. The service
215     * should do best effort to fulfill the request. After the cancellation
216     * is performed, the print job should be marked as cancelled state by
217     * calling {@link PrintJob#cancel()}.
218     *
219     * @param printJob The print job to cancel.
220     *
221     * @see PrintJob#cancel() PrintJob.cancel()
222     * @see PrintJob#isCancelled() PrintJob.isCancelled()
223     */
224    protected abstract void onRequestCancelPrintJob(PrintJob printJob);
225
226    /**
227     * Called when there is a queued print job for one of the printers
228     * managed by this print service.
229     *
230     * @param printJob The new queued print job.
231     *
232     * @see PrintJob#isQueued() PrintJob.isQueued()
233     * @see #getActivePrintJobs()
234     */
235    protected abstract void onPrintJobQueued(PrintJob printJob);
236
237    /**
238     * Gets the active print jobs for the printers managed by this service.
239     * Active print jobs are ones that are not in a final state, i.e. whose
240     * state is queued or started.
241     *
242     * @return The active print jobs.
243     *
244     * @see PrintJob#isQueued() PrintJob.isQueued()
245     * @see PrintJob#isStarted() PrintJob.isStarted()
246     */
247    public final List<PrintJob> getActivePrintJobs() {
248        final IPrintServiceClient client;
249        synchronized (mLock) {
250            client = mClient;
251        }
252        if (client == null) {
253            return Collections.emptyList();
254        }
255        try {
256            List<PrintJob> printJobs = null;
257            List<PrintJobInfo> printJobInfos = client.getPrintJobInfos();
258            if (printJobInfos != null) {
259                final int printJobInfoCount = printJobInfos.size();
260                printJobs = new ArrayList<PrintJob>(printJobInfoCount);
261                for (int i = 0; i < printJobInfoCount; i++) {
262                    printJobs.add(new PrintJob(printJobInfos.get(i), client));
263                }
264            }
265            if (printJobs != null) {
266                return printJobs;
267            }
268        } catch (RemoteException re) {
269            Log.e(LOG_TAG, "Error calling getPrintJobs()", re);
270        }
271        return Collections.emptyList();
272    }
273
274    /**
275     * Generates a global printer id given the printer's locally unique one.
276     *
277     * @param localId A locally unique id in the context of your print service.
278     * @return Global printer id.
279     */
280    public final PrinterId generatePrinterId(String localId) {
281        return new PrinterId(new ComponentName(getPackageName(),
282                getClass().getName()), localId);
283    }
284
285    @Override
286    public final IBinder onBind(Intent intent) {
287        return new IPrintService.Stub() {
288            @Override
289            public void setClient(IPrintServiceClient client) {
290                mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
291                        .sendToTarget();
292            }
293
294            @Override
295            public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
296                mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION,
297                        observer).sendToTarget();
298            }
299
300            @Override
301            public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
302                mHandler.obtainMessage(ServiceHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
303                        printJobInfo).sendToTarget();
304            }
305
306            @Override
307            public void onPrintJobQueued(PrintJobInfo printJobInfo) {
308                mHandler.obtainMessage(ServiceHandler.MSG_ON_PRINTJOB_QUEUED,
309                        printJobInfo).sendToTarget();
310            }
311        };
312    }
313
314    private final class ServiceHandler extends Handler {
315        public static final int MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION = 1;
316        public static final int MSG_ON_PRINTJOB_QUEUED = 2;
317        public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3;
318        public static final int MSG_SET_CLEINT = 4;
319
320        public ServiceHandler(Looper looper) {
321            super(looper, null, true);
322        }
323
324        @Override
325        public void handleMessage(Message message) {
326            final int action = message.what;
327            switch (action) {
328                case MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION: {
329                    IPrinterDiscoverySessionObserver observer =
330                            (IPrinterDiscoverySessionObserver) message.obj;
331                    PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
332                    if (session == null) {
333                        throw new NullPointerException("session cannot be null");
334                    }
335                    synchronized (mLock) {
336                        if (session.getId() == mLastSessionId) {
337                            throw new IllegalStateException("cannot reuse sessions");
338                        }
339                        mLastSessionId = session.getId();
340                    }
341                    session.setObserver(observer);
342                } break;
343
344                case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
345                    PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
346                    onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
347                } break;
348
349                case MSG_ON_PRINTJOB_QUEUED: {
350                    PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
351                    onPrintJobQueued(new PrintJob(printJobInfo, mClient));
352                } break;
353
354                case MSG_SET_CLEINT: {
355                    IPrintServiceClient client = (IPrintServiceClient) message.obj;
356                    synchronized (mLock) {
357                        mClient = client;
358                    }
359                    if (client != null) {
360                        onConnected();
361                     } else {
362                        onDisconnected();
363                    }
364                } break;
365
366                default: {
367                    throw new IllegalArgumentException("Unknown message: " + action);
368                }
369            }
370        }
371    }
372}
373