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