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