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.print; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Activity; 22 import android.app.Application.ActivityLifecycleCallbacks; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.IntentSender; 26 import android.content.IntentSender.SendIntentException; 27 import android.graphics.drawable.Icon; 28 import android.os.Bundle; 29 import android.os.CancellationSignal; 30 import android.os.Handler; 31 import android.os.ICancellationSignal; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.ParcelFileDescriptor; 35 import android.os.RemoteException; 36 import android.print.PrintDocumentAdapter.LayoutResultCallback; 37 import android.print.PrintDocumentAdapter.WriteResultCallback; 38 import android.printservice.PrintServiceInfo; 39 import android.printservice.recommendation.IRecommendationsChangeListener; 40 import android.printservice.recommendation.RecommendationInfo; 41 import android.text.TextUtils; 42 import android.util.ArrayMap; 43 import android.util.Log; 44 45 import com.android.internal.os.SomeArgs; 46 47 import com.android.internal.util.Preconditions; 48 import libcore.io.IoUtils; 49 50 import java.lang.ref.WeakReference; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.Collections; 54 import java.util.List; 55 import java.util.Map; 56 57 /** 58 * System level service for accessing the printing capabilities of the platform. 59 * <p> 60 * To obtain a handle to the print manager do the following: 61 * </p> 62 * 63 * <pre> 64 * PrintManager printManager = 65 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); 66 * </pre> 67 * 68 * <h3>Print mechanics</h3> 69 * <p> 70 * The key idea behind printing on the platform is that the content to be printed 71 * should be laid out for the currently selected print options resulting in an 72 * optimized output and higher user satisfaction. To achieve this goal the platform 73 * declares a contract that the printing application has to follow which is defined 74 * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that 75 * when the user selects some options from the print UI that may affect the way 76 * content is laid out, for example page size, the application receives a callback 77 * allowing it to layout the content to better fit these new constraints. After a 78 * layout pass the system may ask the application to render one or more pages one 79 * or more times. For example, an application may produce a single column list for 80 * smaller page sizes and a multi-column table for larger page sizes. 81 * </p> 82 * <h3>Print jobs</h3> 83 * <p> 84 * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter, 85 * PrintAttributes)} from an activity which results in bringing up the system print 86 * UI. Once the print UI is up, when the user changes a selected print option that 87 * affects the way content is laid out the system starts to interact with the 88 * application following the mechanics described the section above. 89 * </p> 90 * <p> 91 * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link 92 * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started}, 93 * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED 94 * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link 95 * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated 96 * system spooler until they are handled which is they are cancelled or completed. 97 * Active print jobs, ones that are not cancelled or completed, are considered failed 98 * if the device reboots as the new boot may be after a very long time. The user may 99 * choose to restart such print jobs. Once a print job is queued all relevant content 100 * is stored in the system spooler and its lifecycle becomes detached from this of 101 * the application that created it. 102 * </p> 103 * <p> 104 * An applications can query the print spooler for current print jobs it created 105 * but not print jobs created by other applications. 106 * </p> 107 * 108 * @see PrintJob 109 * @see PrintJobInfo 110 */ 111 public final class PrintManager { 112 113 private static final String LOG_TAG = "PrintManager"; 114 115 private static final boolean DEBUG = false; 116 117 private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1; 118 private static final int MSG_NOTIFY_PRINT_SERVICES_CHANGED = 2; 119 private static final int MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED = 3; 120 121 /** 122 * Package name of print spooler. 123 * 124 * @hide 125 */ 126 public static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler"; 127 128 /** 129 * Select enabled services. 130 * </p> 131 * @see #getPrintServices 132 * @hide 133 */ 134 public static final int ENABLED_SERVICES = 1 << 0; 135 136 /** 137 * Select disabled services. 138 * </p> 139 * @see #getPrintServices 140 * @hide 141 */ 142 public static final int DISABLED_SERVICES = 1 << 1; 143 144 /** 145 * Select all services. 146 * </p> 147 * @see #getPrintServices 148 * @hide 149 */ 150 public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES; 151 152 /** 153 * The action for launching the print dialog activity. 154 * 155 * @hide 156 */ 157 public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG"; 158 159 /** 160 * Extra with the intent for starting the print dialog. 161 * <p> 162 * <strong>Type:</strong> {@link android.content.IntentSender} 163 * </p> 164 * 165 * @hide 166 */ 167 public static final String EXTRA_PRINT_DIALOG_INTENT = 168 "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT"; 169 170 /** 171 * Extra with a print job. 172 * <p> 173 * <strong>Type:</strong> {@link android.print.PrintJobInfo} 174 * </p> 175 * 176 * @hide 177 */ 178 public static final String EXTRA_PRINT_JOB = 179 "android.print.intent.extra.EXTRA_PRINT_JOB"; 180 181 /** 182 * Extra with the print document adapter to be printed. 183 * <p> 184 * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter} 185 * </p> 186 * 187 * @hide 188 */ 189 public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = 190 "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER"; 191 192 /** @hide */ 193 public static final int APP_ID_ANY = -2; 194 195 private final Context mContext; 196 197 private final IPrintManager mService; 198 199 private final int mUserId; 200 201 private final int mAppId; 202 203 private final Handler mHandler; 204 205 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> 206 mPrintJobStateChangeListeners; 207 private Map<PrintServicesChangeListener, PrintServicesChangeListenerWrapper> 208 mPrintServicesChangeListeners; 209 private Map<PrintServiceRecommendationsChangeListener, 210 PrintServiceRecommendationsChangeListenerWrapper> 211 mPrintServiceRecommendationsChangeListeners; 212 213 /** @hide */ 214 public interface PrintJobStateChangeListener { 215 216 /** 217 * Callback notifying that a print job state changed. 218 * 219 * @param printJobId The print job id. 220 */ onPrintJobStateChanged(PrintJobId printJobId)221 public void onPrintJobStateChanged(PrintJobId printJobId); 222 } 223 224 /** @hide */ 225 public interface PrintServicesChangeListener { 226 227 /** 228 * Callback notifying that the print services changed. 229 */ onPrintServicesChanged()230 public void onPrintServicesChanged(); 231 } 232 233 /** @hide */ 234 public interface PrintServiceRecommendationsChangeListener { 235 236 /** 237 * Callback notifying that the print service recommendations changed. 238 */ onPrintServiceRecommendationsChanged()239 void onPrintServiceRecommendationsChanged(); 240 } 241 242 /** 243 * Creates a new instance. 244 * 245 * @param context The current context in which to operate. 246 * @param service The backing system service. 247 * @param userId The user id in which to operate. 248 * @param appId The application id in which to operate. 249 * @hide 250 */ PrintManager(Context context, IPrintManager service, int userId, int appId)251 public PrintManager(Context context, IPrintManager service, int userId, int appId) { 252 mContext = context; 253 mService = service; 254 mUserId = userId; 255 mAppId = appId; 256 mHandler = new Handler(context.getMainLooper(), null, false) { 257 @Override 258 public void handleMessage(Message message) { 259 switch (message.what) { 260 case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: { 261 SomeArgs args = (SomeArgs) message.obj; 262 PrintJobStateChangeListenerWrapper wrapper = 263 (PrintJobStateChangeListenerWrapper) args.arg1; 264 PrintJobStateChangeListener listener = wrapper.getListener(); 265 if (listener != null) { 266 PrintJobId printJobId = (PrintJobId) args.arg2; 267 listener.onPrintJobStateChanged(printJobId); 268 } 269 args.recycle(); 270 } break; 271 case MSG_NOTIFY_PRINT_SERVICES_CHANGED: { 272 PrintServicesChangeListenerWrapper wrapper = 273 (PrintServicesChangeListenerWrapper) message.obj; 274 PrintServicesChangeListener listener = wrapper.getListener(); 275 if (listener != null) { 276 listener.onPrintServicesChanged(); 277 } 278 } break; 279 case MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED: { 280 PrintServiceRecommendationsChangeListenerWrapper wrapper = 281 (PrintServiceRecommendationsChangeListenerWrapper) message.obj; 282 PrintServiceRecommendationsChangeListener listener = wrapper.getListener(); 283 if (listener != null) { 284 listener.onPrintServiceRecommendationsChanged(); 285 } 286 } break; 287 } 288 } 289 }; 290 } 291 292 /** 293 * Creates an instance that can access all print jobs. 294 * 295 * @param userId The user id for which to get all print jobs. 296 * @return An instance if the caller has the permission to access all print 297 * jobs, null otherwise. 298 * @hide 299 */ getGlobalPrintManagerForUser(int userId)300 public PrintManager getGlobalPrintManagerForUser(int userId) { 301 if (mService == null) { 302 Log.w(LOG_TAG, "Feature android.software.print not available"); 303 return null; 304 } 305 return new PrintManager(mContext, mService, userId, APP_ID_ANY); 306 } 307 getPrintJobInfo(PrintJobId printJobId)308 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { 309 try { 310 return mService.getPrintJobInfo(printJobId, mAppId, mUserId); 311 } catch (RemoteException re) { 312 throw re.rethrowFromSystemServer(); 313 } 314 } 315 316 /** 317 * Adds a listener for observing the state of print jobs. 318 * 319 * @param listener The listener to add. 320 * @hide 321 */ addPrintJobStateChangeListener(PrintJobStateChangeListener listener)322 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) { 323 if (mService == null) { 324 Log.w(LOG_TAG, "Feature android.software.print not available"); 325 return; 326 } 327 if (mPrintJobStateChangeListeners == null) { 328 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener, 329 PrintJobStateChangeListenerWrapper>(); 330 } 331 PrintJobStateChangeListenerWrapper wrappedListener = 332 new PrintJobStateChangeListenerWrapper(listener, mHandler); 333 try { 334 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId); 335 mPrintJobStateChangeListeners.put(listener, wrappedListener); 336 } catch (RemoteException re) { 337 throw re.rethrowFromSystemServer(); 338 } 339 } 340 341 /** 342 * Removes a listener for observing the state of print jobs. 343 * 344 * @param listener The listener to remove. 345 * @hide 346 */ removePrintJobStateChangeListener(PrintJobStateChangeListener listener)347 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) { 348 if (mService == null) { 349 Log.w(LOG_TAG, "Feature android.software.print not available"); 350 return; 351 } 352 if (mPrintJobStateChangeListeners == null) { 353 return; 354 } 355 PrintJobStateChangeListenerWrapper wrappedListener = 356 mPrintJobStateChangeListeners.remove(listener); 357 if (wrappedListener == null) { 358 return; 359 } 360 if (mPrintJobStateChangeListeners.isEmpty()) { 361 mPrintJobStateChangeListeners = null; 362 } 363 wrappedListener.destroy(); 364 try { 365 mService.removePrintJobStateChangeListener(wrappedListener, mUserId); 366 } catch (RemoteException re) { 367 throw re.rethrowFromSystemServer(); 368 } 369 } 370 371 /** 372 * Gets a print job given its id. 373 * 374 * @param printJobId The id of the print job. 375 * @return The print job list. 376 * @see PrintJob 377 * @hide 378 */ getPrintJob(PrintJobId printJobId)379 public PrintJob getPrintJob(PrintJobId printJobId) { 380 if (mService == null) { 381 Log.w(LOG_TAG, "Feature android.software.print not available"); 382 return null; 383 } 384 try { 385 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId); 386 if (printJob != null) { 387 return new PrintJob(printJob, this); 388 } 389 } catch (RemoteException re) { 390 throw re.rethrowFromSystemServer(); 391 } 392 return null; 393 } 394 395 /** 396 * Get the custom icon for a printer. If the icon is not cached, the icon is 397 * requested asynchronously. Once it is available the printer is updated. 398 * 399 * @param printerId the id of the printer the icon should be loaded for 400 * @return the custom icon to be used for the printer or null if the icon is 401 * not yet available 402 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() 403 * @hide 404 */ getCustomPrinterIcon(PrinterId printerId)405 public Icon getCustomPrinterIcon(PrinterId printerId) { 406 if (mService == null) { 407 Log.w(LOG_TAG, "Feature android.software.print not available"); 408 return null; 409 } 410 try { 411 return mService.getCustomPrinterIcon(printerId, mUserId); 412 } catch (RemoteException re) { 413 throw re.rethrowFromSystemServer(); 414 } 415 } 416 417 /** 418 * Gets the print jobs for this application. 419 * 420 * @return The print job list. 421 * @see PrintJob 422 */ getPrintJobs()423 public @NonNull List<PrintJob> getPrintJobs() { 424 if (mService == null) { 425 Log.w(LOG_TAG, "Feature android.software.print not available"); 426 return Collections.emptyList(); 427 } 428 try { 429 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); 430 if (printJobInfos == null) { 431 return Collections.emptyList(); 432 } 433 final int printJobCount = printJobInfos.size(); 434 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); 435 for (int i = 0; i < printJobCount; i++) { 436 printJobs.add(new PrintJob(printJobInfos.get(i), this)); 437 } 438 return printJobs; 439 } catch (RemoteException re) { 440 throw re.rethrowFromSystemServer(); 441 } 442 } 443 cancelPrintJob(PrintJobId printJobId)444 void cancelPrintJob(PrintJobId printJobId) { 445 if (mService == null) { 446 Log.w(LOG_TAG, "Feature android.software.print not available"); 447 return; 448 } 449 try { 450 mService.cancelPrintJob(printJobId, mAppId, mUserId); 451 } catch (RemoteException re) { 452 throw re.rethrowFromSystemServer(); 453 } 454 } 455 restartPrintJob(PrintJobId printJobId)456 void restartPrintJob(PrintJobId printJobId) { 457 if (mService == null) { 458 Log.w(LOG_TAG, "Feature android.software.print not available"); 459 return; 460 } 461 try { 462 mService.restartPrintJob(printJobId, mAppId, mUserId); 463 } catch (RemoteException re) { 464 throw re.rethrowFromSystemServer(); 465 } 466 } 467 468 /** 469 * Creates a print job for printing a {@link PrintDocumentAdapter} with 470 * default print attributes. 471 * <p> 472 * Calling this method brings the print UI allowing the user to customize 473 * the print job and returns a {@link PrintJob} object without waiting for the 474 * user to customize or confirm the print job. The returned print job instance 475 * is in a {@link PrintJobInfo#STATE_CREATED created} state. 476 * <p> 477 * This method can be called only from an {@link Activity}. The rationale is that 478 * printing from a service will create an inconsistent user experience as the print 479 * UI would appear without any context. 480 * </p> 481 * <p> 482 * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if 483 * your activity is finished. The rationale is that once the activity that 484 * initiated printing is finished, the provided adapter may be in an inconsistent 485 * state as it may depend on the UI presented by the activity. 486 * </p> 487 * <p> 488 * The default print attributes are a hint to the system how the data is to 489 * be printed. For example, a photo editor may look at the photo aspect ratio 490 * to determine the default orientation and provide a hint whether the printing 491 * should be in portrait or landscape. The system will do a best effort to 492 * selected the hinted options in the print dialog, given the current printer 493 * supports them. 494 * </p> 495 * <p> 496 * <strong>Note:</strong> Calling this method will bring the print dialog and 497 * the system will connect to the provided {@link PrintDocumentAdapter}. If a 498 * configuration change occurs that you application does not handle, for example 499 * a rotation change, the system will drop the connection to the adapter as the 500 * activity has to be recreated and the old adapter may be invalid in this context, 501 * hence a new adapter instance is required. As a consequence, if your activity 502 * does not handle configuration changes (default behavior), you have to save the 503 * state that you were printing and call this method again when your activity 504 * is recreated. 505 * </p> 506 * 507 * @param printJobName A name for the new print job which is shown to the user. 508 * @param documentAdapter An adapter that emits the document to print. 509 * @param attributes The default print job attributes or <code>null</code>. 510 * @return The created print job on success or null on failure. 511 * @throws IllegalStateException If not called from an {@link Activity}. 512 * @throws IllegalArgumentException If the print job name is empty or the 513 * document adapter is null. 514 * 515 * @see PrintJob 516 */ print(@onNull String printJobName, @NonNull PrintDocumentAdapter documentAdapter, @Nullable PrintAttributes attributes)517 public @NonNull PrintJob print(@NonNull String printJobName, 518 @NonNull PrintDocumentAdapter documentAdapter, 519 @Nullable PrintAttributes attributes) { 520 if (mService == null) { 521 Log.w(LOG_TAG, "Feature android.software.print not available"); 522 return null; 523 } 524 if (!(mContext instanceof Activity)) { 525 throw new IllegalStateException("Can print only from an activity"); 526 } 527 if (TextUtils.isEmpty(printJobName)) { 528 throw new IllegalArgumentException("printJobName cannot be empty"); 529 } 530 if (documentAdapter == null) { 531 throw new IllegalArgumentException("documentAdapter cannot be null"); 532 } 533 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate( 534 (Activity) mContext, documentAdapter); 535 try { 536 Bundle result = mService.print(printJobName, delegate, 537 attributes, mContext.getPackageName(), mAppId, mUserId); 538 if (result != null) { 539 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB); 540 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT); 541 if (printJob == null || intent == null) { 542 return null; 543 } 544 try { 545 mContext.startIntentSender(intent, null, 0, 0, 0); 546 return new PrintJob(printJob, this); 547 } catch (SendIntentException sie) { 548 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); 549 } 550 } 551 } catch (RemoteException re) { 552 throw re.rethrowFromSystemServer(); 553 } 554 return null; 555 } 556 557 /** 558 * Listen for changes to the installed and enabled print services. 559 * 560 * @param listener the listener to add 561 * 562 * @see android.print.PrintManager#getPrintServices 563 */ addPrintServicesChangeListener(@onNull PrintServicesChangeListener listener)564 void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { 565 Preconditions.checkNotNull(listener); 566 567 if (mService == null) { 568 Log.w(LOG_TAG, "Feature android.software.print not available"); 569 return; 570 } 571 if (mPrintServicesChangeListeners == null) { 572 mPrintServicesChangeListeners = new ArrayMap<>(); 573 } 574 PrintServicesChangeListenerWrapper wrappedListener = 575 new PrintServicesChangeListenerWrapper(listener, mHandler); 576 try { 577 mService.addPrintServicesChangeListener(wrappedListener, mUserId); 578 mPrintServicesChangeListeners.put(listener, wrappedListener); 579 } catch (RemoteException re) { 580 throw re.rethrowFromSystemServer(); 581 } 582 } 583 584 /** 585 * Stop listening for changes to the installed and enabled print services. 586 * 587 * @param listener the listener to remove 588 * 589 * @see android.print.PrintManager#getPrintServices 590 */ removePrintServicesChangeListener(@onNull PrintServicesChangeListener listener)591 void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { 592 Preconditions.checkNotNull(listener); 593 594 if (mService == null) { 595 Log.w(LOG_TAG, "Feature android.software.print not available"); 596 return; 597 } 598 if (mPrintServicesChangeListeners == null) { 599 return; 600 } 601 PrintServicesChangeListenerWrapper wrappedListener = 602 mPrintServicesChangeListeners.remove(listener); 603 if (wrappedListener == null) { 604 return; 605 } 606 if (mPrintServicesChangeListeners.isEmpty()) { 607 mPrintServicesChangeListeners = null; 608 } 609 wrappedListener.destroy(); 610 try { 611 mService.removePrintServicesChangeListener(wrappedListener, mUserId); 612 } catch (RemoteException re) { 613 Log.e(LOG_TAG, "Error removing print services change listener", re); 614 } 615 } 616 617 /** 618 * Gets the list of print services, but does not register for updates. The user has to register 619 * for updates by itself, or use {@link PrintServicesLoader}. 620 * 621 * @param selectionFlags flags selecting which services to get. Either 622 * {@link #ENABLED_SERVICES},{@link #DISABLED_SERVICES}, or both. 623 * 624 * @return The print service list or an empty list. 625 * 626 * @see #addPrintServicesChangeListener(PrintServicesChangeListener) 627 * @see #removePrintServicesChangeListener(PrintServicesChangeListener) 628 * 629 * @hide 630 */ getPrintServices(int selectionFlags)631 public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) { 632 Preconditions.checkFlagsArgument(selectionFlags, ALL_SERVICES); 633 634 try { 635 List<PrintServiceInfo> services = mService.getPrintServices(selectionFlags, mUserId); 636 if (services != null) { 637 return services; 638 } 639 } catch (RemoteException re) { 640 throw re.rethrowFromSystemServer(); 641 } 642 return Collections.emptyList(); 643 } 644 645 /** 646 * Listen for changes to the print service recommendations. 647 * 648 * @param listener the listener to add 649 * 650 * @see android.print.PrintManager#getPrintServiceRecommendations 651 */ addPrintServiceRecommendationsChangeListener( @onNull PrintServiceRecommendationsChangeListener listener)652 void addPrintServiceRecommendationsChangeListener( 653 @NonNull PrintServiceRecommendationsChangeListener listener) { 654 Preconditions.checkNotNull(listener); 655 656 if (mService == null) { 657 Log.w(LOG_TAG, "Feature android.software.print not available"); 658 return; 659 } 660 if (mPrintServiceRecommendationsChangeListeners == null) { 661 mPrintServiceRecommendationsChangeListeners = new ArrayMap<>(); 662 } 663 PrintServiceRecommendationsChangeListenerWrapper wrappedListener = 664 new PrintServiceRecommendationsChangeListenerWrapper(listener, mHandler); 665 try { 666 mService.addPrintServiceRecommendationsChangeListener(wrappedListener, mUserId); 667 mPrintServiceRecommendationsChangeListeners.put(listener, wrappedListener); 668 } catch (RemoteException re) { 669 throw re.rethrowFromSystemServer(); 670 } 671 } 672 673 /** 674 * Stop listening for changes to the print service recommendations. 675 * 676 * @param listener the listener to remove 677 * 678 * @see android.print.PrintManager#getPrintServiceRecommendations 679 */ removePrintServiceRecommendationsChangeListener( @onNull PrintServiceRecommendationsChangeListener listener)680 void removePrintServiceRecommendationsChangeListener( 681 @NonNull PrintServiceRecommendationsChangeListener listener) { 682 Preconditions.checkNotNull(listener); 683 684 if (mService == null) { 685 Log.w(LOG_TAG, "Feature android.software.print not available"); 686 return; 687 } 688 if (mPrintServiceRecommendationsChangeListeners == null) { 689 return; 690 } 691 PrintServiceRecommendationsChangeListenerWrapper wrappedListener = 692 mPrintServiceRecommendationsChangeListeners.remove(listener); 693 if (wrappedListener == null) { 694 return; 695 } 696 if (mPrintServiceRecommendationsChangeListeners.isEmpty()) { 697 mPrintServiceRecommendationsChangeListeners = null; 698 } 699 wrappedListener.destroy(); 700 try { 701 mService.removePrintServiceRecommendationsChangeListener(wrappedListener, mUserId); 702 } catch (RemoteException re) { 703 throw re.rethrowFromSystemServer(); 704 } 705 } 706 707 /** 708 * Gets the list of print service recommendations, but does not register for updates. The user 709 * has to register for updates by itself, or use {@link PrintServiceRecommendationsLoader}. 710 * 711 * @return The print service recommendations list or an empty list. 712 * 713 * @see #addPrintServiceRecommendationsChangeListener 714 * @see #removePrintServiceRecommendationsChangeListener 715 * 716 * @hide 717 */ getPrintServiceRecommendations()718 public @NonNull List<RecommendationInfo> getPrintServiceRecommendations() { 719 try { 720 List<RecommendationInfo> recommendations = 721 mService.getPrintServiceRecommendations(mUserId); 722 if (recommendations != null) { 723 return recommendations; 724 } 725 } catch (RemoteException re) { 726 throw re.rethrowFromSystemServer(); 727 } 728 return Collections.emptyList(); 729 } 730 731 /** 732 * @hide 733 */ createPrinterDiscoverySession()734 public PrinterDiscoverySession createPrinterDiscoverySession() { 735 if (mService == null) { 736 Log.w(LOG_TAG, "Feature android.software.print not available"); 737 return null; 738 } 739 return new PrinterDiscoverySession(mService, mContext, mUserId); 740 } 741 742 /** 743 * Enable or disable a print service. 744 * 745 * @param service The service to enabled or disable 746 * @param isEnabled whether the service should be enabled or disabled 747 * 748 * @hide 749 */ setPrintServiceEnabled(@onNull ComponentName service, boolean isEnabled)750 public void setPrintServiceEnabled(@NonNull ComponentName service, boolean isEnabled) { 751 if (mService == null) { 752 Log.w(LOG_TAG, "Feature android.software.print not available"); 753 return; 754 } 755 try { 756 mService.setPrintServiceEnabled(service, isEnabled, mUserId); 757 } catch (RemoteException re) { 758 Log.e(LOG_TAG, "Error enabling or disabling " + service, re); 759 } 760 } 761 762 /** 763 * @hide 764 */ 765 public static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub 766 implements ActivityLifecycleCallbacks { 767 private final Object mLock = new Object(); 768 769 private Activity mActivity; // Strong reference OK - cleared in destroy 770 771 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy 772 773 private Handler mHandler; // Strong reference OK - cleared in destroy 774 775 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy 776 777 private DestroyableCallback mPendingCallback; 778 PrintDocumentAdapterDelegate(Activity activity, PrintDocumentAdapter documentAdapter)779 public PrintDocumentAdapterDelegate(Activity activity, 780 PrintDocumentAdapter documentAdapter) { 781 if (activity.isFinishing()) { 782 // The activity is already dead hence the onActivityDestroyed callback won't be 783 // triggered. Hence it is not save to print in this situation. 784 throw new IllegalStateException("Cannot start printing for finishing activity"); 785 } 786 787 mActivity = activity; 788 mDocumentAdapter = documentAdapter; 789 mHandler = new MyHandler(mActivity.getMainLooper()); 790 mActivity.getApplication().registerActivityLifecycleCallbacks(this); 791 } 792 793 @Override setObserver(IPrintDocumentAdapterObserver observer)794 public void setObserver(IPrintDocumentAdapterObserver observer) { 795 final boolean destroyed; 796 synchronized (mLock) { 797 mObserver = observer; 798 destroyed = isDestroyedLocked(); 799 } 800 801 if (destroyed && observer != null) { 802 try { 803 observer.onDestroy(); 804 } catch (RemoteException re) { 805 Log.e(LOG_TAG, "Error announcing destroyed state", re); 806 } 807 } 808 } 809 810 @Override start()811 public void start() { 812 synchronized (mLock) { 813 // If destroyed the handler is null. 814 if (!isDestroyedLocked()) { 815 mHandler.obtainMessage(MyHandler.MSG_ON_START, 816 mDocumentAdapter).sendToTarget(); 817 } 818 } 819 } 820 821 @Override layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, ILayoutResultCallback callback, Bundle metadata, int sequence)822 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, 823 ILayoutResultCallback callback, Bundle metadata, int sequence) { 824 825 ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); 826 try { 827 callback.onLayoutStarted(cancellationTransport, sequence); 828 } catch (RemoteException re) { 829 // The spooler is dead - can't recover. 830 Log.e(LOG_TAG, "Error notifying for layout start", re); 831 return; 832 } 833 834 synchronized (mLock) { 835 // If destroyed the handler is null. 836 if (isDestroyedLocked()) { 837 return; 838 } 839 840 CancellationSignal cancellationSignal = CancellationSignal.fromTransport( 841 cancellationTransport); 842 843 SomeArgs args = SomeArgs.obtain(); 844 args.arg1 = mDocumentAdapter; 845 args.arg2 = oldAttributes; 846 args.arg3 = newAttributes; 847 args.arg4 = cancellationSignal; 848 args.arg5 = new MyLayoutResultCallback(callback, sequence); 849 args.arg6 = metadata; 850 851 mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget(); 852 } 853 } 854 855 @Override write(PageRange[] pages, ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence)856 public void write(PageRange[] pages, ParcelFileDescriptor fd, 857 IWriteResultCallback callback, int sequence) { 858 859 ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); 860 try { 861 callback.onWriteStarted(cancellationTransport, sequence); 862 } catch (RemoteException re) { 863 // The spooler is dead - can't recover. 864 Log.e(LOG_TAG, "Error notifying for write start", re); 865 return; 866 } 867 868 synchronized (mLock) { 869 // If destroyed the handler is null. 870 if (isDestroyedLocked()) { 871 return; 872 } 873 874 CancellationSignal cancellationSignal = CancellationSignal.fromTransport( 875 cancellationTransport); 876 877 SomeArgs args = SomeArgs.obtain(); 878 args.arg1 = mDocumentAdapter; 879 args.arg2 = pages; 880 args.arg3 = fd; 881 args.arg4 = cancellationSignal; 882 args.arg5 = new MyWriteResultCallback(callback, fd, sequence); 883 884 mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget(); 885 } 886 } 887 888 @Override finish()889 public void finish() { 890 synchronized (mLock) { 891 // If destroyed the handler is null. 892 if (!isDestroyedLocked()) { 893 mHandler.obtainMessage(MyHandler.MSG_ON_FINISH, 894 mDocumentAdapter).sendToTarget(); 895 } 896 } 897 } 898 899 @Override kill(String reason)900 public void kill(String reason) { 901 synchronized (mLock) { 902 // If destroyed the handler is null. 903 if (!isDestroyedLocked()) { 904 mHandler.obtainMessage(MyHandler.MSG_ON_KILL, 905 reason).sendToTarget(); 906 } 907 } 908 } 909 910 @Override onActivityPaused(Activity activity)911 public void onActivityPaused(Activity activity) { 912 /* do nothing */ 913 } 914 915 @Override onActivityCreated(Activity activity, Bundle savedInstanceState)916 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 917 /* do nothing */ 918 } 919 920 @Override onActivityStarted(Activity activity)921 public void onActivityStarted(Activity activity) { 922 /* do nothing */ 923 } 924 925 @Override onActivityResumed(Activity activity)926 public void onActivityResumed(Activity activity) { 927 /* do nothing */ 928 } 929 930 @Override onActivityStopped(Activity activity)931 public void onActivityStopped(Activity activity) { 932 /* do nothing */ 933 } 934 935 @Override onActivitySaveInstanceState(Activity activity, Bundle outState)936 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 937 /* do nothing */ 938 } 939 940 @Override onActivityDestroyed(Activity activity)941 public void onActivityDestroyed(Activity activity) { 942 // We really care only if the activity is being destroyed to 943 // notify the the print spooler so it can close the print dialog. 944 // Note the the spooler has a death recipient that observes if 945 // this process gets killed so we cover the case of onDestroy not 946 // being called due to this process being killed to reclaim memory. 947 IPrintDocumentAdapterObserver observer = null; 948 synchronized (mLock) { 949 if (activity == mActivity) { 950 observer = mObserver; 951 destroyLocked(); 952 } 953 } 954 if (observer != null) { 955 try { 956 observer.onDestroy(); 957 } catch (RemoteException re) { 958 Log.e(LOG_TAG, "Error announcing destroyed state", re); 959 } 960 } 961 } 962 isDestroyedLocked()963 private boolean isDestroyedLocked() { 964 return (mActivity == null); 965 } 966 destroyLocked()967 private void destroyLocked() { 968 mActivity.getApplication().unregisterActivityLifecycleCallbacks( 969 PrintDocumentAdapterDelegate.this); 970 mActivity = null; 971 972 mDocumentAdapter = null; 973 974 // This method is only called from the main thread, so 975 // clearing the messages guarantees that any time a 976 // message is handled we are not in a destroyed state. 977 mHandler.removeMessages(MyHandler.MSG_ON_START); 978 mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT); 979 mHandler.removeMessages(MyHandler.MSG_ON_WRITE); 980 mHandler.removeMessages(MyHandler.MSG_ON_FINISH); 981 mHandler = null; 982 983 mObserver = null; 984 985 if (mPendingCallback != null) { 986 mPendingCallback.destroy(); 987 mPendingCallback = null; 988 } 989 } 990 991 private final class MyHandler extends Handler { 992 public static final int MSG_ON_START = 1; 993 public static final int MSG_ON_LAYOUT = 2; 994 public static final int MSG_ON_WRITE = 3; 995 public static final int MSG_ON_FINISH = 4; 996 public static final int MSG_ON_KILL = 5; 997 MyHandler(Looper looper)998 public MyHandler(Looper looper) { 999 super(looper, null, true); 1000 } 1001 1002 @Override handleMessage(Message message)1003 public void handleMessage(Message message) { 1004 switch (message.what) { 1005 case MSG_ON_START: { 1006 if (DEBUG) { 1007 Log.i(LOG_TAG, "onStart()"); 1008 } 1009 1010 ((PrintDocumentAdapter) message.obj).onStart(); 1011 } break; 1012 1013 case MSG_ON_LAYOUT: { 1014 SomeArgs args = (SomeArgs) message.obj; 1015 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; 1016 PrintAttributes oldAttributes = (PrintAttributes) args.arg2; 1017 PrintAttributes newAttributes = (PrintAttributes) args.arg3; 1018 CancellationSignal cancellation = (CancellationSignal) args.arg4; 1019 LayoutResultCallback callback = (LayoutResultCallback) args.arg5; 1020 Bundle metadata = (Bundle) args.arg6; 1021 args.recycle(); 1022 1023 if (DEBUG) { 1024 StringBuilder builder = new StringBuilder(); 1025 builder.append("PrintDocumentAdapter#onLayout() {\n"); 1026 builder.append("\n oldAttributes:").append(oldAttributes); 1027 builder.append("\n newAttributes:").append(newAttributes); 1028 builder.append("\n preview:").append(metadata.getBoolean( 1029 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW)); 1030 builder.append("\n}"); 1031 Log.i(LOG_TAG, builder.toString()); 1032 } 1033 1034 adapter.onLayout(oldAttributes, newAttributes, cancellation, 1035 callback, metadata); 1036 } break; 1037 1038 case MSG_ON_WRITE: { 1039 SomeArgs args = (SomeArgs) message.obj; 1040 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; 1041 PageRange[] pages = (PageRange[]) args.arg2; 1042 ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3; 1043 CancellationSignal cancellation = (CancellationSignal) args.arg4; 1044 WriteResultCallback callback = (WriteResultCallback) args.arg5; 1045 args.recycle(); 1046 1047 if (DEBUG) { 1048 StringBuilder builder = new StringBuilder(); 1049 builder.append("PrintDocumentAdapter#onWrite() {\n"); 1050 builder.append("\n pages:").append(Arrays.toString(pages)); 1051 builder.append("\n}"); 1052 Log.i(LOG_TAG, builder.toString()); 1053 } 1054 1055 adapter.onWrite(pages, fd, cancellation, callback); 1056 } break; 1057 1058 case MSG_ON_FINISH: { 1059 if (DEBUG) { 1060 Log.i(LOG_TAG, "onFinish()"); 1061 } 1062 1063 ((PrintDocumentAdapter) message.obj).onFinish(); 1064 1065 // Done printing, so destroy this instance as it 1066 // should not be used anymore. 1067 synchronized (mLock) { 1068 destroyLocked(); 1069 } 1070 } break; 1071 1072 case MSG_ON_KILL: { 1073 if (DEBUG) { 1074 Log.i(LOG_TAG, "onKill()"); 1075 } 1076 1077 String reason = (String) message.obj; 1078 throw new RuntimeException(reason); 1079 } 1080 1081 default: { 1082 throw new IllegalArgumentException("Unknown message: " 1083 + message.what); 1084 } 1085 } 1086 } 1087 } 1088 1089 private interface DestroyableCallback { destroy()1090 public void destroy(); 1091 } 1092 1093 private final class MyLayoutResultCallback extends LayoutResultCallback 1094 implements DestroyableCallback { 1095 private ILayoutResultCallback mCallback; 1096 private final int mSequence; 1097 MyLayoutResultCallback(ILayoutResultCallback callback, int sequence)1098 public MyLayoutResultCallback(ILayoutResultCallback callback, 1099 int sequence) { 1100 mCallback = callback; 1101 mSequence = sequence; 1102 } 1103 1104 @Override onLayoutFinished(PrintDocumentInfo info, boolean changed)1105 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 1106 final ILayoutResultCallback callback; 1107 synchronized (mLock) { 1108 callback = mCallback; 1109 } 1110 1111 // If the callback is null we are destroyed. 1112 if (callback == null) { 1113 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1114 + "finish the printing activity before print completion " 1115 + "or did you invoke a callback after finish?"); 1116 return; 1117 } 1118 1119 try { 1120 if (info == null) { 1121 throw new NullPointerException("document info cannot be null"); 1122 } 1123 1124 try { 1125 callback.onLayoutFinished(info, changed, mSequence); 1126 } catch (RemoteException re) { 1127 Log.e(LOG_TAG, "Error calling onLayoutFinished", re); 1128 } 1129 } finally { 1130 destroy(); 1131 } 1132 } 1133 1134 @Override onLayoutFailed(CharSequence error)1135 public void onLayoutFailed(CharSequence error) { 1136 final ILayoutResultCallback callback; 1137 synchronized (mLock) { 1138 callback = mCallback; 1139 } 1140 1141 // If the callback is null we are destroyed. 1142 if (callback == null) { 1143 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1144 + "finish the printing activity before print completion " 1145 + "or did you invoke a callback after finish?"); 1146 return; 1147 } 1148 1149 try { 1150 callback.onLayoutFailed(error, mSequence); 1151 } catch (RemoteException re) { 1152 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 1153 } finally { 1154 destroy(); 1155 } 1156 } 1157 1158 @Override onLayoutCancelled()1159 public void onLayoutCancelled() { 1160 final ILayoutResultCallback callback; 1161 synchronized (mLock) { 1162 callback = mCallback; 1163 } 1164 1165 // If the callback is null we are destroyed. 1166 if (callback == null) { 1167 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1168 + "finish the printing activity before print completion " 1169 + "or did you invoke a callback after finish?"); 1170 return; 1171 } 1172 1173 try { 1174 callback.onLayoutCanceled(mSequence); 1175 } catch (RemoteException re) { 1176 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 1177 } finally { 1178 destroy(); 1179 } 1180 } 1181 1182 @Override destroy()1183 public void destroy() { 1184 synchronized (mLock) { 1185 mCallback = null; 1186 mPendingCallback = null; 1187 } 1188 } 1189 } 1190 1191 private final class MyWriteResultCallback extends WriteResultCallback 1192 implements DestroyableCallback { 1193 private ParcelFileDescriptor mFd; 1194 private IWriteResultCallback mCallback; 1195 private final int mSequence; 1196 MyWriteResultCallback(IWriteResultCallback callback, ParcelFileDescriptor fd, int sequence)1197 public MyWriteResultCallback(IWriteResultCallback callback, 1198 ParcelFileDescriptor fd, int sequence) { 1199 mFd = fd; 1200 mSequence = sequence; 1201 mCallback = callback; 1202 } 1203 1204 @Override onWriteFinished(PageRange[] pages)1205 public void onWriteFinished(PageRange[] pages) { 1206 final IWriteResultCallback callback; 1207 synchronized (mLock) { 1208 callback = mCallback; 1209 } 1210 1211 // If the callback is null we are destroyed. 1212 if (callback == null) { 1213 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1214 + "finish the printing activity before print completion " 1215 + "or did you invoke a callback after finish?"); 1216 return; 1217 } 1218 1219 try { 1220 if (pages == null) { 1221 throw new IllegalArgumentException("pages cannot be null"); 1222 } 1223 if (pages.length == 0) { 1224 throw new IllegalArgumentException("pages cannot be empty"); 1225 } 1226 1227 try { 1228 callback.onWriteFinished(pages, mSequence); 1229 } catch (RemoteException re) { 1230 Log.e(LOG_TAG, "Error calling onWriteFinished", re); 1231 } 1232 } finally { 1233 destroy(); 1234 } 1235 } 1236 1237 @Override onWriteFailed(CharSequence error)1238 public void onWriteFailed(CharSequence error) { 1239 final IWriteResultCallback callback; 1240 synchronized (mLock) { 1241 callback = mCallback; 1242 } 1243 1244 // If the callback is null we are destroyed. 1245 if (callback == null) { 1246 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1247 + "finish the printing activity before print completion " 1248 + "or did you invoke a callback after finish?"); 1249 return; 1250 } 1251 1252 try { 1253 callback.onWriteFailed(error, mSequence); 1254 } catch (RemoteException re) { 1255 Log.e(LOG_TAG, "Error calling onWriteFailed", re); 1256 } finally { 1257 destroy(); 1258 } 1259 } 1260 1261 @Override onWriteCancelled()1262 public void onWriteCancelled() { 1263 final IWriteResultCallback callback; 1264 synchronized (mLock) { 1265 callback = mCallback; 1266 } 1267 1268 // If the callback is null we are destroyed. 1269 if (callback == null) { 1270 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1271 + "finish the printing activity before print completion " 1272 + "or did you invoke a callback after finish?"); 1273 return; 1274 } 1275 1276 try { 1277 callback.onWriteCanceled(mSequence); 1278 } catch (RemoteException re) { 1279 Log.e(LOG_TAG, "Error calling onWriteCanceled", re); 1280 } finally { 1281 destroy(); 1282 } 1283 } 1284 1285 @Override destroy()1286 public void destroy() { 1287 synchronized (mLock) { 1288 IoUtils.closeQuietly(mFd); 1289 mCallback = null; 1290 mFd = null; 1291 mPendingCallback = null; 1292 } 1293 } 1294 } 1295 } 1296 1297 /** 1298 * @hide 1299 */ 1300 public static final class PrintJobStateChangeListenerWrapper extends 1301 IPrintJobStateChangeListener.Stub { 1302 private final WeakReference<PrintJobStateChangeListener> mWeakListener; 1303 private final WeakReference<Handler> mWeakHandler; 1304 PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, Handler handler)1305 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, 1306 Handler handler) { 1307 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener); 1308 mWeakHandler = new WeakReference<Handler>(handler); 1309 } 1310 1311 @Override onPrintJobStateChanged(PrintJobId printJobId)1312 public void onPrintJobStateChanged(PrintJobId printJobId) { 1313 Handler handler = mWeakHandler.get(); 1314 PrintJobStateChangeListener listener = mWeakListener.get(); 1315 if (handler != null && listener != null) { 1316 SomeArgs args = SomeArgs.obtain(); 1317 args.arg1 = this; 1318 args.arg2 = printJobId; 1319 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED, 1320 args).sendToTarget(); 1321 } 1322 } 1323 destroy()1324 public void destroy() { 1325 mWeakListener.clear(); 1326 } 1327 getListener()1328 public PrintJobStateChangeListener getListener() { 1329 return mWeakListener.get(); 1330 } 1331 } 1332 1333 /** 1334 * @hide 1335 */ 1336 public static final class PrintServicesChangeListenerWrapper extends 1337 IPrintServicesChangeListener.Stub { 1338 private final WeakReference<PrintServicesChangeListener> mWeakListener; 1339 private final WeakReference<Handler> mWeakHandler; 1340 PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener, Handler handler)1341 public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener, 1342 Handler handler) { 1343 mWeakListener = new WeakReference<>(listener); 1344 mWeakHandler = new WeakReference<>(handler); 1345 } 1346 1347 @Override onPrintServicesChanged()1348 public void onPrintServicesChanged() { 1349 Handler handler = mWeakHandler.get(); 1350 PrintServicesChangeListener listener = mWeakListener.get(); 1351 if (handler != null && listener != null) { 1352 handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICES_CHANGED, this).sendToTarget(); 1353 } 1354 } 1355 destroy()1356 public void destroy() { 1357 mWeakListener.clear(); 1358 } 1359 getListener()1360 public PrintServicesChangeListener getListener() { 1361 return mWeakListener.get(); 1362 } 1363 } 1364 1365 /** 1366 * @hide 1367 */ 1368 public static final class PrintServiceRecommendationsChangeListenerWrapper extends 1369 IRecommendationsChangeListener.Stub { 1370 private final WeakReference<PrintServiceRecommendationsChangeListener> mWeakListener; 1371 private final WeakReference<Handler> mWeakHandler; 1372 PrintServiceRecommendationsChangeListenerWrapper( PrintServiceRecommendationsChangeListener listener, Handler handler)1373 public PrintServiceRecommendationsChangeListenerWrapper( 1374 PrintServiceRecommendationsChangeListener listener, Handler handler) { 1375 mWeakListener = new WeakReference<>(listener); 1376 mWeakHandler = new WeakReference<>(handler); 1377 } 1378 1379 @Override onRecommendationsChanged()1380 public void onRecommendationsChanged() { 1381 Handler handler = mWeakHandler.get(); 1382 PrintServiceRecommendationsChangeListener listener = mWeakListener.get(); 1383 if (handler != null && listener != null) { 1384 handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED, 1385 this).sendToTarget(); 1386 } 1387 } 1388 destroy()1389 public void destroy() { 1390 mWeakListener.clear(); 1391 } 1392 getListener()1393 public PrintServiceRecommendationsChangeListener getListener() { 1394 return mWeakListener.get(); 1395 } 1396 } 1397 } 1398