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