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