PrintManager.java revision 85a85a0ed775999533b14a415fd79b50fe63e6d2
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 return new PrintManager(mContext, mService, userId, APP_ID_ANY); 218 } 219 220 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { 221 try { 222 return mService.getPrintJobInfo(printJobId, mAppId, mUserId); 223 } catch (RemoteException re) { 224 Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); 225 } 226 return null; 227 } 228 229 /** 230 * Adds a listener for observing the state of print jobs. 231 * 232 * @param listener The listener to add. 233 * @hide 234 */ 235 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) { 236 if (mPrintJobStateChangeListeners == null) { 237 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener, 238 PrintJobStateChangeListenerWrapper>(); 239 } 240 PrintJobStateChangeListenerWrapper wrappedListener = 241 new PrintJobStateChangeListenerWrapper(listener, mHandler); 242 try { 243 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId); 244 mPrintJobStateChangeListeners.put(listener, wrappedListener); 245 } catch (RemoteException re) { 246 Log.e(LOG_TAG, "Error adding print job state change listener", re); 247 } 248 } 249 250 /** 251 * Removes a listener for observing the state of print jobs. 252 * 253 * @param listener The listener to remove. 254 * @hide 255 */ 256 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) { 257 if (mPrintJobStateChangeListeners == null) { 258 return; 259 } 260 PrintJobStateChangeListenerWrapper wrappedListener = 261 mPrintJobStateChangeListeners.remove(listener); 262 if (wrappedListener == null) { 263 return; 264 } 265 if (mPrintJobStateChangeListeners.isEmpty()) { 266 mPrintJobStateChangeListeners = null; 267 } 268 wrappedListener.destroy(); 269 try { 270 mService.removePrintJobStateChangeListener(wrappedListener, mUserId); 271 } catch (RemoteException re) { 272 Log.e(LOG_TAG, "Error removing print job state change listener", re); 273 } 274 } 275 276 /** 277 * Gets a print job given its id. 278 * 279 * @return The print job list. 280 * @see PrintJob 281 * @hide 282 */ 283 public PrintJob getPrintJob(PrintJobId printJobId) { 284 try { 285 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId); 286 if (printJob != null) { 287 return new PrintJob(printJob, this); 288 } 289 } catch (RemoteException re) { 290 Log.e(LOG_TAG, "Error getting print job", re); 291 } 292 return null; 293 } 294 295 /** 296 * Gets the print jobs for this application. 297 * 298 * @return The print job list. 299 * @see PrintJob 300 */ 301 public List<PrintJob> getPrintJobs() { 302 try { 303 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); 304 if (printJobInfos == null) { 305 return Collections.emptyList(); 306 } 307 final int printJobCount = printJobInfos.size(); 308 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); 309 for (int i = 0; i < printJobCount; i++) { 310 printJobs.add(new PrintJob(printJobInfos.get(i), this)); 311 } 312 return printJobs; 313 } catch (RemoteException re) { 314 Log.e(LOG_TAG, "Error getting print jobs", re); 315 } 316 return Collections.emptyList(); 317 } 318 319 void cancelPrintJob(PrintJobId printJobId) { 320 try { 321 mService.cancelPrintJob(printJobId, mAppId, mUserId); 322 } catch (RemoteException re) { 323 Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); 324 } 325 } 326 327 void restartPrintJob(PrintJobId printJobId) { 328 try { 329 mService.restartPrintJob(printJobId, mAppId, mUserId); 330 } catch (RemoteException re) { 331 Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re); 332 } 333 } 334 335 /** 336 * Creates a print job for printing a {@link PrintDocumentAdapter} with 337 * default print attributes. 338 * <p> 339 * Calling this method brings the print UI allowing the user to customize 340 * the print job and returns a {@link PrintJob} object without waiting for the 341 * user to customize or confirm the print job. The returned print job instance 342 * is in a {@link PrintJobInfo#STATE_CREATED created} state. 343 * <p> 344 * This method can be called only from an {@link Activity}. The rationale is that 345 * printing from a service will create an inconsistent user experience as the print 346 * UI would appear without any context. 347 * </p> 348 * <p> 349 * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if 350 * your activity is finished. The rationale is that once the activity that 351 * initiated printing is finished, the provided adapter may be in an inconsistent 352 * state as it may depend on the UI presented by the activity. 353 * </p> 354 * <p> 355 * The default print attributes are a hint to the system how the data is to 356 * be printed. For example, a photo editor may look at the photo aspect ratio 357 * to determine the default orientation and provide a hint whether the printing 358 * should be in portrait or landscape. The system will do a best effort to 359 * selected the hinted options in the print dialog, given the current printer 360 * supports them. 361 * </p> 362 * <p> 363 * <strong>Note:</strong> Calling this method will bring the print dialog and 364 * the system will connect to the provided {@link PrintDocumentAdapter}. If a 365 * configuration change occurs that you application does not handle, for example 366 * a rotation change, the system will drop the connection to the adapter as the 367 * activity has to be recreated and the old adapter may be invalid in this context, 368 * hence a new adapter instance is required. As a consequence, if your activity 369 * does not handle configuration changes (default behavior), you have to save the 370 * state that you were printing and call this method again when your activity 371 * is recreated. 372 * </p> 373 * 374 * @param printJobName A name for the new print job which is shown to the user. 375 * @param documentAdapter An adapter that emits the document to print. 376 * @param attributes The default print job attributes or <code>null</code>. 377 * @return The created print job on success or null on failure. 378 * @throws IllegalStateException If not called from an {@link Activity}. 379 * @throws IllegalArgumentException If the print job name is empty or the 380 * document adapter is null. 381 * 382 * @see PrintJob 383 */ 384 public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, 385 PrintAttributes attributes) { 386 if (!(mContext instanceof Activity)) { 387 throw new IllegalStateException("Can print only from an activity"); 388 } 389 if (TextUtils.isEmpty(printJobName)) { 390 throw new IllegalArgumentException("printJobName cannot be empty"); 391 } 392 if (documentAdapter == null) { 393 throw new IllegalArgumentException("documentAdapter cannot be null"); 394 } 395 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate( 396 (Activity) mContext, documentAdapter); 397 try { 398 Bundle result = mService.print(printJobName, delegate, 399 attributes, mContext.getPackageName(), mAppId, mUserId); 400 if (result != null) { 401 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB); 402 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT); 403 if (printJob == null || intent == null) { 404 return null; 405 } 406 try { 407 mContext.startIntentSender(intent, null, 0, 0, 0); 408 return new PrintJob(printJob, this); 409 } catch (SendIntentException sie) { 410 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); 411 } 412 } 413 } catch (RemoteException re) { 414 Log.e(LOG_TAG, "Error creating a print job", re); 415 } 416 return null; 417 } 418 419 /** 420 * Gets the list of enabled print services. 421 * 422 * @return The enabled service list or an empty list. 423 * @hide 424 */ 425 public List<PrintServiceInfo> getEnabledPrintServices() { 426 try { 427 List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId); 428 if (enabledServices != null) { 429 return enabledServices; 430 } 431 } catch (RemoteException re) { 432 Log.e(LOG_TAG, "Error getting the enabled print services", re); 433 } 434 return Collections.emptyList(); 435 } 436 437 /** 438 * Gets the list of installed print services. 439 * 440 * @return The installed service list or an empty list. 441 * @hide 442 */ 443 public List<PrintServiceInfo> getInstalledPrintServices() { 444 try { 445 List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId); 446 if (installedServices != null) { 447 return installedServices; 448 } 449 } catch (RemoteException re) { 450 Log.e(LOG_TAG, "Error getting the installed print services", re); 451 } 452 return Collections.emptyList(); 453 } 454 455 /** 456 * @hide 457 */ 458 public PrinterDiscoverySession createPrinterDiscoverySession() { 459 return new PrinterDiscoverySession(mService, mContext, mUserId); 460 } 461 462 private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub 463 implements ActivityLifecycleCallbacks { 464 465 private final Object mLock = new Object(); 466 467 private CancellationSignal mLayoutOrWriteCancellation; 468 469 private Activity mActivity; // Strong reference OK - cleared in finish() 470 471 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish 472 473 private Handler mHandler; // Strong reference OK - cleared in finish() 474 475 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish 476 477 private LayoutSpec mLastLayoutSpec; 478 479 private WriteSpec mLastWriteSpec; 480 481 private boolean mStartReqeusted; 482 private boolean mStarted; 483 484 private boolean mFinishRequested; 485 private boolean mFinished; 486 487 private boolean mDestroyed; 488 489 public PrintDocumentAdapterDelegate(Activity activity, 490 PrintDocumentAdapter documentAdapter) { 491 mActivity = activity; 492 mDocumentAdapter = documentAdapter; 493 mHandler = new MyHandler(mActivity.getMainLooper()); 494 mActivity.getApplication().registerActivityLifecycleCallbacks(this); 495 } 496 497 @Override 498 public void setObserver(IPrintDocumentAdapterObserver observer) { 499 final boolean destroyed; 500 synchronized (mLock) { 501 if (!mDestroyed) { 502 mObserver = observer; 503 } 504 destroyed = mDestroyed; 505 } 506 if (destroyed) { 507 try { 508 observer.onDestroy(); 509 } catch (RemoteException re) { 510 Log.e(LOG_TAG, "Error announcing destroyed state", re); 511 } 512 } 513 } 514 515 @Override 516 public void start() { 517 synchronized (mLock) { 518 // Started called or finish called or destroyed - nothing to do. 519 if (mStartReqeusted || mFinishRequested || mDestroyed) { 520 return; 521 } 522 523 mStartReqeusted = true; 524 525 doPendingWorkLocked(); 526 } 527 } 528 529 @Override 530 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, 531 ILayoutResultCallback callback, Bundle metadata, int sequence) { 532 final boolean destroyed; 533 synchronized (mLock) { 534 destroyed = mDestroyed; 535 // If start called and not finished called and not destroyed - do some work. 536 if (mStartReqeusted && !mFinishRequested && !mDestroyed) { 537 // Layout cancels write and overrides layout. 538 if (mLastWriteSpec != null) { 539 IoUtils.closeQuietly(mLastWriteSpec.fd); 540 mLastWriteSpec = null; 541 } 542 543 mLastLayoutSpec = new LayoutSpec(); 544 mLastLayoutSpec.callback = callback; 545 mLastLayoutSpec.oldAttributes = oldAttributes; 546 mLastLayoutSpec.newAttributes = newAttributes; 547 mLastLayoutSpec.metadata = metadata; 548 mLastLayoutSpec.sequence = sequence; 549 550 // Cancel the previous cancellable operation.When the 551 // cancellation completes we will do the pending work. 552 if (cancelPreviousCancellableOperationLocked()) { 553 return; 554 } 555 556 doPendingWorkLocked(); 557 } 558 } 559 if (destroyed) { 560 try { 561 callback.onLayoutFailed(null, sequence); 562 } catch (RemoteException re) { 563 Log.i(LOG_TAG, "Error notifying for cancelled layout", re); 564 } 565 } 566 } 567 568 @Override 569 public void write(PageRange[] pages, ParcelFileDescriptor fd, 570 IWriteResultCallback callback, int sequence) { 571 final boolean destroyed; 572 synchronized (mLock) { 573 destroyed = mDestroyed; 574 // If start called and not finished called and not destroyed - do some work. 575 if (mStartReqeusted && !mFinishRequested && !mDestroyed) { 576 // Write cancels previous writes. 577 if (mLastWriteSpec != null) { 578 IoUtils.closeQuietly(mLastWriteSpec.fd); 579 mLastWriteSpec = null; 580 } 581 582 mLastWriteSpec = new WriteSpec(); 583 mLastWriteSpec.callback = callback; 584 mLastWriteSpec.pages = pages; 585 mLastWriteSpec.fd = fd; 586 mLastWriteSpec.sequence = sequence; 587 588 // Cancel the previous cancellable operation.When the 589 // cancellation completes we will do the pending work. 590 if (cancelPreviousCancellableOperationLocked()) { 591 return; 592 } 593 594 doPendingWorkLocked(); 595 } 596 } 597 if (destroyed) { 598 try { 599 callback.onWriteFailed(null, sequence); 600 } catch (RemoteException re) { 601 Log.i(LOG_TAG, "Error notifying for cancelled write", re); 602 } 603 } 604 } 605 606 @Override 607 public void finish() { 608 synchronized (mLock) { 609 // Start not called or finish called or destroyed - nothing to do. 610 if (!mStartReqeusted || mFinishRequested || mDestroyed) { 611 return; 612 } 613 614 mFinishRequested = true; 615 616 // When the current write or layout complete we 617 // will do the pending work. 618 if (mLastLayoutSpec != null || mLastWriteSpec != null) { 619 if (DEBUG) { 620 Log.i(LOG_TAG, "Waiting for current operation"); 621 } 622 return; 623 } 624 625 doPendingWorkLocked(); 626 } 627 } 628 629 @Override 630 public void cancel() { 631 // Start not called or finish called or destroyed - nothing to do. 632 if (!mStartReqeusted || mFinishRequested || mDestroyed) { 633 return; 634 } 635 // Request cancellation of pending work if needed. 636 synchronized (mLock) { 637 cancelPreviousCancellableOperationLocked(); 638 } 639 } 640 641 @Override 642 public void onActivityPaused(Activity activity) { 643 /* do nothing */ 644 } 645 646 @Override 647 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 648 /* do nothing */ 649 } 650 651 @Override 652 public void onActivityStarted(Activity activity) { 653 /* do nothing */ 654 } 655 656 @Override 657 public void onActivityResumed(Activity activity) { 658 /* do nothing */ 659 } 660 661 @Override 662 public void onActivityStopped(Activity activity) { 663 /* do nothing */ 664 } 665 666 @Override 667 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 668 /* do nothing */ 669 } 670 671 @Override 672 public void onActivityDestroyed(Activity activity) { 673 // We really care only if the activity is being destroyed to 674 // notify the the print spooler so it can close the print dialog. 675 // Note the the spooler has a death recipient that observes if 676 // this process gets killed so we cover the case of onDestroy not 677 // being called due to this process being killed to reclaim memory. 678 final IPrintDocumentAdapterObserver observer; 679 synchronized (mLock) { 680 if (activity == mActivity) { 681 mDestroyed = true; 682 observer = mObserver; 683 clearLocked(); 684 } else { 685 observer = null; 686 activity = null; 687 } 688 } 689 if (observer != null) { 690 activity.getApplication().unregisterActivityLifecycleCallbacks( 691 PrintDocumentAdapterDelegate.this); 692 try { 693 observer.onDestroy(); 694 } catch (RemoteException re) { 695 Log.e(LOG_TAG, "Error announcing destroyed state", re); 696 } 697 } 698 } 699 700 private boolean isFinished() { 701 return mDocumentAdapter == null; 702 } 703 704 private void clearLocked() { 705 mActivity = null; 706 mDocumentAdapter = null; 707 mHandler = null; 708 mLayoutOrWriteCancellation = null; 709 mLastLayoutSpec = null; 710 if (mLastWriteSpec != null) { 711 IoUtils.closeQuietly(mLastWriteSpec.fd); 712 mLastWriteSpec = null; 713 } 714 } 715 716 private boolean cancelPreviousCancellableOperationLocked() { 717 if (mLayoutOrWriteCancellation != null) { 718 mLayoutOrWriteCancellation.cancel(); 719 if (DEBUG) { 720 Log.i(LOG_TAG, "Cancelling previous operation"); 721 } 722 return true; 723 } 724 return false; 725 } 726 727 private void doPendingWorkLocked() { 728 if (mStartReqeusted && !mStarted) { 729 mStarted = true; 730 mHandler.sendEmptyMessage(MyHandler.MSG_START); 731 } else if (mLastLayoutSpec != null) { 732 mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT); 733 } else if (mLastWriteSpec != null) { 734 mHandler.sendEmptyMessage(MyHandler.MSG_WRITE); 735 } else if (mFinishRequested && !mFinished) { 736 mFinished = true; 737 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); 738 } 739 } 740 741 private class LayoutSpec { 742 ILayoutResultCallback callback; 743 PrintAttributes oldAttributes; 744 PrintAttributes newAttributes; 745 Bundle metadata; 746 int sequence; 747 } 748 749 private class WriteSpec { 750 IWriteResultCallback callback; 751 PageRange[] pages; 752 ParcelFileDescriptor fd; 753 int sequence; 754 } 755 756 private final class MyHandler extends Handler { 757 public static final int MSG_START = 1; 758 public static final int MSG_LAYOUT = 2; 759 public static final int MSG_WRITE = 3; 760 public static final int MSG_FINISH = 4; 761 762 public MyHandler(Looper looper) { 763 super(looper, null, true); 764 } 765 766 @Override 767 public void handleMessage(Message message) { 768 if (isFinished()) { 769 return; 770 } 771 switch (message.what) { 772 case MSG_START: { 773 final PrintDocumentAdapter adapter; 774 synchronized (mLock) { 775 adapter = mDocumentAdapter; 776 } 777 if (adapter != null) { 778 adapter.onStart(); 779 } 780 } break; 781 782 case MSG_LAYOUT: { 783 final PrintDocumentAdapter adapter; 784 final CancellationSignal cancellation; 785 final LayoutSpec layoutSpec; 786 787 synchronized (mLock) { 788 adapter = mDocumentAdapter; 789 layoutSpec = mLastLayoutSpec; 790 mLastLayoutSpec = null; 791 cancellation = new CancellationSignal(); 792 mLayoutOrWriteCancellation = cancellation; 793 } 794 795 if (layoutSpec != null && adapter != null) { 796 if (DEBUG) { 797 Log.i(LOG_TAG, "Performing layout"); 798 } 799 adapter.onLayout(layoutSpec.oldAttributes, 800 layoutSpec.newAttributes, cancellation, 801 new MyLayoutResultCallback(layoutSpec.callback, 802 layoutSpec.sequence), layoutSpec.metadata); 803 } 804 } break; 805 806 case MSG_WRITE: { 807 final PrintDocumentAdapter adapter; 808 final CancellationSignal cancellation; 809 final WriteSpec writeSpec; 810 811 synchronized (mLock) { 812 adapter = mDocumentAdapter; 813 writeSpec = mLastWriteSpec; 814 mLastWriteSpec = null; 815 cancellation = new CancellationSignal(); 816 mLayoutOrWriteCancellation = cancellation; 817 } 818 819 if (writeSpec != null && adapter != null) { 820 if (DEBUG) { 821 Log.i(LOG_TAG, "Performing write"); 822 } 823 adapter.onWrite(writeSpec.pages, writeSpec.fd, 824 cancellation, new MyWriteResultCallback(writeSpec.callback, 825 writeSpec.fd, writeSpec.sequence)); 826 } 827 } break; 828 829 case MSG_FINISH: { 830 if (DEBUG) { 831 Log.i(LOG_TAG, "Performing finish"); 832 } 833 final PrintDocumentAdapter adapter; 834 final Activity activity; 835 synchronized (mLock) { 836 adapter = mDocumentAdapter; 837 activity = mActivity; 838 clearLocked(); 839 } 840 if (adapter != null) { 841 adapter.onFinish(); 842 } 843 if (activity != null) { 844 activity.getApplication().unregisterActivityLifecycleCallbacks( 845 PrintDocumentAdapterDelegate.this); 846 } 847 } break; 848 849 default: { 850 throw new IllegalArgumentException("Unknown message: " 851 + message.what); 852 } 853 } 854 } 855 } 856 857 private final class MyLayoutResultCallback extends LayoutResultCallback { 858 private ILayoutResultCallback mCallback; 859 private final int mSequence; 860 861 public MyLayoutResultCallback(ILayoutResultCallback callback, 862 int sequence) { 863 mCallback = callback; 864 mSequence = sequence; 865 } 866 867 @Override 868 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 869 if (info == null) { 870 throw new NullPointerException("document info cannot be null"); 871 } 872 final ILayoutResultCallback callback; 873 synchronized (mLock) { 874 if (mDestroyed) { 875 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 876 + "finish the printing activity before print completion?"); 877 return; 878 } 879 callback = mCallback; 880 clearLocked(); 881 } 882 if (callback != null) { 883 try { 884 callback.onLayoutFinished(info, changed, mSequence); 885 } catch (RemoteException re) { 886 Log.e(LOG_TAG, "Error calling onLayoutFinished", re); 887 } 888 } 889 } 890 891 @Override 892 public void onLayoutFailed(CharSequence error) { 893 final ILayoutResultCallback callback; 894 synchronized (mLock) { 895 if (mDestroyed) { 896 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 897 + "finish the printing activity before print completion?"); 898 return; 899 } 900 callback = mCallback; 901 clearLocked(); 902 } 903 if (callback != null) { 904 try { 905 callback.onLayoutFailed(error, mSequence); 906 } catch (RemoteException re) { 907 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 908 } 909 } 910 } 911 912 @Override 913 public void onLayoutCancelled() { 914 synchronized (mLock) { 915 if (mDestroyed) { 916 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 917 + "finish the printing activity before print completion?"); 918 return; 919 } 920 clearLocked(); 921 } 922 } 923 924 private void clearLocked() { 925 mLayoutOrWriteCancellation = null; 926 mCallback = null; 927 doPendingWorkLocked(); 928 } 929 } 930 931 private final class MyWriteResultCallback extends WriteResultCallback { 932 private ParcelFileDescriptor mFd; 933 private int mSequence; 934 private IWriteResultCallback mCallback; 935 936 public MyWriteResultCallback(IWriteResultCallback callback, 937 ParcelFileDescriptor fd, int sequence) { 938 mFd = fd; 939 mSequence = sequence; 940 mCallback = callback; 941 } 942 943 @Override 944 public void onWriteFinished(PageRange[] pages) { 945 final IWriteResultCallback callback; 946 synchronized (mLock) { 947 if (mDestroyed) { 948 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 949 + "finish the printing activity before print completion?"); 950 return; 951 } 952 callback = mCallback; 953 clearLocked(); 954 } 955 if (pages == null) { 956 throw new IllegalArgumentException("pages cannot be null"); 957 } 958 if (pages.length == 0) { 959 throw new IllegalArgumentException("pages cannot be empty"); 960 } 961 if (callback != null) { 962 try { 963 callback.onWriteFinished(pages, mSequence); 964 } catch (RemoteException re) { 965 Log.e(LOG_TAG, "Error calling onWriteFinished", re); 966 } 967 } 968 } 969 970 @Override 971 public void onWriteFailed(CharSequence error) { 972 final IWriteResultCallback callback; 973 synchronized (mLock) { 974 if (mDestroyed) { 975 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 976 + "finish the printing activity before print completion?"); 977 return; 978 } 979 callback = mCallback; 980 clearLocked(); 981 } 982 if (callback != null) { 983 try { 984 callback.onWriteFailed(error, mSequence); 985 } catch (RemoteException re) { 986 Log.e(LOG_TAG, "Error calling onWriteFailed", re); 987 } 988 } 989 } 990 991 @Override 992 public void onWriteCancelled() { 993 synchronized (mLock) { 994 if (mDestroyed) { 995 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 996 + "finish the printing activity before print completion?"); 997 return; 998 } 999 clearLocked(); 1000 } 1001 } 1002 1003 private void clearLocked() { 1004 mLayoutOrWriteCancellation = null; 1005 IoUtils.closeQuietly(mFd); 1006 mCallback = null; 1007 mFd = null; 1008 doPendingWorkLocked(); 1009 } 1010 } 1011 } 1012 1013 private static final class PrintJobStateChangeListenerWrapper extends 1014 IPrintJobStateChangeListener.Stub { 1015 private final WeakReference<PrintJobStateChangeListener> mWeakListener; 1016 private final WeakReference<Handler> mWeakHandler; 1017 1018 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, 1019 Handler handler) { 1020 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener); 1021 mWeakHandler = new WeakReference<Handler>(handler); 1022 } 1023 1024 @Override 1025 public void onPrintJobStateChanged(PrintJobId printJobId) { 1026 Handler handler = mWeakHandler.get(); 1027 PrintJobStateChangeListener listener = mWeakListener.get(); 1028 if (handler != null && listener != null) { 1029 SomeArgs args = SomeArgs.obtain(); 1030 args.arg1 = this; 1031 args.arg2 = printJobId; 1032 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED, 1033 args).sendToTarget(); 1034 } 1035 } 1036 1037 public void destroy() { 1038 mWeakListener.clear(); 1039 } 1040 1041 public PrintJobStateChangeListener getListener() { 1042 return mWeakListener.get(); 1043 } 1044 } 1045} 1046