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