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