PrintManager.java revision 858a1850e2e1c4516129d27ecdf54aaeade606ca
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 */ 59public final class PrintManager { 60 61 private static final String LOG_TAG = "PrintManager"; 62 63 private static final boolean DEBUG = false; 64 65 private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1; 66 67 /** 68 * The action for launching the print dialog activity. 69 * 70 * @hide 71 */ 72 public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG"; 73 74 /** 75 * Extra with the intent for starting the print dialog. 76 * <p> 77 * <strong>Type:</strong> {@link android.content.IntentSender} 78 * </p> 79 * 80 * @hide 81 */ 82 public static final String EXTRA_PRINT_DIALOG_INTENT = 83 "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT"; 84 85 /** 86 * Extra with a print job. 87 * <p> 88 * <strong>Type:</strong> {@link android.print.PrintJobInfo} 89 * </p> 90 * 91 * @hide 92 */ 93 public static final String EXTRA_PRINT_JOB = 94 "android.print.intent.extra.EXTRA_PRINT_JOB"; 95 96 /** 97 * Extra with the print document adapter to be printed. 98 * <p> 99 * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter} 100 * </p> 101 * 102 * @hide 103 */ 104 public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = 105 "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER"; 106 107 /** @hide */ 108 public static final int APP_ID_ANY = -2; 109 110 private final Context mContext; 111 112 private final IPrintManager mService; 113 114 private final int mUserId; 115 116 private final int mAppId; 117 118 private final Handler mHandler; 119 120 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners; 121 122 /** @hide */ 123 public interface PrintJobStateChangeListener { 124 125 /** 126 * Callback notifying that a print job state changed. 127 * 128 * @param printJobId The print job id. 129 */ 130 public void onPrintJobStateChanged(PrintJobId printJobId); 131 } 132 133 /** 134 * Creates a new instance. 135 * 136 * @param context The current context in which to operate. 137 * @param service The backing system service. 138 * @hide 139 */ 140 public PrintManager(Context context, IPrintManager service, int userId, int appId) { 141 mContext = context; 142 mService = service; 143 mUserId = userId; 144 mAppId = appId; 145 mHandler = new Handler(context.getMainLooper(), null, false) { 146 @Override 147 public void handleMessage(Message message) { 148 switch (message.what) { 149 case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: { 150 SomeArgs args = (SomeArgs) message.obj; 151 PrintJobStateChangeListenerWrapper wrapper = 152 (PrintJobStateChangeListenerWrapper) args.arg1; 153 PrintJobStateChangeListener listener = wrapper.getListener(); 154 if (listener != null) { 155 PrintJobId printJobId = (PrintJobId) args.arg2; 156 listener.onPrintJobStateChanged(printJobId); 157 } 158 args.recycle(); 159 } break; 160 } 161 } 162 }; 163 } 164 165 /** 166 * Creates an instance that can access all print jobs. 167 * 168 * @param userId The user id for which to get all print jobs. 169 * @return An instance if the caller has the permission to access all print 170 * jobs, null otherwise. 171 * @hide 172 */ 173 public PrintManager getGlobalPrintManagerForUser(int userId) { 174 return new PrintManager(mContext, mService, userId, APP_ID_ANY); 175 } 176 177 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { 178 try { 179 return mService.getPrintJobInfo(printJobId, mAppId, mUserId); 180 } catch (RemoteException re) { 181 Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); 182 } 183 return null; 184 } 185 186 /** 187 * Adds a listener for observing the state of print jobs. 188 * 189 * @param listener The listener to add. 190 * @hide 191 */ 192 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) { 193 if (mPrintJobStateChangeListeners == null) { 194 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener, 195 PrintJobStateChangeListenerWrapper>(); 196 } 197 PrintJobStateChangeListenerWrapper wrappedListener = 198 new PrintJobStateChangeListenerWrapper(listener, mHandler); 199 try { 200 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId); 201 mPrintJobStateChangeListeners.put(listener, wrappedListener); 202 } catch (RemoteException re) { 203 Log.e(LOG_TAG, "Error adding print job state change listener", re); 204 } 205 } 206 207 /** 208 * Removes a listener for observing the state of print jobs. 209 * 210 * @param listener The listener to remove. 211 * @hide 212 */ 213 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) { 214 if (mPrintJobStateChangeListeners == null) { 215 return; 216 } 217 PrintJobStateChangeListenerWrapper wrappedListener = 218 mPrintJobStateChangeListeners.remove(listener); 219 if (wrappedListener == null) { 220 return; 221 } 222 if (mPrintJobStateChangeListeners.isEmpty()) { 223 mPrintJobStateChangeListeners = null; 224 } 225 wrappedListener.destroy(); 226 try { 227 mService.removePrintJobStateChangeListener(wrappedListener, mUserId); 228 } catch (RemoteException re) { 229 Log.e(LOG_TAG, "Error removing print job state change listener", re); 230 } 231 } 232 233 /** 234 * Gets a print job given its id. 235 * 236 * @return The print job list. 237 * @see PrintJob 238 * @hide 239 */ 240 public PrintJob getPrintJob(PrintJobId printJobId) { 241 try { 242 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId); 243 if (printJob != null) { 244 return new PrintJob(printJob, this); 245 } 246 } catch (RemoteException re) { 247 Log.e(LOG_TAG, "Error getting print job", re); 248 } 249 return null; 250 } 251 252 /** 253 * Gets the print jobs for this application. 254 * 255 * @return The print job list. 256 * @see PrintJob 257 */ 258 public List<PrintJob> getPrintJobs() { 259 try { 260 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); 261 if (printJobInfos == null) { 262 return Collections.emptyList(); 263 } 264 final int printJobCount = printJobInfos.size(); 265 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); 266 for (int i = 0; i < printJobCount; i++) { 267 printJobs.add(new PrintJob(printJobInfos.get(i), this)); 268 } 269 return printJobs; 270 } catch (RemoteException re) { 271 Log.e(LOG_TAG, "Error getting print jobs", re); 272 } 273 return Collections.emptyList(); 274 } 275 276 void cancelPrintJob(PrintJobId printJobId) { 277 try { 278 mService.cancelPrintJob(printJobId, mAppId, mUserId); 279 } catch (RemoteException re) { 280 Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); 281 } 282 } 283 284 void restartPrintJob(PrintJobId printJobId) { 285 try { 286 mService.restartPrintJob(printJobId, mAppId, mUserId); 287 } catch (RemoteException re) { 288 Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re); 289 } 290 } 291 292 /** 293 * Creates a print job for printing a {@link PrintDocumentAdapter} with 294 * default print attributes. 295 * 296 * @param printJobName A name for the new print job. 297 * @param documentAdapter An adapter that emits the document to print. 298 * @param attributes The default print job attributes. 299 * @return The created print job on success or null on failure. 300 * @see PrintJob 301 */ 302 public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, 303 PrintAttributes attributes) { 304 if (TextUtils.isEmpty(printJobName)) { 305 throw new IllegalArgumentException("priintJobName cannot be empty"); 306 } 307 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate( 308 mContext, documentAdapter); 309 try { 310 Bundle result = mService.print(printJobName, delegate, 311 attributes, mContext.getPackageName(), mAppId, mUserId); 312 if (result != null) { 313 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB); 314 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT); 315 if (printJob == null || intent == null) { 316 return null; 317 } 318 try { 319 mContext.startIntentSender(intent, null, 0, 0, 0); 320 return new PrintJob(printJob, this); 321 } catch (SendIntentException sie) { 322 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); 323 } 324 } 325 } catch (RemoteException re) { 326 Log.e(LOG_TAG, "Error creating a print job", re); 327 } 328 return null; 329 } 330 331 /** 332 * Gets the list of enabled print services. 333 * 334 * @return The enabled service list or an empty list. 335 * @hide 336 */ 337 public List<PrintServiceInfo> getEnabledPrintServices() { 338 try { 339 List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId); 340 if (enabledServices != null) { 341 return enabledServices; 342 } 343 } catch (RemoteException re) { 344 Log.e(LOG_TAG, "Error getting the enabled print services", re); 345 } 346 return Collections.emptyList(); 347 } 348 349 /** 350 * Gets the list of installed print services. 351 * 352 * @return The installed service list or an empty list. 353 * @hide 354 */ 355 public List<PrintServiceInfo> getInstalledPrintServices() { 356 try { 357 List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId); 358 if (installedServices != null) { 359 return installedServices; 360 } 361 } catch (RemoteException re) { 362 Log.e(LOG_TAG, "Error getting the installed print services", re); 363 } 364 return Collections.emptyList(); 365 } 366 367 /** 368 * @hide 369 */ 370 public PrinterDiscoverySession createPrinterDiscoverySession() { 371 return new PrinterDiscoverySession(mService, mContext, mUserId); 372 } 373 374 private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub 375 implements ActivityLifecycleCallbacks { 376 377 private final Object mLock = new Object(); 378 379 private CancellationSignal mLayoutOrWriteCancellation; 380 381 private Activity mActivity; // Strong reference OK - cleared in finish() 382 383 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish 384 385 private Handler mHandler; // Strong reference OK - cleared in finish() 386 387 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish 388 389 private LayoutSpec mLastLayoutSpec; 390 391 private WriteSpec mLastWriteSpec; 392 393 private boolean mStartReqeusted; 394 private boolean mStarted; 395 396 private boolean mFinishRequested; 397 private boolean mFinished; 398 399 private boolean mDestroyed; 400 401 public PrintDocumentAdapterDelegate(Context context, 402 PrintDocumentAdapter documentAdapter) { 403 if (!(context instanceof Activity)) { 404 throw new IllegalStateException("Can print only from an activity"); 405 } 406 mActivity = (Activity) context; 407 mDocumentAdapter = documentAdapter; 408 mHandler = new MyHandler(mActivity.getMainLooper()); 409 mActivity.getApplication().registerActivityLifecycleCallbacks(this); 410 } 411 412 @Override 413 public void setObserver(IPrintDocumentAdapterObserver observer) { 414 final boolean destroyed; 415 synchronized (mLock) { 416 if (!mDestroyed) { 417 mObserver = observer; 418 } 419 destroyed = mDestroyed; 420 } 421 if (destroyed) { 422 try { 423 observer.onDestroy(); 424 } catch (RemoteException re) { 425 Log.e(LOG_TAG, "Error announcing destroyed state", re); 426 } 427 } 428 } 429 430 @Override 431 public void start() { 432 synchronized (mLock) { 433 // Started called or finish called or destroyed - nothing to do. 434 if (mStartReqeusted || mFinishRequested || mDestroyed) { 435 return; 436 } 437 438 mStartReqeusted = true; 439 440 doPendingWorkLocked(); 441 } 442 } 443 444 @Override 445 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, 446 ILayoutResultCallback callback, Bundle metadata, int sequence) { 447 final boolean destroyed; 448 synchronized (mLock) { 449 destroyed = mDestroyed; 450 // If start called and not finished called and not destroyed - do some work. 451 if (mStartReqeusted && !mFinishRequested && !mDestroyed) { 452 // Layout cancels write and overrides layout. 453 if (mLastWriteSpec != null) { 454 IoUtils.closeQuietly(mLastWriteSpec.fd); 455 mLastWriteSpec = null; 456 } 457 458 mLastLayoutSpec = new LayoutSpec(); 459 mLastLayoutSpec.callback = callback; 460 mLastLayoutSpec.oldAttributes = oldAttributes; 461 mLastLayoutSpec.newAttributes = newAttributes; 462 mLastLayoutSpec.metadata = metadata; 463 mLastLayoutSpec.sequence = sequence; 464 465 // Cancel the previous cancellable operation.When the 466 // cancellation completes we will do the pending work. 467 if (cancelPreviousCancellableOperationLocked()) { 468 return; 469 } 470 471 doPendingWorkLocked(); 472 } 473 } 474 if (destroyed) { 475 try { 476 callback.onLayoutFailed(null, sequence); 477 } catch (RemoteException re) { 478 Log.i(LOG_TAG, "Error notifying for cancelled layout", re); 479 } 480 } 481 } 482 483 @Override 484 public void write(PageRange[] pages, ParcelFileDescriptor fd, 485 IWriteResultCallback callback, int sequence) { 486 final boolean destroyed; 487 synchronized (mLock) { 488 destroyed = mDestroyed; 489 // If start called and not finished called and not destroyed - do some work. 490 if (mStartReqeusted && !mFinishRequested && !mDestroyed) { 491 // Write cancels previous writes. 492 if (mLastWriteSpec != null) { 493 IoUtils.closeQuietly(mLastWriteSpec.fd); 494 mLastWriteSpec = null; 495 } 496 497 mLastWriteSpec = new WriteSpec(); 498 mLastWriteSpec.callback = callback; 499 mLastWriteSpec.pages = pages; 500 mLastWriteSpec.fd = fd; 501 mLastWriteSpec.sequence = sequence; 502 503 // Cancel the previous cancellable operation.When the 504 // cancellation completes we will do the pending work. 505 if (cancelPreviousCancellableOperationLocked()) { 506 return; 507 } 508 509 doPendingWorkLocked(); 510 } 511 } 512 if (destroyed) { 513 try { 514 callback.onWriteFailed(null, sequence); 515 } catch (RemoteException re) { 516 Log.i(LOG_TAG, "Error notifying for cancelled write", re); 517 } 518 } 519 } 520 521 @Override 522 public void finish() { 523 synchronized (mLock) { 524 // Start not called or finish called or destroyed - nothing to do. 525 if (!mStartReqeusted || mFinishRequested || mDestroyed) { 526 return; 527 } 528 529 mFinishRequested = true; 530 531 // When the current write or layout complete we 532 // will do the pending work. 533 if (mLastLayoutSpec != null || mLastWriteSpec != null) { 534 if (DEBUG) { 535 Log.i(LOG_TAG, "Waiting for current operation"); 536 } 537 return; 538 } 539 540 doPendingWorkLocked(); 541 } 542 } 543 544 @Override 545 public void onActivityPaused(Activity activity) { 546 /* do nothing */ 547 } 548 549 @Override 550 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 551 /* do nothing */ 552 } 553 554 @Override 555 public void onActivityStarted(Activity activity) { 556 /* do nothing */ 557 } 558 559 @Override 560 public void onActivityResumed(Activity activity) { 561 /* do nothing */ 562 } 563 564 @Override 565 public void onActivityStopped(Activity activity) { 566 /* do nothing */ 567 } 568 569 @Override 570 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 571 /* do nothing */ 572 } 573 574 @Override 575 public void onActivityDestroyed(Activity activity) { 576 // We really care only if the activity is being destroyed to 577 // notify the the print spooler so it can close the print dialog. 578 // Note the the spooler has a death recipient that observes if 579 // this process gets killed so we cover the case of onDestroy not 580 // being called due to this process being killed to reclaim memory. 581 final IPrintDocumentAdapterObserver observer; 582 synchronized (mLock) { 583 if (activity == mActivity) { 584 mDestroyed = true; 585 observer = mObserver; 586 clearLocked(); 587 } else { 588 observer = null; 589 activity = null; 590 } 591 } 592 if (observer != null) { 593 activity.getApplication().unregisterActivityLifecycleCallbacks( 594 PrintDocumentAdapterDelegate.this); 595 try { 596 observer.onDestroy(); 597 } catch (RemoteException re) { 598 Log.e(LOG_TAG, "Error announcing destroyed state", re); 599 } 600 } 601 } 602 603 private boolean isFinished() { 604 return mDocumentAdapter == null; 605 } 606 607 private void clearLocked() { 608 mActivity = null; 609 mDocumentAdapter = null; 610 mHandler = null; 611 mLayoutOrWriteCancellation = null; 612 mLastLayoutSpec = null; 613 if (mLastWriteSpec != null) { 614 IoUtils.closeQuietly(mLastWriteSpec.fd); 615 mLastWriteSpec = null; 616 } 617 } 618 619 private boolean cancelPreviousCancellableOperationLocked() { 620 if (mLayoutOrWriteCancellation != null) { 621 mLayoutOrWriteCancellation.cancel(); 622 if (DEBUG) { 623 Log.i(LOG_TAG, "Cancelling previous operation"); 624 } 625 return true; 626 } 627 return false; 628 } 629 630 private void doPendingWorkLocked() { 631 if (mStartReqeusted && !mStarted) { 632 mStarted = true; 633 mHandler.sendEmptyMessage(MyHandler.MSG_START); 634 } else if (mLastLayoutSpec != null) { 635 mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT); 636 } else if (mLastWriteSpec != null) { 637 mHandler.sendEmptyMessage(MyHandler.MSG_WRITE); 638 } else if (mFinishRequested && !mFinished) { 639 mFinished = true; 640 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); 641 } 642 } 643 644 private class LayoutSpec { 645 ILayoutResultCallback callback; 646 PrintAttributes oldAttributes; 647 PrintAttributes newAttributes; 648 Bundle metadata; 649 int sequence; 650 } 651 652 private class WriteSpec { 653 IWriteResultCallback callback; 654 PageRange[] pages; 655 ParcelFileDescriptor fd; 656 int sequence; 657 } 658 659 private final class MyHandler extends Handler { 660 public static final int MSG_START = 1; 661 public static final int MSG_LAYOUT = 2; 662 public static final int MSG_WRITE = 3; 663 public static final int MSG_FINISH = 4; 664 665 public MyHandler(Looper looper) { 666 super(looper, null, true); 667 } 668 669 @Override 670 public void handleMessage(Message message) { 671 if (isFinished()) { 672 return; 673 } 674 switch (message.what) { 675 case MSG_START: { 676 final PrintDocumentAdapter adapter; 677 synchronized (mLock) { 678 adapter = mDocumentAdapter; 679 } 680 if (adapter != null) { 681 adapter.onStart(); 682 } 683 } break; 684 685 case MSG_LAYOUT: { 686 final PrintDocumentAdapter adapter; 687 final CancellationSignal cancellation; 688 final LayoutSpec layoutSpec; 689 690 synchronized (mLock) { 691 adapter = mDocumentAdapter; 692 layoutSpec = mLastLayoutSpec; 693 mLastLayoutSpec = null; 694 cancellation = new CancellationSignal(); 695 mLayoutOrWriteCancellation = cancellation; 696 } 697 698 if (layoutSpec != null && adapter != null) { 699 if (DEBUG) { 700 Log.i(LOG_TAG, "Performing layout"); 701 } 702 adapter.onLayout(layoutSpec.oldAttributes, 703 layoutSpec.newAttributes, cancellation, 704 new MyLayoutResultCallback(layoutSpec.callback, 705 layoutSpec.sequence), layoutSpec.metadata); 706 } 707 } break; 708 709 case MSG_WRITE: { 710 final PrintDocumentAdapter adapter; 711 final CancellationSignal cancellation; 712 final WriteSpec writeSpec; 713 714 synchronized (mLock) { 715 adapter = mDocumentAdapter; 716 writeSpec = mLastWriteSpec; 717 mLastWriteSpec = null; 718 cancellation = new CancellationSignal(); 719 mLayoutOrWriteCancellation = cancellation; 720 } 721 722 if (writeSpec != null && adapter != null) { 723 if (DEBUG) { 724 Log.i(LOG_TAG, "Performing write"); 725 } 726 adapter.onWrite(writeSpec.pages, writeSpec.fd, 727 cancellation, new MyWriteResultCallback(writeSpec.callback, 728 writeSpec.fd, writeSpec.sequence)); 729 } 730 } break; 731 732 case MSG_FINISH: { 733 if (DEBUG) { 734 Log.i(LOG_TAG, "Performing finish"); 735 } 736 final PrintDocumentAdapter adapter; 737 final Activity activity; 738 synchronized (mLock) { 739 adapter = mDocumentAdapter; 740 activity = mActivity; 741 clearLocked(); 742 } 743 if (adapter != null) { 744 adapter.onFinish(); 745 } 746 if (activity != null) { 747 activity.getApplication().unregisterActivityLifecycleCallbacks( 748 PrintDocumentAdapterDelegate.this); 749 } 750 } break; 751 752 default: { 753 throw new IllegalArgumentException("Unknown message: " 754 + message.what); 755 } 756 } 757 } 758 } 759 760 private final class MyLayoutResultCallback extends LayoutResultCallback { 761 private ILayoutResultCallback mCallback; 762 private final int mSequence; 763 764 public MyLayoutResultCallback(ILayoutResultCallback callback, 765 int sequence) { 766 mCallback = callback; 767 mSequence = sequence; 768 } 769 770 @Override 771 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 772 if (info == null) { 773 throw new NullPointerException("document info cannot be null"); 774 } 775 final ILayoutResultCallback callback; 776 synchronized (mLock) { 777 callback = mCallback; 778 clearLocked(); 779 } 780 if (callback != null) { 781 try { 782 callback.onLayoutFinished(info, changed, mSequence); 783 } catch (RemoteException re) { 784 Log.e(LOG_TAG, "Error calling onLayoutFinished", re); 785 } 786 } 787 } 788 789 @Override 790 public void onLayoutFailed(CharSequence error) { 791 final ILayoutResultCallback callback; 792 synchronized (mLock) { 793 callback = mCallback; 794 clearLocked(); 795 } 796 if (callback != null) { 797 try { 798 callback.onLayoutFailed(error, mSequence); 799 } catch (RemoteException re) { 800 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 801 } 802 } 803 } 804 805 @Override 806 public void onLayoutCancelled() { 807 synchronized (mLock) { 808 clearLocked(); 809 } 810 } 811 812 private void clearLocked() { 813 mLayoutOrWriteCancellation = null; 814 mCallback = null; 815 doPendingWorkLocked(); 816 } 817 } 818 819 private final class MyWriteResultCallback extends WriteResultCallback { 820 private ParcelFileDescriptor mFd; 821 private int mSequence; 822 private IWriteResultCallback mCallback; 823 824 public MyWriteResultCallback(IWriteResultCallback callback, 825 ParcelFileDescriptor fd, int sequence) { 826 mFd = fd; 827 mSequence = sequence; 828 mCallback = callback; 829 } 830 831 @Override 832 public void onWriteFinished(PageRange[] pages) { 833 final IWriteResultCallback callback; 834 synchronized (mLock) { 835 callback = mCallback; 836 clearLocked(); 837 } 838 if (pages == null) { 839 throw new IllegalArgumentException("pages cannot be null"); 840 } 841 if (pages.length == 0) { 842 throw new IllegalArgumentException("pages cannot be empty"); 843 } 844 if (callback != null) { 845 try { 846 callback.onWriteFinished(pages, mSequence); 847 } catch (RemoteException re) { 848 Log.e(LOG_TAG, "Error calling onWriteFinished", re); 849 } 850 } 851 } 852 853 @Override 854 public void onWriteFailed(CharSequence error) { 855 final IWriteResultCallback callback; 856 synchronized (mLock) { 857 callback = mCallback; 858 clearLocked(); 859 } 860 if (callback != null) { 861 try { 862 callback.onWriteFailed(error, mSequence); 863 } catch (RemoteException re) { 864 Log.e(LOG_TAG, "Error calling onWriteFailed", re); 865 } 866 } 867 } 868 869 @Override 870 public void onWriteCancelled() { 871 synchronized (mLock) { 872 clearLocked(); 873 } 874 } 875 876 private void clearLocked() { 877 mLayoutOrWriteCancellation = null; 878 IoUtils.closeQuietly(mFd); 879 mCallback = null; 880 mFd = null; 881 doPendingWorkLocked(); 882 } 883 } 884 } 885 886 private static final class PrintJobStateChangeListenerWrapper extends 887 IPrintJobStateChangeListener.Stub { 888 private final WeakReference<PrintJobStateChangeListener> mWeakListener; 889 private final WeakReference<Handler> mWeakHandler; 890 891 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, 892 Handler handler) { 893 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener); 894 mWeakHandler = new WeakReference<Handler>(handler); 895 } 896 897 @Override 898 public void onPrintJobStateChanged(PrintJobId printJobId) { 899 Handler handler = mWeakHandler.get(); 900 PrintJobStateChangeListener listener = mWeakListener.get(); 901 if (handler != null && listener != null) { 902 SomeArgs args = SomeArgs.obtain(); 903 args.arg1 = this; 904 args.arg2 = printJobId; 905 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED, 906 args).sendToTarget(); 907 } 908 } 909 910 public void destroy() { 911 mWeakListener.clear(); 912 } 913 914 public PrintJobStateChangeListener getListener() { 915 return mWeakListener.get(); 916 } 917 } 918} 919