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.app.Activity; 20 import android.app.Application.ActivityLifecycleCallbacks; 21 import android.content.Context; 22 import android.content.IntentSender; 23 import android.content.IntentSender.SendIntentException; 24 import android.os.Bundle; 25 import android.os.CancellationSignal; 26 import android.os.Handler; 27 import android.os.ICancellationSignal; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.ParcelFileDescriptor; 31 import android.os.RemoteException; 32 import android.print.PrintDocumentAdapter.LayoutResultCallback; 33 import android.print.PrintDocumentAdapter.WriteResultCallback; 34 import android.printservice.PrintServiceInfo; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.Log; 38 39 import com.android.internal.os.SomeArgs; 40 41 import libcore.io.IoUtils; 42 43 import java.lang.ref.WeakReference; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.List; 48 import java.util.Map; 49 50 /** 51 * System level service for accessing the printing capabilities of the platform. 52 * <p> 53 * To obtain a handle to the print manager do the following: 54 * </p> 55 * 56 * <pre> 57 * PrintManager printManager = 58 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); 59 * </pre> 60 * 61 * <h3>Print mechanics</h3> 62 * <p> 63 * The key idea behind printing on the platform is that the content to be printed 64 * should be laid out for the currently selected print options resulting in an 65 * optimized output and higher user satisfaction. To achieve this goal the platform 66 * declares a contract that the printing application has to follow which is defined 67 * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that 68 * when the user selects some options from the print UI that may affect the way 69 * content is laid out, for example page size, the application receives a callback 70 * allowing it to layout the content to better fit these new constraints. After a 71 * layout pass the system may ask the application to render one or more pages one 72 * or more times. For example, an application may produce a single column list for 73 * smaller page sizes and a multi-column table for larger page sizes. 74 * </p> 75 * <h3>Print jobs</h3> 76 * <p> 77 * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter, 78 * PrintAttributes)} from an activity which results in bringing up the system print 79 * UI. Once the print UI is up, when the user changes a selected print option that 80 * affects the way content is laid out the system starts to interact with the 81 * application following the mechanics described the section above. 82 * </p> 83 * <p> 84 * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link 85 * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started}, 86 * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED 87 * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link 88 * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated 89 * system spooler until they are handled which is they are cancelled or completed. 90 * Active print jobs, ones that are not cancelled or completed, are considered failed 91 * if the device reboots as the new boot may be after a very long time. The user may 92 * choose to restart such print jobs. Once a print job is queued all relevant content 93 * is stored in the system spooler and its lifecycle becomes detached from this of 94 * the application that created it. 95 * </p> 96 * <p> 97 * An applications can query the print spooler for current print jobs it created 98 * but not print jobs created by other applications. 99 * </p> 100 * 101 * @see PrintJob 102 * @see PrintJobInfo 103 */ 104 public final class PrintManager { 105 106 private static final String LOG_TAG = "PrintManager"; 107 108 private static final boolean DEBUG = false; 109 110 private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1; 111 112 /** 113 * The action for launching the print dialog activity. 114 * 115 * @hide 116 */ 117 public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG"; 118 119 /** 120 * Extra with the intent for starting the print dialog. 121 * <p> 122 * <strong>Type:</strong> {@link android.content.IntentSender} 123 * </p> 124 * 125 * @hide 126 */ 127 public static final String EXTRA_PRINT_DIALOG_INTENT = 128 "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT"; 129 130 /** 131 * Extra with a print job. 132 * <p> 133 * <strong>Type:</strong> {@link android.print.PrintJobInfo} 134 * </p> 135 * 136 * @hide 137 */ 138 public static final String EXTRA_PRINT_JOB = 139 "android.print.intent.extra.EXTRA_PRINT_JOB"; 140 141 /** 142 * Extra with the print document adapter to be printed. 143 * <p> 144 * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter} 145 * </p> 146 * 147 * @hide 148 */ 149 public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = 150 "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER"; 151 152 /** @hide */ 153 public static final int APP_ID_ANY = -2; 154 155 private final Context mContext; 156 157 private final IPrintManager mService; 158 159 private final int mUserId; 160 161 private final int mAppId; 162 163 private final Handler mHandler; 164 165 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners; 166 167 /** @hide */ 168 public interface PrintJobStateChangeListener { 169 170 /** 171 * Callback notifying that a print job state changed. 172 * 173 * @param printJobId The print job id. 174 */ onPrintJobStateChanged(PrintJobId printJobId)175 public void onPrintJobStateChanged(PrintJobId printJobId); 176 } 177 178 /** 179 * Creates a new instance. 180 * 181 * @param context The current context in which to operate. 182 * @param service The backing system service. 183 * @hide 184 */ PrintManager(Context context, IPrintManager service, int userId, int appId)185 public PrintManager(Context context, IPrintManager service, int userId, int appId) { 186 mContext = context; 187 mService = service; 188 mUserId = userId; 189 mAppId = appId; 190 mHandler = new Handler(context.getMainLooper(), null, false) { 191 @Override 192 public void handleMessage(Message message) { 193 switch (message.what) { 194 case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: { 195 SomeArgs args = (SomeArgs) message.obj; 196 PrintJobStateChangeListenerWrapper wrapper = 197 (PrintJobStateChangeListenerWrapper) args.arg1; 198 PrintJobStateChangeListener listener = wrapper.getListener(); 199 if (listener != null) { 200 PrintJobId printJobId = (PrintJobId) args.arg2; 201 listener.onPrintJobStateChanged(printJobId); 202 } 203 args.recycle(); 204 } break; 205 } 206 } 207 }; 208 } 209 210 /** 211 * Creates an instance that can access all print jobs. 212 * 213 * @param userId The user id for which to get all print jobs. 214 * @return An instance if the caller has the permission to access all print 215 * jobs, null otherwise. 216 * @hide 217 */ getGlobalPrintManagerForUser(int userId)218 public PrintManager getGlobalPrintManagerForUser(int userId) { 219 if (mService == null) { 220 Log.w(LOG_TAG, "Feature android.software.print not available"); 221 return null; 222 } 223 return new PrintManager(mContext, mService, userId, APP_ID_ANY); 224 } 225 getPrintJobInfo(PrintJobId printJobId)226 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { 227 try { 228 return mService.getPrintJobInfo(printJobId, mAppId, mUserId); 229 } catch (RemoteException re) { 230 Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); 231 } 232 return null; 233 } 234 235 /** 236 * Adds a listener for observing the state of print jobs. 237 * 238 * @param listener The listener to add. 239 * @hide 240 */ addPrintJobStateChangeListener(PrintJobStateChangeListener listener)241 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) { 242 if (mService == null) { 243 Log.w(LOG_TAG, "Feature android.software.print not available"); 244 return; 245 } 246 if (mPrintJobStateChangeListeners == null) { 247 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener, 248 PrintJobStateChangeListenerWrapper>(); 249 } 250 PrintJobStateChangeListenerWrapper wrappedListener = 251 new PrintJobStateChangeListenerWrapper(listener, mHandler); 252 try { 253 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId); 254 mPrintJobStateChangeListeners.put(listener, wrappedListener); 255 } catch (RemoteException re) { 256 Log.e(LOG_TAG, "Error adding print job state change listener", re); 257 } 258 } 259 260 /** 261 * Removes a listener for observing the state of print jobs. 262 * 263 * @param listener The listener to remove. 264 * @hide 265 */ removePrintJobStateChangeListener(PrintJobStateChangeListener listener)266 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) { 267 if (mService == null) { 268 Log.w(LOG_TAG, "Feature android.software.print not available"); 269 return; 270 } 271 if (mPrintJobStateChangeListeners == null) { 272 return; 273 } 274 PrintJobStateChangeListenerWrapper wrappedListener = 275 mPrintJobStateChangeListeners.remove(listener); 276 if (wrappedListener == null) { 277 return; 278 } 279 if (mPrintJobStateChangeListeners.isEmpty()) { 280 mPrintJobStateChangeListeners = null; 281 } 282 wrappedListener.destroy(); 283 try { 284 mService.removePrintJobStateChangeListener(wrappedListener, mUserId); 285 } catch (RemoteException re) { 286 Log.e(LOG_TAG, "Error removing print job state change listener", re); 287 } 288 } 289 290 /** 291 * Gets a print job given its id. 292 * 293 * @return The print job list. 294 * @see PrintJob 295 * @hide 296 */ getPrintJob(PrintJobId printJobId)297 public PrintJob getPrintJob(PrintJobId printJobId) { 298 if (mService == null) { 299 Log.w(LOG_TAG, "Feature android.software.print not available"); 300 return null; 301 } 302 try { 303 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId); 304 if (printJob != null) { 305 return new PrintJob(printJob, this); 306 } 307 } catch (RemoteException re) { 308 Log.e(LOG_TAG, "Error getting print job", re); 309 } 310 return null; 311 } 312 313 /** 314 * Gets the print jobs for this application. 315 * 316 * @return The print job list. 317 * @see PrintJob 318 */ getPrintJobs()319 public List<PrintJob> getPrintJobs() { 320 if (mService == null) { 321 Log.w(LOG_TAG, "Feature android.software.print not available"); 322 return Collections.emptyList(); 323 } 324 try { 325 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); 326 if (printJobInfos == null) { 327 return Collections.emptyList(); 328 } 329 final int printJobCount = printJobInfos.size(); 330 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); 331 for (int i = 0; i < printJobCount; i++) { 332 printJobs.add(new PrintJob(printJobInfos.get(i), this)); 333 } 334 return printJobs; 335 } catch (RemoteException re) { 336 Log.e(LOG_TAG, "Error getting print jobs", re); 337 } 338 return Collections.emptyList(); 339 } 340 cancelPrintJob(PrintJobId printJobId)341 void cancelPrintJob(PrintJobId printJobId) { 342 if (mService == null) { 343 Log.w(LOG_TAG, "Feature android.software.print not available"); 344 return; 345 } 346 try { 347 mService.cancelPrintJob(printJobId, mAppId, mUserId); 348 } catch (RemoteException re) { 349 Log.e(LOG_TAG, "Error canceling a print job: " + printJobId, re); 350 } 351 } 352 restartPrintJob(PrintJobId printJobId)353 void restartPrintJob(PrintJobId printJobId) { 354 if (mService == null) { 355 Log.w(LOG_TAG, "Feature android.software.print not available"); 356 return; 357 } 358 try { 359 mService.restartPrintJob(printJobId, mAppId, mUserId); 360 } catch (RemoteException re) { 361 Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re); 362 } 363 } 364 365 /** 366 * Creates a print job for printing a {@link PrintDocumentAdapter} with 367 * default print attributes. 368 * <p> 369 * Calling this method brings the print UI allowing the user to customize 370 * the print job and returns a {@link PrintJob} object without waiting for the 371 * user to customize or confirm the print job. The returned print job instance 372 * is in a {@link PrintJobInfo#STATE_CREATED created} state. 373 * <p> 374 * This method can be called only from an {@link Activity}. The rationale is that 375 * printing from a service will create an inconsistent user experience as the print 376 * UI would appear without any context. 377 * </p> 378 * <p> 379 * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if 380 * your activity is finished. The rationale is that once the activity that 381 * initiated printing is finished, the provided adapter may be in an inconsistent 382 * state as it may depend on the UI presented by the activity. 383 * </p> 384 * <p> 385 * The default print attributes are a hint to the system how the data is to 386 * be printed. For example, a photo editor may look at the photo aspect ratio 387 * to determine the default orientation and provide a hint whether the printing 388 * should be in portrait or landscape. The system will do a best effort to 389 * selected the hinted options in the print dialog, given the current printer 390 * supports them. 391 * </p> 392 * <p> 393 * <strong>Note:</strong> Calling this method will bring the print dialog and 394 * the system will connect to the provided {@link PrintDocumentAdapter}. If a 395 * configuration change occurs that you application does not handle, for example 396 * a rotation change, the system will drop the connection to the adapter as the 397 * activity has to be recreated and the old adapter may be invalid in this context, 398 * hence a new adapter instance is required. As a consequence, if your activity 399 * does not handle configuration changes (default behavior), you have to save the 400 * state that you were printing and call this method again when your activity 401 * is recreated. 402 * </p> 403 * 404 * @param printJobName A name for the new print job which is shown to the user. 405 * @param documentAdapter An adapter that emits the document to print. 406 * @param attributes The default print job attributes or <code>null</code>. 407 * @return The created print job on success or null on failure. 408 * @throws IllegalStateException If not called from an {@link Activity}. 409 * @throws IllegalArgumentException If the print job name is empty or the 410 * document adapter is null. 411 * 412 * @see PrintJob 413 */ print(String printJobName, PrintDocumentAdapter documentAdapter, PrintAttributes attributes)414 public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, 415 PrintAttributes attributes) { 416 if (mService == null) { 417 Log.w(LOG_TAG, "Feature android.software.print not available"); 418 return null; 419 } 420 if (!(mContext instanceof Activity)) { 421 throw new IllegalStateException("Can print only from an activity"); 422 } 423 if (TextUtils.isEmpty(printJobName)) { 424 throw new IllegalArgumentException("printJobName cannot be empty"); 425 } 426 if (documentAdapter == null) { 427 throw new IllegalArgumentException("documentAdapter cannot be null"); 428 } 429 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate( 430 (Activity) mContext, documentAdapter); 431 try { 432 Bundle result = mService.print(printJobName, delegate, 433 attributes, mContext.getPackageName(), mAppId, mUserId); 434 if (result != null) { 435 PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB); 436 IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT); 437 if (printJob == null || intent == null) { 438 return null; 439 } 440 try { 441 mContext.startIntentSender(intent, null, 0, 0, 0); 442 return new PrintJob(printJob, this); 443 } catch (SendIntentException sie) { 444 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); 445 } 446 } 447 } catch (RemoteException re) { 448 Log.e(LOG_TAG, "Error creating a print job", re); 449 } 450 return null; 451 } 452 453 /** 454 * Gets the list of enabled print services. 455 * 456 * @return The enabled service list or an empty list. 457 * @hide 458 */ getEnabledPrintServices()459 public List<PrintServiceInfo> getEnabledPrintServices() { 460 if (mService == null) { 461 Log.w(LOG_TAG, "Feature android.software.print not available"); 462 return Collections.emptyList(); 463 } 464 try { 465 List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId); 466 if (enabledServices != null) { 467 return enabledServices; 468 } 469 } catch (RemoteException re) { 470 Log.e(LOG_TAG, "Error getting the enabled print services", re); 471 } 472 return Collections.emptyList(); 473 } 474 475 /** 476 * Gets the list of installed print services. 477 * 478 * @return The installed service list or an empty list. 479 * @hide 480 */ getInstalledPrintServices()481 public List<PrintServiceInfo> getInstalledPrintServices() { 482 if (mService == null) { 483 Log.w(LOG_TAG, "Feature android.software.print not available"); 484 return Collections.emptyList(); 485 } 486 try { 487 List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId); 488 if (installedServices != null) { 489 return installedServices; 490 } 491 } catch (RemoteException re) { 492 Log.e(LOG_TAG, "Error getting the installed print services", re); 493 } 494 return Collections.emptyList(); 495 } 496 497 /** 498 * @hide 499 */ createPrinterDiscoverySession()500 public PrinterDiscoverySession createPrinterDiscoverySession() { 501 if (mService == null) { 502 Log.w(LOG_TAG, "Feature android.software.print not available"); 503 return null; 504 } 505 return new PrinterDiscoverySession(mService, mContext, mUserId); 506 } 507 508 private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub 509 implements ActivityLifecycleCallbacks { 510 private final Object mLock = new Object(); 511 512 private Activity mActivity; // Strong reference OK - cleared in destroy 513 514 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy 515 516 private Handler mHandler; // Strong reference OK - cleared in destroy 517 518 private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy 519 520 private DestroyableCallback mPendingCallback; 521 PrintDocumentAdapterDelegate(Activity activity, PrintDocumentAdapter documentAdapter)522 public PrintDocumentAdapterDelegate(Activity activity, 523 PrintDocumentAdapter documentAdapter) { 524 mActivity = activity; 525 mDocumentAdapter = documentAdapter; 526 mHandler = new MyHandler(mActivity.getMainLooper()); 527 mActivity.getApplication().registerActivityLifecycleCallbacks(this); 528 } 529 530 @Override setObserver(IPrintDocumentAdapterObserver observer)531 public void setObserver(IPrintDocumentAdapterObserver observer) { 532 final boolean destroyed; 533 synchronized (mLock) { 534 mObserver = observer; 535 destroyed = isDestroyedLocked(); 536 } 537 538 if (destroyed && observer != null) { 539 try { 540 observer.onDestroy(); 541 } catch (RemoteException re) { 542 Log.e(LOG_TAG, "Error announcing destroyed state", re); 543 } 544 } 545 } 546 547 @Override start()548 public void start() { 549 synchronized (mLock) { 550 // If destroyed the handler is null. 551 if (!isDestroyedLocked()) { 552 mHandler.obtainMessage(MyHandler.MSG_ON_START, 553 mDocumentAdapter).sendToTarget(); 554 } 555 } 556 } 557 558 @Override layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, ILayoutResultCallback callback, Bundle metadata, int sequence)559 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, 560 ILayoutResultCallback callback, Bundle metadata, int sequence) { 561 562 ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); 563 try { 564 callback.onLayoutStarted(cancellationTransport, sequence); 565 } catch (RemoteException re) { 566 // The spooler is dead - can't recover. 567 Log.e(LOG_TAG, "Error notifying for layout start", re); 568 return; 569 } 570 571 synchronized (mLock) { 572 // If destroyed the handler is null. 573 if (isDestroyedLocked()) { 574 return; 575 } 576 577 CancellationSignal cancellationSignal = CancellationSignal.fromTransport( 578 cancellationTransport); 579 580 SomeArgs args = SomeArgs.obtain(); 581 args.arg1 = mDocumentAdapter; 582 args.arg2 = oldAttributes; 583 args.arg3 = newAttributes; 584 args.arg4 = cancellationSignal; 585 args.arg5 = new MyLayoutResultCallback(callback, sequence); 586 args.arg6 = metadata; 587 588 mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget(); 589 } 590 } 591 592 @Override write(PageRange[] pages, ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence)593 public void write(PageRange[] pages, ParcelFileDescriptor fd, 594 IWriteResultCallback callback, int sequence) { 595 596 ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); 597 try { 598 callback.onWriteStarted(cancellationTransport, sequence); 599 } catch (RemoteException re) { 600 // The spooler is dead - can't recover. 601 Log.e(LOG_TAG, "Error notifying for write start", re); 602 return; 603 } 604 605 synchronized (mLock) { 606 // If destroyed the handler is null. 607 if (isDestroyedLocked()) { 608 return; 609 } 610 611 CancellationSignal cancellationSignal = CancellationSignal.fromTransport( 612 cancellationTransport); 613 614 SomeArgs args = SomeArgs.obtain(); 615 args.arg1 = mDocumentAdapter; 616 args.arg2 = pages; 617 args.arg3 = fd; 618 args.arg4 = cancellationSignal; 619 args.arg5 = new MyWriteResultCallback(callback, fd, sequence); 620 621 mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget(); 622 } 623 } 624 625 @Override finish()626 public void finish() { 627 synchronized (mLock) { 628 // If destroyed the handler is null. 629 if (!isDestroyedLocked()) { 630 mHandler.obtainMessage(MyHandler.MSG_ON_FINISH, 631 mDocumentAdapter).sendToTarget(); 632 } 633 } 634 } 635 636 @Override kill(String reason)637 public void kill(String reason) { 638 synchronized (mLock) { 639 // If destroyed the handler is null. 640 if (!isDestroyedLocked()) { 641 mHandler.obtainMessage(MyHandler.MSG_ON_KILL, 642 reason).sendToTarget(); 643 } 644 } 645 } 646 647 @Override onActivityPaused(Activity activity)648 public void onActivityPaused(Activity activity) { 649 /* do nothing */ 650 } 651 652 @Override onActivityCreated(Activity activity, Bundle savedInstanceState)653 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 654 /* do nothing */ 655 } 656 657 @Override onActivityStarted(Activity activity)658 public void onActivityStarted(Activity activity) { 659 /* do nothing */ 660 } 661 662 @Override onActivityResumed(Activity activity)663 public void onActivityResumed(Activity activity) { 664 /* do nothing */ 665 } 666 667 @Override onActivityStopped(Activity activity)668 public void onActivityStopped(Activity activity) { 669 /* do nothing */ 670 } 671 672 @Override onActivitySaveInstanceState(Activity activity, Bundle outState)673 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 674 /* do nothing */ 675 } 676 677 @Override onActivityDestroyed(Activity activity)678 public void onActivityDestroyed(Activity activity) { 679 // We really care only if the activity is being destroyed to 680 // notify the the print spooler so it can close the print dialog. 681 // Note the the spooler has a death recipient that observes if 682 // this process gets killed so we cover the case of onDestroy not 683 // being called due to this process being killed to reclaim memory. 684 IPrintDocumentAdapterObserver observer = null; 685 synchronized (mLock) { 686 if (activity == mActivity) { 687 observer = mObserver; 688 destroyLocked(); 689 } 690 } 691 if (observer != null) { 692 try { 693 observer.onDestroy(); 694 } catch (RemoteException re) { 695 Log.e(LOG_TAG, "Error announcing destroyed state", re); 696 } 697 } 698 } 699 isDestroyedLocked()700 private boolean isDestroyedLocked() { 701 return (mActivity == null); 702 } 703 destroyLocked()704 private void destroyLocked() { 705 mActivity.getApplication().unregisterActivityLifecycleCallbacks( 706 PrintDocumentAdapterDelegate.this); 707 mActivity = null; 708 709 mDocumentAdapter = null; 710 711 // This method is only called from the main thread, so 712 // clearing the messages guarantees that any time a 713 // message is handled we are not in a destroyed state. 714 mHandler.removeMessages(MyHandler.MSG_ON_START); 715 mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT); 716 mHandler.removeMessages(MyHandler.MSG_ON_WRITE); 717 mHandler.removeMessages(MyHandler.MSG_ON_FINISH); 718 mHandler = null; 719 720 mObserver = null; 721 722 if (mPendingCallback != null) { 723 mPendingCallback.destroy(); 724 mPendingCallback = null; 725 } 726 } 727 728 private final class MyHandler extends Handler { 729 public static final int MSG_ON_START = 1; 730 public static final int MSG_ON_LAYOUT = 2; 731 public static final int MSG_ON_WRITE = 3; 732 public static final int MSG_ON_FINISH = 4; 733 public static final int MSG_ON_KILL = 5; 734 MyHandler(Looper looper)735 public MyHandler(Looper looper) { 736 super(looper, null, true); 737 } 738 739 @Override handleMessage(Message message)740 public void handleMessage(Message message) { 741 switch (message.what) { 742 case MSG_ON_START: { 743 if (DEBUG) { 744 Log.i(LOG_TAG, "onStart()"); 745 } 746 747 ((PrintDocumentAdapter) message.obj).onStart(); 748 } break; 749 750 case MSG_ON_LAYOUT: { 751 SomeArgs args = (SomeArgs) message.obj; 752 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; 753 PrintAttributes oldAttributes = (PrintAttributes) args.arg2; 754 PrintAttributes newAttributes = (PrintAttributes) args.arg3; 755 CancellationSignal cancellation = (CancellationSignal) args.arg4; 756 LayoutResultCallback callback = (LayoutResultCallback) args.arg5; 757 Bundle metadata = (Bundle) args.arg6; 758 args.recycle(); 759 760 if (DEBUG) { 761 StringBuilder builder = new StringBuilder(); 762 builder.append("PrintDocumentAdapter#onLayout() {\n"); 763 builder.append("\n oldAttributes:").append(oldAttributes); 764 builder.append("\n newAttributes:").append(newAttributes); 765 builder.append("\n preview:").append(metadata.getBoolean( 766 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW)); 767 builder.append("\n}"); 768 Log.i(LOG_TAG, builder.toString()); 769 } 770 771 adapter.onLayout(oldAttributes, newAttributes, cancellation, 772 callback, metadata); 773 } break; 774 775 case MSG_ON_WRITE: { 776 SomeArgs args = (SomeArgs) message.obj; 777 PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; 778 PageRange[] pages = (PageRange[]) args.arg2; 779 ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3; 780 CancellationSignal cancellation = (CancellationSignal) args.arg4; 781 WriteResultCallback callback = (WriteResultCallback) args.arg5; 782 args.recycle(); 783 784 if (DEBUG) { 785 StringBuilder builder = new StringBuilder(); 786 builder.append("PrintDocumentAdapter#onWrite() {\n"); 787 builder.append("\n pages:").append(Arrays.toString(pages)); 788 builder.append("\n}"); 789 Log.i(LOG_TAG, builder.toString()); 790 } 791 792 adapter.onWrite(pages, fd, cancellation, callback); 793 } break; 794 795 case MSG_ON_FINISH: { 796 if (DEBUG) { 797 Log.i(LOG_TAG, "onFinish()"); 798 } 799 800 ((PrintDocumentAdapter) message.obj).onFinish(); 801 802 // Done printing, so destroy this instance as it 803 // should not be used anymore. 804 synchronized (mLock) { 805 destroyLocked(); 806 } 807 } break; 808 809 case MSG_ON_KILL: { 810 if (DEBUG) { 811 Log.i(LOG_TAG, "onKill()"); 812 } 813 814 String reason = (String) message.obj; 815 throw new RuntimeException(reason); 816 } 817 818 default: { 819 throw new IllegalArgumentException("Unknown message: " 820 + message.what); 821 } 822 } 823 } 824 } 825 826 private interface DestroyableCallback { destroy()827 public void destroy(); 828 } 829 830 private final class MyLayoutResultCallback extends LayoutResultCallback 831 implements DestroyableCallback { 832 private ILayoutResultCallback mCallback; 833 private final int mSequence; 834 MyLayoutResultCallback(ILayoutResultCallback callback, int sequence)835 public MyLayoutResultCallback(ILayoutResultCallback callback, 836 int sequence) { 837 mCallback = callback; 838 mSequence = sequence; 839 } 840 841 @Override onLayoutFinished(PrintDocumentInfo info, boolean changed)842 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 843 final ILayoutResultCallback callback; 844 synchronized (mLock) { 845 callback = mCallback; 846 } 847 848 // If the callback is null we are destroyed. 849 if (callback == null) { 850 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 851 + "finish the printing activity before print completion " 852 + "or did you invoke a callback after finish?"); 853 return; 854 } 855 856 try { 857 if (info == null) { 858 throw new NullPointerException("document info cannot be null"); 859 } 860 861 try { 862 callback.onLayoutFinished(info, changed, mSequence); 863 } catch (RemoteException re) { 864 Log.e(LOG_TAG, "Error calling onLayoutFinished", re); 865 } 866 } finally { 867 destroy(); 868 } 869 } 870 871 @Override onLayoutFailed(CharSequence error)872 public void onLayoutFailed(CharSequence error) { 873 final ILayoutResultCallback callback; 874 synchronized (mLock) { 875 callback = mCallback; 876 } 877 878 // If the callback is null we are destroyed. 879 if (callback == null) { 880 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 881 + "finish the printing activity before print completion " 882 + "or did you invoke a callback after finish?"); 883 return; 884 } 885 886 try { 887 callback.onLayoutFailed(error, mSequence); 888 } catch (RemoteException re) { 889 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 890 } finally { 891 destroy(); 892 } 893 } 894 895 @Override onLayoutCancelled()896 public void onLayoutCancelled() { 897 final ILayoutResultCallback callback; 898 synchronized (mLock) { 899 callback = mCallback; 900 } 901 902 // If the callback is null we are destroyed. 903 if (callback == null) { 904 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 905 + "finish the printing activity before print completion " 906 + "or did you invoke a callback after finish?"); 907 return; 908 } 909 910 try { 911 callback.onLayoutCanceled(mSequence); 912 } catch (RemoteException re) { 913 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 914 } finally { 915 destroy(); 916 } 917 } 918 919 @Override destroy()920 public void destroy() { 921 synchronized (mLock) { 922 mCallback = null; 923 mPendingCallback = null; 924 } 925 } 926 } 927 928 private final class MyWriteResultCallback extends WriteResultCallback 929 implements DestroyableCallback { 930 private ParcelFileDescriptor mFd; 931 private IWriteResultCallback mCallback; 932 private final int mSequence; 933 MyWriteResultCallback(IWriteResultCallback callback, ParcelFileDescriptor fd, int sequence)934 public MyWriteResultCallback(IWriteResultCallback callback, 935 ParcelFileDescriptor fd, int sequence) { 936 mFd = fd; 937 mSequence = sequence; 938 mCallback = callback; 939 } 940 941 @Override onWriteFinished(PageRange[] pages)942 public void onWriteFinished(PageRange[] pages) { 943 final IWriteResultCallback callback; 944 synchronized (mLock) { 945 callback = mCallback; 946 } 947 948 // If the callback is null we are destroyed. 949 if (callback == null) { 950 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 951 + "finish the printing activity before print completion " 952 + "or did you invoke a callback after finish?"); 953 return; 954 } 955 956 try { 957 if (pages == null) { 958 throw new IllegalArgumentException("pages cannot be null"); 959 } 960 if (pages.length == 0) { 961 throw new IllegalArgumentException("pages cannot be empty"); 962 } 963 964 try { 965 callback.onWriteFinished(pages, mSequence); 966 } catch (RemoteException re) { 967 Log.e(LOG_TAG, "Error calling onWriteFinished", re); 968 } 969 } finally { 970 destroy(); 971 } 972 } 973 974 @Override onWriteFailed(CharSequence error)975 public void onWriteFailed(CharSequence error) { 976 final IWriteResultCallback callback; 977 synchronized (mLock) { 978 callback = mCallback; 979 } 980 981 // If the callback is null we are destroyed. 982 if (callback == null) { 983 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 984 + "finish the printing activity before print completion " 985 + "or did you invoke a callback after finish?"); 986 return; 987 } 988 989 try { 990 callback.onWriteFailed(error, mSequence); 991 } catch (RemoteException re) { 992 Log.e(LOG_TAG, "Error calling onWriteFailed", re); 993 } finally { 994 destroy(); 995 } 996 } 997 998 @Override onWriteCancelled()999 public void onWriteCancelled() { 1000 final IWriteResultCallback callback; 1001 synchronized (mLock) { 1002 callback = mCallback; 1003 } 1004 1005 // If the callback is null we are destroyed. 1006 if (callback == null) { 1007 Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " 1008 + "finish the printing activity before print completion " 1009 + "or did you invoke a callback after finish?"); 1010 return; 1011 } 1012 1013 try { 1014 callback.onWriteCanceled(mSequence); 1015 } catch (RemoteException re) { 1016 Log.e(LOG_TAG, "Error calling onWriteCanceled", re); 1017 } finally { 1018 destroy(); 1019 } 1020 } 1021 1022 @Override destroy()1023 public void destroy() { 1024 synchronized (mLock) { 1025 IoUtils.closeQuietly(mFd); 1026 mCallback = null; 1027 mFd = null; 1028 mPendingCallback = null; 1029 } 1030 } 1031 } 1032 } 1033 1034 private static final class PrintJobStateChangeListenerWrapper extends 1035 IPrintJobStateChangeListener.Stub { 1036 private final WeakReference<PrintJobStateChangeListener> mWeakListener; 1037 private final WeakReference<Handler> mWeakHandler; 1038 PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, Handler handler)1039 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, 1040 Handler handler) { 1041 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener); 1042 mWeakHandler = new WeakReference<Handler>(handler); 1043 } 1044 1045 @Override onPrintJobStateChanged(PrintJobId printJobId)1046 public void onPrintJobStateChanged(PrintJobId printJobId) { 1047 Handler handler = mWeakHandler.get(); 1048 PrintJobStateChangeListener listener = mWeakListener.get(); 1049 if (handler != null && listener != null) { 1050 SomeArgs args = SomeArgs.obtain(); 1051 args.arg1 = this; 1052 args.arg2 = printJobId; 1053 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED, 1054 args).sendToTarget(); 1055 } 1056 } 1057 destroy()1058 public void destroy() { 1059 mWeakListener.clear(); 1060 } 1061 getListener()1062 public PrintJobStateChangeListener getListener() { 1063 return mWeakListener.get(); 1064 } 1065 } 1066 } 1067