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