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