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 * <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 */ 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 * <{@link android.R.styleable#PrintService print-service}></code> tag. This is 164 * a sample XML file configuring a print service: 165 * <pre> <print-service 166 * android:vendor="SomeVendor" 167 * android:settingsActivity="foo.bar.MySettingsActivity" 168 * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity." 169 * . . . 170 * /></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