PrinterDiscoverySession.java revision 773f54de3de9bce7b6f915aa47ed686b161d77aa
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#addPrinters(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 = 50; 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 sendAddedPrinters(mObserver, getPrinters()); 116 } 117 } 118 119 int getId() { 120 return mId; 121 } 122 123 /** 124 * Gets the printers reported in this session. For example, if you add two 125 * printers and remove one of them, the returned list will contain only 126 * the printer that was added but not removed. 127 * <p> 128 * <strong>Note: </strong> Calls to this method after the session is 129 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 130 * </p> 131 * 132 * @return The printers. 133 * 134 * @see #addPrinters(List) 135 * @see #removePrinters(List) 136 * @see #isDestroyed() 137 */ 138 public final List<PrinterInfo> getPrinters() { 139 PrintService.throwIfNotCalledOnMainThread(); 140 if (mIsDestroyed) { 141 return Collections.emptyList(); 142 } 143 return new ArrayList<PrinterInfo>(mPrinters.values()); 144 } 145 146 /** 147 * Adds discovered printers. Adding an already added printer updates it. 148 * Removed printers can be added again. You can call this method multiple 149 * times during the life of this session. Duplicates will be ignored. 150 * <p> 151 * <strong>Note: </strong> Calls to this method after the session is 152 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 153 * </p> 154 * 155 * @param printers The printers to add. 156 * 157 * @see #removePrinters(List) 158 * @see #getPrinters() 159 * @see #isDestroyed() 160 */ 161 public final void addPrinters(List<PrinterInfo> printers) { 162 PrintService.throwIfNotCalledOnMainThread(); 163 164 // If the session is destroyed - nothing do to. 165 if (mIsDestroyed) { 166 Log.w(LOG_TAG, "Not adding printers - session destroyed."); 167 return; 168 } 169 170 if (mIsDiscoveryStarted) { 171 // If during discovery, add the new printers and send them. 172 List<PrinterInfo> addedPrinters = null; 173 final int addedPrinterCount = printers.size(); 174 for (int i = 0; i < addedPrinterCount; i++) { 175 PrinterInfo addedPrinter = printers.get(i); 176 PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter); 177 if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) { 178 if (addedPrinters == null) { 179 addedPrinters = new ArrayList<PrinterInfo>(); 180 } 181 addedPrinters.add(addedPrinter); 182 } 183 } 184 185 // Send the added printers, if such. 186 if (addedPrinters != null) { 187 sendAddedPrinters(mObserver, addedPrinters); 188 } 189 } else { 190 // Remember the last sent printers if needed. 191 if (mLastSentPrinters == null) { 192 mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters); 193 } 194 195 // Update the printers. 196 final int addedPrinterCount = printers.size(); 197 for (int i = 0; i < addedPrinterCount; i++) { 198 PrinterInfo addedPrinter = printers.get(i); 199 if (mPrinters.get(addedPrinter.getId()) == null) { 200 mPrinters.put(addedPrinter.getId(), addedPrinter); 201 } 202 } 203 } 204 } 205 206 private static void sendAddedPrinters(IPrintServiceClient observer, 207 List<PrinterInfo> printers) { 208 try { 209 final int printerCount = printers.size(); 210 if (printerCount <= MAX_ITEMS_PER_CALLBACK) { 211 observer.onPrintersAdded(printers); 212 } else { 213 // Send the added printers in chunks avoiding the binder transaction limit. 214 final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1; 215 for (int i = 0; i < transactionCount; i++) { 216 final int start = i * MAX_ITEMS_PER_CALLBACK; 217 final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount); 218 List<PrinterInfo> subPrinters = printers.subList(start, end); 219 observer.onPrintersAdded(subPrinters); 220 } 221 } 222 } catch (RemoteException re) { 223 Log.e(LOG_TAG, "Error sending added printers", re); 224 } 225 } 226 227 /** 228 * Removes added printers. Removing an already removed or never added 229 * printer has no effect. Removed printers can be added again. You can 230 * call this method multiple times during the lifetime of this session. 231 * <p> 232 * <strong>Note: </strong> Calls to this method after the session is 233 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 234 * </p> 235 * 236 * @param printerIds The ids of the removed printers. 237 * 238 * @see #addPrinters(List) 239 * @see #getPrinters() 240 * @see #isDestroyed() 241 */ 242 public final void removePrinters(List<PrinterId> printerIds) { 243 PrintService.throwIfNotCalledOnMainThread(); 244 245 // If the session is destroyed - nothing do to. 246 if (mIsDestroyed) { 247 Log.w(LOG_TAG, "Not removing printers - session destroyed."); 248 return; 249 } 250 251 if (mIsDiscoveryStarted) { 252 // If during discovery, remove existing printers and send them. 253 List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>(); 254 final int removedPrinterIdCount = printerIds.size(); 255 for (int i = 0; i < removedPrinterIdCount; i++) { 256 PrinterId removedPrinterId = printerIds.get(i); 257 if (mPrinters.remove(removedPrinterId) != null) { 258 removedPrinterIds.add(removedPrinterId); 259 } 260 } 261 262 // Send the removed printers, if such. 263 if (!removedPrinterIds.isEmpty()) { 264 sendRemovedPrinters(mObserver, removedPrinterIds); 265 } 266 } else { 267 // Remember the last sent printers if needed. 268 if (mLastSentPrinters == null) { 269 mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters); 270 } 271 272 // Update the printers. 273 final int removedPrinterIdCount = printerIds.size(); 274 for (int i = 0; i < removedPrinterIdCount; i++) { 275 PrinterId removedPrinterId = printerIds.get(i); 276 mPrinters.remove(removedPrinterId); 277 } 278 } 279 } 280 281 private static void sendRemovedPrinters(IPrintServiceClient observer, 282 List<PrinterId> printerIds) { 283 try { 284 final int printerIdCount = printerIds.size(); 285 if (printerIdCount <= MAX_ITEMS_PER_CALLBACK) { 286 observer.onPrintersRemoved(printerIds); 287 } else { 288 final int transactionCount = (printerIdCount / MAX_ITEMS_PER_CALLBACK) + 1; 289 for (int i = 0; i < transactionCount; i++) { 290 final int start = i * MAX_ITEMS_PER_CALLBACK; 291 final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerIdCount); 292 List<PrinterId> subPrinterIds = printerIds.subList(start, end); 293 observer.onPrintersRemoved(subPrinterIds); 294 } 295 } 296 } catch (RemoteException re) { 297 Log.e(LOG_TAG, "Error sending removed printers", re); 298 } 299 } 300 301 private void sendOutOfDiscoveryPeriodPrinterChanges() { 302 // Noting changed since the last discovery period - nothing to do. 303 if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) { 304 mLastSentPrinters = null; 305 return; 306 } 307 308 // Determine the added printers. 309 List<PrinterInfo> addedPrinters = null; 310 for (PrinterInfo printer : mPrinters.values()) { 311 PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId()); 312 if (sentPrinter == null || !sentPrinter.equals(printer)) { 313 if (addedPrinters == null) { 314 addedPrinters = new ArrayList<PrinterInfo>(); 315 } 316 addedPrinters.add(printer); 317 } 318 } 319 320 // Send the added printers, if such. 321 if (addedPrinters != null) { 322 sendAddedPrinters(mObserver, addedPrinters); 323 } 324 325 // Determine the removed printers. 326 List<PrinterId> removedPrinterIds = null; 327 for (PrinterInfo sentPrinter : mLastSentPrinters.values()) { 328 if (!mPrinters.containsKey(sentPrinter.getId())) { 329 if (removedPrinterIds == null) { 330 removedPrinterIds = new ArrayList<PrinterId>(); 331 } 332 removedPrinterIds.add(sentPrinter.getId()); 333 } 334 } 335 336 // Send the removed printers, if such. 337 if (removedPrinterIds != null) { 338 sendRemovedPrinters(mObserver, removedPrinterIds); 339 } 340 341 mLastSentPrinters = null; 342 } 343 344 /** 345 * Callback asking you to start printer discovery. Discovered printers should be 346 * added via calling {@link #addPrinters(List)}. Added printers that disappeared 347 * should be removed via calling {@link #removePrinters(List)}. Added printers 348 * whose properties or capabilities changed should be updated via calling {@link 349 * #addPrinters(List)}. You will receive a call to {@link #onStopPrinterDiscovery()} 350 * when you should stop printer discovery. 351 * <p> 352 * During the lifetime of this session all printers that are known to your print 353 * service have to be added. The system does not retain any printers across sessions. 354 * However, if you were asked to start and then stop performing printer discovery 355 * in this session, then a subsequent discovering should not re-discover already 356 * discovered printers. You can get the printers reported during this session by 357 * calling {@link #getPrinters()}. 358 * </p> 359 * <p> 360 * <strong>Note: </strong>You are also given a list of printers whose availability 361 * has to be checked first. For example, these printers could be the user's favorite 362 * ones, therefore they have to be verified first. You do <strong>not need</strong> 363 * to provide the capabilities of the printers, rather verify whether they exist 364 * similarly to {@link #onValidatePrinters(List)}. 365 * </p> 366 * 367 * @param priorityList The list of printers to validate first. Never null. 368 * 369 * @see #onStopPrinterDiscovery() 370 * @see #addPrinters(List) 371 * @see #removePrinters(List) 372 * @see #isPrinterDiscoveryStarted() 373 */ 374 public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList); 375 376 /** 377 * Callback notifying you that you should stop printer discovery. 378 * 379 * @see #onStartPrinterDiscovery(List) 380 * @see #isPrinterDiscoveryStarted() 381 */ 382 public abstract void onStopPrinterDiscovery(); 383 384 /** 385 * Callback asking you to validate that the given printers are valid, that 386 * is they exist. You are responsible for checking whether these printers 387 * exist and for the ones that do exist notify the system via calling 388 * {@link #addPrinters(List)}. 389 * <p> 390 * <strong>Note: </strong> You are <strong>not required</strong> to provide 391 * the printer capabilities when updating the printers that do exist. 392 * <p> 393 * 394 * @param printerIds The printers to validate. 395 * 396 * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) 397 * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) 398 */ 399 public abstract void onValidatePrinters(List<PrinterId> printerIds); 400 401 /** 402 * Callback asking you to start tracking the state of a printer. Tracking 403 * the state means that you should do a best effort to observe the state 404 * of this printer and notify the system if that state changes via calling 405 * {@link #addPrinters(List)}. 406 * <p> 407 * <strong>Note: </strong> A printer can be initially added without its 408 * capabilities to avoid polling printers that the user will not select. 409 * However, after this method is called you are expected to update the 410 * printer <strong>including</strong> its capabilities. Otherwise, the 411 * printer will be ignored. 412 * <p> 413 * <p> 414 * A scenario when you may be requested to track a printer's state is if 415 * the user selects that printer and the system has to present print 416 * options UI based on the printer's capabilities. In this case the user 417 * should be promptly informed if, for example, the printer becomes 418 * unavailable. 419 * </p> 420 * 421 * @param printerId The printer to start tracking. 422 * 423 * @see #onStopPrinterStateTracking(PrinterId) 424 * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) 425 * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) 426 */ 427 public abstract void onStartPrinterStateTracking(PrinterId printerId); 428 429 /** 430 * Callback asking you to stop tracking the state of a printer. The passed 431 * in printer id is the one for which you received a call to {@link 432 * #onStartPrinterStateTracking(PrinterId)}. 433 * 434 * @param printerId The printer to stop tracking. 435 * 436 * @see #onStartPrinterStateTracking(PrinterId) 437 */ 438 public abstract void onStopPrinterStateTracking(PrinterId printerId); 439 440 /** 441 * Gets the printers that should be tracked. These are printers that are 442 * important to the user and for which you received a call to {@link 443 * #onStartPrinterStateTracking(PrinterId)} asking you to observer their 444 * state and reporting it to the system via {@link #addPrinters(List)}. 445 * You will receive a call to {@link #onStopPrinterStateTracking(PrinterId)} 446 * if you should stop tracking a printer. 447 * <p> 448 * <strong>Note: </strong> Calls to this method after the session is 449 * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. 450 * </p> 451 * 452 * @return The printers. 453 * 454 * @see #onStartPrinterStateTracking(PrinterId) 455 * @see #onStopPrinterStateTracking(PrinterId) 456 * @see #isDestroyed() 457 */ 458 public final List<PrinterId> getTrackedPrinters() { 459 PrintService.throwIfNotCalledOnMainThread(); 460 if (mIsDestroyed) { 461 return Collections.emptyList(); 462 } 463 return new ArrayList<PrinterId>(mTrackedPrinters); 464 } 465 466 /** 467 * Notifies you that the session is destroyed. After this callback is invoked 468 * any calls to the methods of this class will be ignored, {@link #isDestroyed()} 469 * will return true and you will also no longer receive callbacks. 470 * 471 * @see #isDestroyed() 472 */ 473 public abstract void onDestroy(); 474 475 /** 476 * Gets whether the session is destroyed. 477 * 478 * @return Whether the session is destroyed. 479 * 480 * @see #onDestroy() 481 */ 482 public final boolean isDestroyed() { 483 PrintService.throwIfNotCalledOnMainThread(); 484 return mIsDestroyed; 485 } 486 487 /** 488 * Gets whether printer discovery is started. 489 * 490 * @return Whether printer discovery is destroyed. 491 * 492 * @see #onStartPrinterDiscovery(List) 493 * @see #onStopPrinterDiscovery() 494 */ 495 public final boolean isPrinterDiscoveryStarted() { 496 PrintService.throwIfNotCalledOnMainThread(); 497 return mIsDiscoveryStarted; 498 } 499 500 void startPrinterDiscovery(List<PrinterId> priorityList) { 501 if (!mIsDestroyed) { 502 mIsDiscoveryStarted = true; 503 sendOutOfDiscoveryPeriodPrinterChanges(); 504 if (priorityList == null) { 505 priorityList = Collections.emptyList(); 506 } 507 onStartPrinterDiscovery(priorityList); 508 } 509 } 510 511 void stopPrinterDiscovery() { 512 if (!mIsDestroyed) { 513 mIsDiscoveryStarted = false; 514 onStopPrinterDiscovery(); 515 } 516 } 517 518 void validatePrinters(List<PrinterId> printerIds) { 519 if (!mIsDestroyed && mObserver != null) { 520 onValidatePrinters(printerIds); 521 } 522 } 523 524 void startPrinterStateTracking(PrinterId printerId) { 525 if (!mIsDestroyed && mObserver != null 526 && !mTrackedPrinters.contains(printerId)) { 527 mTrackedPrinters.add(printerId); 528 onStartPrinterStateTracking(printerId); 529 } 530 } 531 532 void stopPrinterStateTracking(PrinterId printerId) { 533 if (!mIsDestroyed && mObserver != null 534 && mTrackedPrinters.remove(printerId)) { 535 onStopPrinterStateTracking(printerId); 536 } 537 } 538 539 void destroy() { 540 if (!mIsDestroyed) { 541 mIsDestroyed = true; 542 mIsDiscoveryStarted = false; 543 mPrinters.clear(); 544 mLastSentPrinters = null; 545 mObserver = null; 546 onDestroy(); 547 } 548 } 549} 550