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