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