PrinterDiscoverySession.java revision bb9f686b40743df2642b7d3b7778dbf7284ae665
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.annotation.NonNull; 20import android.content.pm.ParceledListSlice; 21import android.os.RemoteException; 22import android.print.PrinterCapabilitiesInfo; 23import android.print.PrinterId; 24import android.print.PrinterInfo; 25import android.util.ArrayMap; 26import android.util.Log; 27 28import java.util.ArrayList; 29import java.util.Collections; 30import java.util.List; 31 32/** 33 * This class encapsulates the interaction between a print service and the 34 * system during printer discovery. During printer discovery you are responsible 35 * for adding discovered printers, removing previously added printers that 36 * disappeared, and updating already added printers. 37 * <p> 38 * During the lifetime of this session you may be asked to start and stop 39 * performing printer discovery multiple times. You will receive a call to {@link 40 * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer 41 * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()} 42 * to stop printer discovery. When the system is no longer interested in printers 43 * discovered by this session you will receive a call to {@link #onDestroy()} at 44 * which point the system will no longer call into the session and all the session 45 * methods will do nothing. 46 * </p> 47 * <p> 48 * Discovered printers are added by invoking {@link 49 * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared are 50 * removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. Added 51 * printers whose properties or capabilities changed are updated through a call to 52 * {@link PrinterDiscoverySession#addPrinters(List)}. The printers added in this 53 * session can be acquired via {@link #getPrinters()} where the returned printers 54 * will be an up-to-date snapshot of the printers that you reported during the 55 * session. Printers are <strong>not</strong> persisted across sessions. 56 * </p> 57 * <p> 58 * The system will make a call to {@link #onValidatePrinters(List)} if you 59 * need to update some printers. It is possible that you add a printer without 60 * specifying its capabilities. This enables you to avoid querying all discovered 61 * printers for their capabilities, rather querying the capabilities of a printer 62 * only if necessary. For example, the system will request that you update a printer 63 * if it gets selected by the user. When validating printers you do not need to 64 * provide the printers' capabilities but may do so. 65 * </p> 66 * <p> 67 * If the system is interested in being constantly updated for the state of a 68 * printer you will receive a call to {@link #onStartPrinterStateTracking(PrinterId)} 69 * after which you will have to do a best effort to keep the system updated for 70 * changes in the printer state and capabilities. You also <strong>must</strong> 71 * update the printer capabilities if you did not provide them when adding it, or 72 * the printer will be ignored. When the system is no longer interested in getting 73 * updates for a printer you will receive a call to {@link #onStopPrinterStateTracking( 74 * PrinterId)}. 75 * </p> 76 * <p> 77 * <strong>Note: </strong> All callbacks in this class are executed on the main 78 * application thread. You also have to invoke any method of this class on the main 79 * application thread. 80 * </p> 81 */ 82public abstract class PrinterDiscoverySession { 83 private static final String LOG_TAG = "PrinterDiscoverySession"; 84 85 private static int sIdCounter = 0; 86 87 private final int mId; 88 89 private final ArrayMap<PrinterId, PrinterInfo> mPrinters = 90 new ArrayMap<PrinterId, PrinterInfo>(); 91 92 private final List<PrinterId> mTrackedPrinters = 93 new ArrayList<PrinterId>(); 94 95 private ArrayMap<PrinterId, PrinterInfo> mLastSentPrinters; 96 97 private IPrintServiceClient mObserver; 98 99 private boolean mIsDestroyed; 100 101 private boolean mIsDiscoveryStarted; 102 103 /** 104 * Constructor. 105 */ 106 public PrinterDiscoverySession() { 107 mId = sIdCounter++; 108 } 109 110 void setObserver(IPrintServiceClient observer) { 111 mObserver = observer; 112 // If some printers were added in the method that 113 // created the session, send them over. 114 if (!mPrinters.isEmpty()) { 115 try { 116 mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(getPrinters())); 117 } catch (RemoteException re) { 118 Log.e(LOG_TAG, "Error sending added printers", re); 119 } 120 } 121 } 122 123 int getId() { 124 return mId; 125 } 126 127 /** 128 * Gets the printers reported in this session. For example, if you add two 129 * printers and remove one of them, the returned list will contain only 130 * the printer that was added but not removed. 131 * <p> 132 * <strong>Note: </strong> Calls to this method after the session is 133 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 134 * </p> 135 * 136 * @return The printers. 137 * 138 * @see #addPrinters(List) 139 * @see #removePrinters(List) 140 * @see #isDestroyed() 141 */ 142 public final List<PrinterInfo> getPrinters() { 143 PrintService.throwIfNotCalledOnMainThread(); 144 if (mIsDestroyed) { 145 return Collections.emptyList(); 146 } 147 return new ArrayList<PrinterInfo>(mPrinters.values()); 148 } 149 150 /** 151 * Adds discovered printers. Adding an already added printer updates it. 152 * Removed printers can be added again. You can call this method multiple 153 * times during the life of this session. Duplicates will be ignored. 154 * <p> 155 * <strong>Note: </strong> Calls to this method after the session is 156 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 157 * </p> 158 * 159 * @param printers The printers to add. 160 * 161 * @see #removePrinters(List) 162 * @see #getPrinters() 163 * @see #isDestroyed() 164 */ 165 public final void addPrinters(List<PrinterInfo> printers) { 166 PrintService.throwIfNotCalledOnMainThread(); 167 168 // If the session is destroyed - nothing do to. 169 if (mIsDestroyed) { 170 Log.w(LOG_TAG, "Not adding printers - session destroyed."); 171 return; 172 } 173 174 if (mIsDiscoveryStarted) { 175 // If during discovery, add the new printers and send them. 176 List<PrinterInfo> addedPrinters = null; 177 final int addedPrinterCount = printers.size(); 178 for (int i = 0; i < addedPrinterCount; i++) { 179 PrinterInfo addedPrinter = printers.get(i); 180 PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter); 181 if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) { 182 if (addedPrinters == null) { 183 addedPrinters = new ArrayList<PrinterInfo>(); 184 } 185 addedPrinters.add(addedPrinter); 186 } 187 } 188 189 // Send the added printers, if such. 190 if (addedPrinters != null) { 191 try { 192 mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(addedPrinters)); 193 } catch (RemoteException re) { 194 Log.e(LOG_TAG, "Error sending added printers", re); 195 } 196 } 197 } else { 198 // Remember the last sent printers if needed. 199 if (mLastSentPrinters == null) { 200 mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters); 201 } 202 203 // Update the printers. 204 final int addedPrinterCount = printers.size(); 205 for (int i = 0; i < addedPrinterCount; i++) { 206 PrinterInfo addedPrinter = printers.get(i); 207 if (mPrinters.get(addedPrinter.getId()) == null) { 208 mPrinters.put(addedPrinter.getId(), addedPrinter); 209 } 210 } 211 } 212 } 213 214 /** 215 * Removes added printers. Removing an already removed or never added 216 * printer has no effect. Removed printers can be added again. You can 217 * call this method multiple times during the lifetime of this session. 218 * <p> 219 * <strong>Note: </strong> Calls to this method after the session is 220 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 221 * </p> 222 * 223 * @param printerIds The ids of the removed printers. 224 * 225 * @see #addPrinters(List) 226 * @see #getPrinters() 227 * @see #isDestroyed() 228 */ 229 public final void removePrinters(List<PrinterId> printerIds) { 230 PrintService.throwIfNotCalledOnMainThread(); 231 232 // If the session is destroyed - nothing do to. 233 if (mIsDestroyed) { 234 Log.w(LOG_TAG, "Not removing printers - session destroyed."); 235 return; 236 } 237 238 if (mIsDiscoveryStarted) { 239 // If during discovery, remove existing printers and send them. 240 List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>(); 241 final int removedPrinterIdCount = printerIds.size(); 242 for (int i = 0; i < removedPrinterIdCount; i++) { 243 PrinterId removedPrinterId = printerIds.get(i); 244 if (mPrinters.remove(removedPrinterId) != null) { 245 removedPrinterIds.add(removedPrinterId); 246 } 247 } 248 249 // Send the removed printers, if such. 250 if (!removedPrinterIds.isEmpty()) { 251 try { 252 mObserver.onPrintersRemoved(new ParceledListSlice<PrinterId>( 253 removedPrinterIds)); 254 } catch (RemoteException re) { 255 Log.e(LOG_TAG, "Error sending removed printers", re); 256 } 257 } 258 } else { 259 // Remember the last sent printers if needed. 260 if (mLastSentPrinters == null) { 261 mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters); 262 } 263 264 // Update the printers. 265 final int removedPrinterIdCount = printerIds.size(); 266 for (int i = 0; i < removedPrinterIdCount; i++) { 267 PrinterId removedPrinterId = printerIds.get(i); 268 mPrinters.remove(removedPrinterId); 269 } 270 } 271 } 272 273 private void sendOutOfDiscoveryPeriodPrinterChanges() { 274 // Noting changed since the last discovery period - nothing to do. 275 if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) { 276 mLastSentPrinters = null; 277 return; 278 } 279 280 // Determine the added printers. 281 List<PrinterInfo> addedPrinters = null; 282 for (PrinterInfo printer : mPrinters.values()) { 283 PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId()); 284 if (sentPrinter == null || !sentPrinter.equals(printer)) { 285 if (addedPrinters == null) { 286 addedPrinters = new ArrayList<PrinterInfo>(); 287 } 288 addedPrinters.add(printer); 289 } 290 } 291 292 // Send the added printers, if such. 293 if (addedPrinters != null) { 294 try { 295 mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(addedPrinters)); 296 } catch (RemoteException re) { 297 Log.e(LOG_TAG, "Error sending added printers", re); 298 } 299 } 300 301 // Determine the removed printers. 302 List<PrinterId> removedPrinterIds = null; 303 for (PrinterInfo sentPrinter : mLastSentPrinters.values()) { 304 if (!mPrinters.containsKey(sentPrinter.getId())) { 305 if (removedPrinterIds == null) { 306 removedPrinterIds = new ArrayList<PrinterId>(); 307 } 308 removedPrinterIds.add(sentPrinter.getId()); 309 } 310 } 311 312 // Send the removed printers, if such. 313 if (removedPrinterIds != null) { 314 try { 315 mObserver.onPrintersRemoved(new ParceledListSlice<PrinterId>(removedPrinterIds)); 316 } catch (RemoteException re) { 317 Log.e(LOG_TAG, "Error sending removed printers", re); 318 } 319 } 320 321 mLastSentPrinters = null; 322 } 323 324 /** 325 * Callback asking you to start printer discovery. Discovered printers should be 326 * added via calling {@link #addPrinters(List)}. Added printers that disappeared 327 * should be removed via calling {@link #removePrinters(List)}. Added printers 328 * whose properties or capabilities changed should be updated via calling {@link 329 * #addPrinters(List)}. You will receive a call to {@link #onStopPrinterDiscovery()} 330 * when you should stop printer discovery. 331 * <p> 332 * During the lifetime of this session all printers that are known to your print 333 * service have to be added. The system does not retain any printers across sessions. 334 * However, if you were asked to start and then stop performing printer discovery 335 * in this session, then a subsequent discovering should not re-discover already 336 * discovered printers. You can get the printers reported during this session by 337 * calling {@link #getPrinters()}. 338 * </p> 339 * <p> 340 * <strong>Note: </strong>You are also given a list of printers whose availability 341 * has to be checked first. For example, these printers could be the user's favorite 342 * ones, therefore they have to be verified first. You do <strong>not need</strong> 343 * to provide the capabilities of the printers, rather verify whether they exist 344 * similarly to {@link #onValidatePrinters(List)}. 345 * </p> 346 * 347 * @param priorityList The list of printers to validate first. Never null. 348 * 349 * @see #onStopPrinterDiscovery() 350 * @see #addPrinters(List) 351 * @see #removePrinters(List) 352 * @see #isPrinterDiscoveryStarted() 353 */ 354 public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList); 355 356 /** 357 * Callback notifying you that you should stop printer discovery. 358 * 359 * @see #onStartPrinterDiscovery(List) 360 * @see #isPrinterDiscoveryStarted() 361 */ 362 public abstract void onStopPrinterDiscovery(); 363 364 /** 365 * Callback asking you to validate that the given printers are valid, that 366 * is they exist. You are responsible for checking whether these printers 367 * exist and for the ones that do exist notify the system via calling 368 * {@link #addPrinters(List)}. 369 * <p> 370 * <strong>Note: </strong> You are <strong>not required</strong> to provide 371 * the printer capabilities when updating the printers that do exist. 372 * <p> 373 * 374 * @param printerIds The printers to validate. 375 * 376 * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) 377 * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) 378 */ 379 public abstract void onValidatePrinters(List<PrinterId> printerIds); 380 381 /** 382 * Callback asking you to start tracking the state of a printer. Tracking 383 * the state means that you should do a best effort to observe the state 384 * of this printer and notify the system if that state changes via calling 385 * {@link #addPrinters(List)}. 386 * <p> 387 * <strong>Note: </strong> A printer can be initially added without its 388 * capabilities to avoid polling printers that the user will not select. 389 * However, after this method is called you are expected to update the 390 * printer <strong>including</strong> its capabilities. Otherwise, the 391 * printer will be ignored. 392 * <p> 393 * <p> 394 * A scenario when you may be requested to track a printer's state is if 395 * the user selects that printer and the system has to present print 396 * options UI based on the printer's capabilities. In this case the user 397 * should be promptly informed if, for example, the printer becomes 398 * unavailable. 399 * </p> 400 * 401 * @param printerId The printer to start tracking. 402 * 403 * @see #onStopPrinterStateTracking(PrinterId) 404 * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) 405 * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) 406 */ 407 public abstract void onStartPrinterStateTracking(PrinterId printerId); 408 409 /** 410 * Request the custom icon for a printer. Once the icon is available use 411 * {@link CustomPrinterIconCallback#onCustomPrinterIconLoaded} to send the data to the print 412 * service. 413 * 414 * @param printerId The printer to icon belongs to. 415 * @param callback Callback for returning the icon to the print spooler. 416 * 417 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() 418 */ 419 public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId, 420 @NonNull CustomPrinterIconCallback callback) { 421 } 422 423 /** 424 * Callback asking you to stop tracking the state of a printer. The passed 425 * in printer id is the one for which you received a call to {@link 426 * #onStartPrinterStateTracking(PrinterId)}. 427 * 428 * @param printerId The printer to stop tracking. 429 * 430 * @see #onStartPrinterStateTracking(PrinterId) 431 */ 432 public abstract void onStopPrinterStateTracking(PrinterId printerId); 433 434 /** 435 * Gets the printers that should be tracked. These are printers that are 436 * important to the user and for which you received a call to {@link 437 * #onStartPrinterStateTracking(PrinterId)} asking you to observer their 438 * state and reporting it to the system via {@link #addPrinters(List)}. 439 * You will receive a call to {@link #onStopPrinterStateTracking(PrinterId)} 440 * if you should stop tracking a printer. 441 * <p> 442 * <strong>Note: </strong> Calls to this method after the session is 443 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 444 * </p> 445 * 446 * @return The printers. 447 * 448 * @see #onStartPrinterStateTracking(PrinterId) 449 * @see #onStopPrinterStateTracking(PrinterId) 450 * @see #isDestroyed() 451 */ 452 public final List<PrinterId> getTrackedPrinters() { 453 PrintService.throwIfNotCalledOnMainThread(); 454 if (mIsDestroyed) { 455 return Collections.emptyList(); 456 } 457 return new ArrayList<PrinterId>(mTrackedPrinters); 458 } 459 460 /** 461 * Notifies you that the session is destroyed. After this callback is invoked 462 * any calls to the methods of this class will be ignored, {@link #isDestroyed()} 463 * will return true and you will also no longer receive callbacks. 464 * 465 * @see #isDestroyed() 466 */ 467 public abstract void onDestroy(); 468 469 /** 470 * Gets whether the session is destroyed. 471 * 472 * @return Whether the session is destroyed. 473 * 474 * @see #onDestroy() 475 */ 476 public final boolean isDestroyed() { 477 PrintService.throwIfNotCalledOnMainThread(); 478 return mIsDestroyed; 479 } 480 481 /** 482 * Gets whether printer discovery is started. 483 * 484 * @return Whether printer discovery is destroyed. 485 * 486 * @see #onStartPrinterDiscovery(List) 487 * @see #onStopPrinterDiscovery() 488 */ 489 public final boolean isPrinterDiscoveryStarted() { 490 PrintService.throwIfNotCalledOnMainThread(); 491 return mIsDiscoveryStarted; 492 } 493 494 void startPrinterDiscovery(List<PrinterId> priorityList) { 495 if (!mIsDestroyed) { 496 mIsDiscoveryStarted = true; 497 sendOutOfDiscoveryPeriodPrinterChanges(); 498 if (priorityList == null) { 499 priorityList = Collections.emptyList(); 500 } 501 onStartPrinterDiscovery(priorityList); 502 } 503 } 504 505 void stopPrinterDiscovery() { 506 if (!mIsDestroyed) { 507 mIsDiscoveryStarted = false; 508 onStopPrinterDiscovery(); 509 } 510 } 511 512 void validatePrinters(List<PrinterId> printerIds) { 513 if (!mIsDestroyed && mObserver != null) { 514 onValidatePrinters(printerIds); 515 } 516 } 517 518 void startPrinterStateTracking(PrinterId printerId) { 519 if (!mIsDestroyed && mObserver != null 520 && !mTrackedPrinters.contains(printerId)) { 521 mTrackedPrinters.add(printerId); 522 onStartPrinterStateTracking(printerId); 523 } 524 } 525 526 /** 527 * Request the custom icon for a printer. 528 * 529 * @param printerId The printer to icon belongs to. 530 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() 531 */ 532 void requestCustomPrinterIcon(PrinterId printerId) { 533 if (!mIsDestroyed && mObserver != null) { 534 CustomPrinterIconCallback callback = new CustomPrinterIconCallback(printerId, 535 mObserver); 536 onRequestCustomPrinterIcon(printerId, callback); 537 } 538 } 539 540 void stopPrinterStateTracking(PrinterId printerId) { 541 if (!mIsDestroyed && mObserver != null 542 && mTrackedPrinters.remove(printerId)) { 543 onStopPrinterStateTracking(printerId); 544 } 545 } 546 547 void destroy() { 548 if (!mIsDestroyed) { 549 mIsDestroyed = true; 550 mIsDiscoveryStarted = false; 551 mPrinters.clear(); 552 mLastSentPrinters = null; 553 mObserver = null; 554 onDestroy(); 555 } 556 } 557} 558