blob: 820c2d8ff4efa8a6714214ff3217ba04399868cf [file] [log] [blame]
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -07001/*
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;
Svetoslav Ganova0027152013-06-25 14:59:53 -070028import android.print.IPrinterDiscoveryObserver;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070029import android.print.PrintJobInfo;
30import android.print.PrinterId;
31import android.print.PrinterInfo;
32import android.util.Log;
33
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37
38/**
39 * <p>
40 * This is the base class for implementing print services. A print service
41 * knows how to discover and interact one or more printers via one or more
42 * protocols.
43 * </p>
44 * <h3>Printer discovery</h3>
45 * <p>
46 * A print service is responsible for discovering and reporting printers.
47 * A printer discovery period starts with a call to
48 * {@link #onStartPrinterDiscovery()} and ends with a call to
49 * {@link #onStopPrinterDiscovery()}. During a printer discovery
50 * period the print service reports newly discovered printers by
Svetoslav Ganova0027152013-06-25 14:59:53 -070051 * calling {@link #addDiscoveredPrinters(List)} and reports added printers
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070052 * that disappeared by calling {@link #removeDiscoveredPrinters(List)}.
53 * Calls to {@link #addDiscoveredPrinters(List)} and
54 * {@link #removeDiscoveredPrinters(List)} before a call to
55 * {@link #onStartPrinterDiscovery()} and after a call to
Svetoslavfd906512013-06-24 09:04:48 -070056 * {@link #onStopPrinterDiscovery()} are a no-op.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070057 * </p>
58 * <p>
59 * For every printer discovery period all printers have to be added. Each
60 * printer known to this print service should be added only once during a
61 * discovery period, unless it was added and then removed before that.
62 * Only an already added printer can be removed.
63 * </p>
64 * <h3>Print jobs</h3>
65 * <p>
66 * When a new print job targeted to the printers managed by this print
67 * service is queued, i.e. ready for processing by the print service,
68 * a call to {@link #onPrintJobQueued(PrintJob)} is made and the print
69 * service may handle it immediately or schedule that for an appropriate
70 * time in the future. The list of all print jobs for this service
Svetoslav Ganova0027152013-06-25 14:59:53 -070071 * are be available by calling {@link #getPrintJobs()}.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070072 * </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
Svetoslav Ganova0027152013-06-25 14:59:53 -070078 * that data by calling {@link PrintJob#getDocument()}. A queued print
79 * job's {@link PrintJob#isQueued()} method returns true.
80 * </p>
81 * <p>
82 * After the print service starts printing the data it should set the
83 * print job state to {@link PrintJobInfo#STATE_STARTED} by calling
84 * {@link PrintJob#start()}. Upon successful completion, the print job
85 * state has to be set to {@link PrintJobInfo#STATE_COMPLETED} by calling
86 * {@link PrintJob#complete()}. In case of a failure, the print job
87 * state should be set to {@link PrintJobInfo#STATE_FAILED} by calling
88 * {@link PrintJob#fail(CharSequence)}. If a print job is in a
89 * {@link PrintJobInfo#STATE_STARTED} state, i.e. {@link PrintJob#isStarted()}
90 * return true, and the user requests to cancel it, the print service will
91 * receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which
92 * requests from the service to do a best effort in canceling the job. In
93 * case the job is successfully canceled, its state has to be set to
94 * {@link PrintJobInfo#STATE_CANCELED}. by calling {@link PrintJob#cancel()}.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -070095 * </p>
96 * <h3>Lifecycle</h3>
97 * <p>
98 * The lifecycle of a print service is managed exclusively by the system
99 * and follows the established service lifecycle. Additionally, starting
100 * or stopping a print service is triggered exclusively by an explicit
101 * user action through enabling or disabling it in the device settings.
102 * After the system binds to a print service, it calls {@link #onConnected()}.
103 * This method can be overriden by clients to perform post binding setup.
104 * Also after the system unbinds from a print service, it calls
105 * {@link #onDisconnected()}. This method can be overriden by clients to
106 * perform post unbinding cleanup.
107 * </p>
108 * <h3>Declaration</h3>
109 * <p>
110 * A print service is declared as any other service in an AndroidManifest.xml
111 * but it must also specify that it handles the {@link android.content.Intent}
112 * with action {@link #SERVICE_INTERFACE}. Failure to declare this intent
113 * will cause the system to ignore the print service. Additionally, a print
114 * service must request the {@link android.Manifest.permission#BIND_PRINT_SERVICE}
115 * permission to ensure that only the system can bind to it. Failure to
116 * declare this intent will cause the system to ignore the print service.
117 * Following is an example declaration:
118 * </p>
119 * <pre>
120 * &lt;service android:name=".MyPrintService"
121 * android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
122 * &lt;intent-filter&gt;
123 * &lt;action android:name="android.printservice.PrintService" /&gt;
124 * &lt;/intent-filter&gt;
125 * . . .
126 * &lt;/service&gt;
127 * </pre>
128 * <h3>Configuration</h3>
129 * <p>
130 * A print service can be configured by specifying an optional settings
131 * activity which exposes service specific options, an optional add
Svetoslav Ganova0027152013-06-25 14:59:53 -0700132 * prints activity which is used for manual addition of printers, vendor
133 * name ,etc. It is a responsibility of the system to launch the settings
134 * and add printers activities when appropriate.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700135 * </p>
136 * <p>
137 * A print service is configured by providing a
138 * {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
139 * the service. A service declaration with a meta-data tag is presented
140 * below:
141 * <pre> &lt;service android:name=".MyPrintService"
142 * android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
143 * &lt;intent-filter&gt;
144 * &lt;action android:name="android.printservice.PrintService" /&gt;
145 * &lt;/intent-filter&gt;
146 * &lt;meta-data android:name="android.printservice" android:resource="@xml/printservice" /&gt;
147 * &lt;/service&gt;</pre>
148 * </p>
149 * <p>
150 * For more details refer to {@link #SERVICE_META_DATA} and
151 * <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>.
152 * </p>
153 */
154public abstract class PrintService extends Service {
155
Svetoslav Ganova0027152013-06-25 14:59:53 -0700156 private static final String LOG_TAG = "PrintService";
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700157
158 /**
159 * The {@link Intent} action that must be declared as handled by a service
160 * in its manifest to allow the system to recognize it as a print service.
161 */
162 public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
163
164 /**
165 * Name under which a PrintService component publishes additional information
166 * about itself. This meta-data must reference an XML resource containing a
167 * <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>
168 * tag. This is a a sample XML file configuring a print service:
169 * <pre> &lt;print-service
Svetoslav Ganova0027152013-06-25 14:59:53 -0700170 * android:vendor="SomeVendor"
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700171 * android:settingsActivity="foo.bar.MySettingsActivity"
172 * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
173 * . . .
174 * /&gt;</pre>
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
Svetoslav Ganova0027152013-06-25 14:59:53 -0700184 private IPrinterDiscoveryObserver mDiscoveryObserver;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700185
186 @Override
187 protected void attachBaseContext(Context base) {
188 super.attachBaseContext(base);
189 mHandler = new MyHandler(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 requesting from this service to start printer discovery.
208 * At the end of the printer discovery period the system will call
Svetoslavfd906512013-06-24 09:04:48 -0700209 * {@link #onStopPrinterDiscovery()}. Discovered printers should be
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700210 * reported by calling #addDiscoveredPrinters(List) and reported ones
211 * that disappear should be reported by calling
212 * {@link #removeDiscoveredPrinters(List)}.
213 *
214 * @see #onStopPrinterDiscovery()
215 * @see #addDiscoveredPrinters(List)
216 * @see #removeDiscoveredPrinters(List)
217 */
218 protected abstract void onStartPrinterDiscovery();
219
220 /**
221 * Callback requesting from this service to stop printer discovery.
222 *
223 * @see #onStartPrinterDiscovery()
224 * @see #addDiscoveredPrinters(List)
225 * @see #removeDiscoveredPrinters(List)
226 */
227 protected abstract void onStopPrinterDiscovery();
228
229 /**
230 * Adds discovered printers. This method should be called during a
231 * printer discovery period, i.e. after a call to
232 * {@link #onStartPrinterDiscovery()} and before the corresponding
233 * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
234 * <p>
235 * <strong>Note:</strong> For every printer discovery period all
236 * printers have to be added. You can call this method as many times as
237 * necessary during the discovery period but should not pass in already
238 * added printers. If a printer is already added in the same printer
Svetoslav Ganova0027152013-06-25 14:59:53 -0700239 * discovery period, it will be ignored. If you want to update an already
240 * added printer, you should removed it and then re-add it.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700241 * </p>
242 *
243 * @param printers A list with discovered printers.
244 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700245 * @see #removeDiscoveredPrinters(List)
246 * @see #onStartPrinterDiscovery()
247 * @see #onStopPrinterDiscovery()
Svetoslav Ganova0027152013-06-25 14:59:53 -0700248 *
249 * @throws IllegalStateException If this service is not connected.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700250 */
251 public final void addDiscoveredPrinters(List<PrinterInfo> printers) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700252 final IPrinterDiscoveryObserver observer;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700253 synchronized (mLock) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700254 throwIfNotConnectedLocked();
255 observer = mDiscoveryObserver;
256 }
257 if (observer != null) {
258 try {
259 observer.addDiscoveredPrinters(printers);
260 } catch (RemoteException re) {
261 Log.e(LOG_TAG, "Error adding discovered printers", re);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700262 }
263 }
264 }
265
266 /**
267 * Removes discovered printers given their ids. This method should be called
268 * during a printer discovery period, i.e. after a call to
269 * {@link #onStartPrinterDiscovery()} and before the corresponding
270 * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
271 * <p>
272 * For every printer discovery period all printers have to be added. You
273 * should remove only printers that were added in this printer discovery
274 * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
275 * this method as many times as necessary during the discovery period
276 * but should not pass in already removed printer ids. If a printer with
277 * a given id is already removed in the same discovery period, it will
Svetoslav Ganova0027152013-06-25 14:59:53 -0700278 * be ignored. If you want to update an already added printer, you should
279 * removed it and then re-add it.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700280 * </p>
281 *
282 * @param printerIds A list with disappeared printer ids.
283 *
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700284 * @see #addDiscoveredPrinters(List)
285 * @see #onStartPrinterDiscovery()
286 * @see #onStopPrinterDiscovery()
Svetoslav Ganova0027152013-06-25 14:59:53 -0700287 *
288 * @throws IllegalStateException If this service is not connected.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700289 */
290 public final void removeDiscoveredPrinters(List<PrinterId> printerIds) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700291 final IPrinterDiscoveryObserver observer;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700292 synchronized (mLock) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700293 throwIfNotConnectedLocked();
294 observer = mDiscoveryObserver;
295 }
296 if (observer != null) {
297 try {
298 observer.removeDiscoveredPrinters(printerIds);
299 } catch (RemoteException re) {
300 Log.e(LOG_TAG, "Error removing discovered printers", re);
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700301 }
302 }
303 }
304
305 /**
306 * Called when canceling of a print job is requested. The service
Svetoslav Ganova0027152013-06-25 14:59:53 -0700307 * should do best effort to fulfill the request. After the cancellation
308 * is performed, the print job should be set to a cancelled state by
309 * calling {@link PrintJob#cancel()}.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700310 *
311 * @param printJob The print job to be canceled.
312 */
313 protected void onRequestCancelPrintJob(PrintJob printJob) {
314 }
315
316 /**
317 * Called when there is a queued print job for one of the printers
318 * managed by this print service. A queued print job is ready for
319 * processing by a print service which can get the data to be printed
Svetoslav Ganova0027152013-06-25 14:59:53 -0700320 * by calling {@link PrintJob#getDocument()}. This service may start
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700321 * processing the passed in print job or schedule handling of queued
322 * print jobs at a convenient time. The service can get the print
323 * jobs by a call to {@link #getPrintJobs()} and examine their state
Svetoslav Ganova0027152013-06-25 14:59:53 -0700324 * to find the ones with state {@link PrintJobInfo#STATE_QUEUED} by
325 * calling {@link PrintJob#isQueued()}.
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700326 *
327 * @param printJob The new queued print job.
328 *
329 * @see #getPrintJobs()
330 */
331 protected abstract void onPrintJobQueued(PrintJob printJob);
332
333 /**
334 * Gets the print jobs for the printers managed by this service.
335 *
336 * @return The print jobs.
337 *
338 * @throws IllegalStateException If this service is not connected.
339 */
340 public final List<PrintJob> getPrintJobs() {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700341 final IPrintServiceClient client;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700342 synchronized (mLock) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700343 throwIfNotConnectedLocked();
344 client = mClient;
345 }
346 if (client == null) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700347 return Collections.emptyList();
348 }
Svetoslav Ganova0027152013-06-25 14:59:53 -0700349 try {
350 List<PrintJob> printJobs = null;
351 List<PrintJobInfo> printJobInfos = client.getPrintJobInfos();
352 if (printJobInfos != null) {
353 final int printJobInfoCount = printJobInfos.size();
354 printJobs = new ArrayList<PrintJob>(printJobInfoCount);
355 for (int i = 0; i < printJobInfoCount; i++) {
356 printJobs.add(new PrintJob(printJobInfos.get(i), client));
357 }
358 }
359 if (printJobs != null) {
360 return printJobs;
361 }
362 } catch (RemoteException re) {
363 Log.e(LOG_TAG, "Error calling getPrintJobs()", re);
364 }
365 return Collections.emptyList();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700366 }
367
368 /**
369 * Generates a global printer id from a local id. The local id is unique
370 * only within this print service.
371 *
372 * @param localId The local id.
373 * @return Global printer id.
374 */
375 public final PrinterId generatePrinterId(String localId) {
376 return new PrinterId(new ComponentName(getPackageName(),
377 getClass().getName()), localId);
378 }
379
380 @Override
381 public final IBinder onBind(Intent intent) {
382 return new IPrintService.Stub() {
383 @Override
384 public void setClient(IPrintServiceClient client) {
385 mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLEINT, client).sendToTarget();
386 }
387
388 @Override
Svetoslav Ganova0027152013-06-25 14:59:53 -0700389 public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
390 mHandler.obtainMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY,
391 observer).sendToTarget();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700392 }
393
394 @Override
395 public void stopPrinterDiscovery() {
396 mHandler.sendEmptyMessage(MyHandler.MESSAGE_STOP_PRINTER_DISCOVERY);
397 }
398
399 @Override
Svetoslav Ganova0027152013-06-25 14:59:53 -0700400 public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
401 mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB,
402 printJobInfo).sendToTarget();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700403 }
404
405 @Override
Svetoslav Ganova0027152013-06-25 14:59:53 -0700406 public void onPrintJobQueued(PrintJobInfo printJobInfo) {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700407 mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED,
Svetoslav Ganova0027152013-06-25 14:59:53 -0700408 printJobInfo).sendToTarget();
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700409 }
410 };
411 }
412
Svetoslav Ganova0027152013-06-25 14:59:53 -0700413 private void throwIfNotConnectedLocked() {
414 if (mClient == null) {
415 throw new IllegalStateException("Print serivice not connected");
416 }
417 }
418
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700419 private final class MyHandler extends Handler {
420 public static final int MESSAGE_START_PRINTER_DISCOVERY = 1;
421 public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2;
422 public static final int MESSAGE_CANCEL_PRINTJOB = 3;
423 public static final int MESSAGE_ON_PRINTJOB_QUEUED = 4;
424 public static final int MESSAGE_SET_CLEINT = 5;
425
426 public MyHandler(Looper looper) {
427 super(looper, null, true);
428 }
429
430 @Override
431 public void handleMessage(Message message) {
432 final int action = message.what;
433 switch (action) {
434 case MESSAGE_START_PRINTER_DISCOVERY: {
435 synchronized (mLock) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700436 mDiscoveryObserver = (IPrinterDiscoveryObserver) message.obj;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700437 }
438 onStartPrinterDiscovery();
439 } break;
440
441 case MESSAGE_STOP_PRINTER_DISCOVERY: {
442 synchronized (mLock) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700443 mDiscoveryObserver = null;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700444 }
445 onStopPrinterDiscovery();
446 } break;
447
448 case MESSAGE_CANCEL_PRINTJOB: {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700449 PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
450 onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700451 } break;
452
453 case MESSAGE_ON_PRINTJOB_QUEUED: {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700454 PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
455 onPrintJobQueued(new PrintJob(printJobInfo, mClient));
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700456 } break;
457
458 case MESSAGE_SET_CLEINT: {
459 IPrintServiceClient client = (IPrintServiceClient) message.obj;
460 synchronized (mLock) {
461 mClient = client;
462 if (client == null) {
Svetoslav Ganova0027152013-06-25 14:59:53 -0700463 mDiscoveryObserver = null;
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700464 }
465 }
466 if (client != null) {
467 onConnected();
468 } else {
Svetoslav Ganov4b9a4d12013-06-11 15:20:06 -0700469 onDisconnected();
470 }
471 } break;
472
473 default: {
474 throw new IllegalArgumentException("Unknown message: " + action);
475 }
476 }
477 }
478 }
479}