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