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