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 * <service android:name=".MyPrintService" 118 * android:permission="android.permission.BIND_PRINT_SERVICE"> 119 * <intent-filter> 120 * <action android:name="android.printservice.PrintService" /> 121 * </intent-filter> 122 * . . . 123 * </service> 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> <service android:name=".MyPrintService" 137 * android:permission="android.permission.BIND_PRINT_SERVICE"> 138 * <intent-filter> 139 * <action android:name="android.printservice.PrintService" /> 140 * </intent-filter> 141 * <meta-data android:name="android.printservice" android:resource="@xml/printservice" /> 142 * </service></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><{@link android.R.styleable#PrintService 147 * print-service}></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 * <{@link android.R.styleable#PrintService print-service}></code> tag. This is 171 * a sample XML file configuring a print service: 172 * <pre> <print-service 173 * android:vendor="SomeVendor" 174 * android:settingsActivity="foo.bar.MySettingsActivity" 175 * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity." 176 * . . . 177 * /></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