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