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 * <service android:name=".MyPrintService" 116 * android:permission="android.permission.BIND_PRINT_SERVICE"> 117 * <intent-filter> 118 * <action android:name="android.printservice.PrintService" /> 119 * </intent-filter> 120 * . . . 121 * </service> 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> <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 refer to {@link #SERVICE_META_DATA} and 146 * <code><{@link android.R.styleable#PrintService print-service}></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><{@link android.R.styleable#PrintService print-service}></code> 163 * tag. This is a a sample XML file configuring a print service: 164 * <pre> <print-service 165 * android:settingsActivity="foo.bar.MySettingsActivity" 166 * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity." 167 * . . . 168 * /></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