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