1 /* 2 * Copyright (C) 2014 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 com.android.printspooler.model; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.net.Uri; 22 import android.os.AsyncTask; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.IBinder.DeathRecipient; 26 import android.os.ICancellationSignal; 27 import android.os.Looper; 28 import android.os.Message; 29 import android.os.ParcelFileDescriptor; 30 import android.os.RemoteException; 31 import android.print.ILayoutResultCallback; 32 import android.print.IPrintDocumentAdapter; 33 import android.print.IPrintDocumentAdapterObserver; 34 import android.print.IWriteResultCallback; 35 import android.print.PageRange; 36 import android.print.PrintAttributes; 37 import android.print.PrintDocumentAdapter; 38 import android.print.PrintDocumentInfo; 39 import android.util.Log; 40 41 import com.android.internal.util.function.pooled.PooledLambda; 42 import com.android.printspooler.R; 43 import com.android.printspooler.util.PageRangeUtils; 44 45 import libcore.io.IoUtils; 46 47 import java.io.File; 48 import java.io.FileInputStream; 49 import java.io.FileOutputStream; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.io.OutputStream; 53 import java.lang.ref.WeakReference; 54 import java.util.Arrays; 55 56 public final class RemotePrintDocument { 57 private static final String LOG_TAG = "RemotePrintDocument"; 58 59 private static final boolean DEBUG = false; 60 61 private static final long FORCE_CANCEL_TIMEOUT = 1000; // ms 62 63 private static final int STATE_INITIAL = 0; 64 private static final int STATE_STARTED = 1; 65 private static final int STATE_UPDATING = 2; 66 private static final int STATE_UPDATED = 3; 67 private static final int STATE_FAILED = 4; 68 private static final int STATE_FINISHED = 5; 69 private static final int STATE_CANCELING = 6; 70 private static final int STATE_CANCELED = 7; 71 private static final int STATE_DESTROYED = 8; 72 73 private final Context mContext; 74 75 private final RemotePrintDocumentInfo mDocumentInfo; 76 private final UpdateSpec mUpdateSpec = new UpdateSpec(); 77 78 private final Looper mLooper; 79 private final IPrintDocumentAdapter mPrintDocumentAdapter; 80 private final RemoteAdapterDeathObserver mAdapterDeathObserver; 81 82 private final UpdateResultCallbacks mUpdateCallbacks; 83 84 private final CommandDoneCallback mCommandResultCallback = 85 new CommandDoneCallback() { 86 @Override 87 public void onDone() { 88 if (mCurrentCommand.isCompleted()) { 89 if (mCurrentCommand instanceof LayoutCommand) { 90 // If there is a next command after a layout is done, then another 91 // update was issued and the next command is another layout, so we 92 // do nothing. However, if there is no next command we may need to 93 // ask for some pages given we do not already have them or we do 94 // but the content has changed. 95 if (mNextCommand == null) { 96 if (mUpdateSpec.pages != null && (mDocumentInfo.changed 97 || mDocumentInfo.pagesWrittenToFile == null 98 || (mDocumentInfo.info.getPageCount() 99 != PrintDocumentInfo.PAGE_COUNT_UNKNOWN 100 && !PageRangeUtils.contains(mDocumentInfo.pagesWrittenToFile, 101 mUpdateSpec.pages, mDocumentInfo.info.getPageCount())))) { 102 mNextCommand = new WriteCommand(mContext, mLooper, 103 mPrintDocumentAdapter, mDocumentInfo, 104 mDocumentInfo.info.getPageCount(), mUpdateSpec.pages, 105 mDocumentInfo.fileProvider, mCommandResultCallback); 106 } else { 107 if (mUpdateSpec.pages != null) { 108 // If we have the requested pages, update which ones to be printed. 109 mDocumentInfo.pagesInFileToPrint = 110 PageRangeUtils.computeWhichPagesInFileToPrint( 111 mUpdateSpec.pages, mDocumentInfo.pagesWrittenToFile, 112 mDocumentInfo.info.getPageCount()); 113 } 114 // Notify we are done. 115 mState = STATE_UPDATED; 116 mDocumentInfo.updated = true; 117 notifyUpdateCompleted(); 118 } 119 } 120 } else { 121 // We always notify after a write. 122 mState = STATE_UPDATED; 123 mDocumentInfo.updated = true; 124 notifyUpdateCompleted(); 125 } 126 runPendingCommand(); 127 } else if (mCurrentCommand.isFailed()) { 128 mState = STATE_FAILED; 129 CharSequence error = mCurrentCommand.getError(); 130 mCurrentCommand = null; 131 mNextCommand = null; 132 mUpdateSpec.reset(); 133 notifyUpdateFailed(error); 134 } else if (mCurrentCommand.isCanceled()) { 135 if (mState == STATE_CANCELING) { 136 mState = STATE_CANCELED; 137 notifyUpdateCanceled(); 138 } 139 if (mNextCommand != null) { 140 runPendingCommand(); 141 } else { 142 // The update was not performed, hence the spec is stale 143 mUpdateSpec.reset(); 144 } 145 } 146 } 147 }; 148 149 private final DeathRecipient mDeathRecipient = new DeathRecipient() { 150 @Override 151 public void binderDied() { 152 onPrintingAppDied(); 153 } 154 }; 155 156 private int mState = STATE_INITIAL; 157 158 private AsyncCommand mCurrentCommand; 159 private AsyncCommand mNextCommand; 160 161 public interface RemoteAdapterDeathObserver { 162 public void onDied(); 163 } 164 165 public interface UpdateResultCallbacks { 166 public void onUpdateCompleted(RemotePrintDocumentInfo document); 167 public void onUpdateCanceled(); 168 public void onUpdateFailed(CharSequence error); 169 } 170 171 public RemotePrintDocument(Context context, IPrintDocumentAdapter adapter, 172 MutexFileProvider fileProvider, RemoteAdapterDeathObserver deathObserver, 173 UpdateResultCallbacks callbacks) { 174 mPrintDocumentAdapter = adapter; 175 mLooper = context.getMainLooper(); 176 mContext = context; 177 mAdapterDeathObserver = deathObserver; 178 mDocumentInfo = new RemotePrintDocumentInfo(); 179 mDocumentInfo.fileProvider = fileProvider; 180 mUpdateCallbacks = callbacks; 181 connectToRemoteDocument(); 182 } 183 184 public void start() { 185 if (DEBUG) { 186 Log.i(LOG_TAG, "[CALLED] start()"); 187 } 188 if (mState == STATE_FAILED) { 189 Log.w(LOG_TAG, "Failed before start."); 190 } else if (mState == STATE_DESTROYED) { 191 Log.w(LOG_TAG, "Destroyed before start."); 192 } else { 193 if (mState != STATE_INITIAL) { 194 throw new IllegalStateException("Cannot start in state:" + stateToString(mState)); 195 } 196 try { 197 mPrintDocumentAdapter.start(); 198 mState = STATE_STARTED; 199 } catch (RemoteException re) { 200 Log.e(LOG_TAG, "Error calling start()", re); 201 mState = STATE_FAILED; 202 } 203 } 204 } 205 206 public boolean update(PrintAttributes attributes, PageRange[] pages, boolean preview) { 207 boolean willUpdate; 208 209 if (DEBUG) { 210 Log.i(LOG_TAG, "[CALLED] update()"); 211 } 212 213 if (hasUpdateError()) { 214 throw new IllegalStateException("Cannot update without a clearing the failure"); 215 } 216 217 if (mState == STATE_INITIAL || mState == STATE_FINISHED || mState == STATE_DESTROYED) { 218 throw new IllegalStateException("Cannot update in state:" + stateToString(mState)); 219 } 220 221 // We schedule a layout if the constraints changed. 222 if (!mUpdateSpec.hasSameConstraints(attributes, preview)) { 223 willUpdate = true; 224 225 // If there is a current command that is running we ask for a 226 // cancellation and start over. 227 if (mCurrentCommand != null && (mCurrentCommand.isRunning() 228 || mCurrentCommand.isPending())) { 229 mCurrentCommand.cancel(false); 230 } 231 232 // Schedule a layout command. 233 PrintAttributes oldAttributes = mDocumentInfo.attributes != null 234 ? mDocumentInfo.attributes : new PrintAttributes.Builder().build(); 235 AsyncCommand command = new LayoutCommand(mLooper, mPrintDocumentAdapter, 236 mDocumentInfo, oldAttributes, attributes, preview, mCommandResultCallback); 237 scheduleCommand(command); 238 239 mDocumentInfo.updated = false; 240 mState = STATE_UPDATING; 241 // If no layout in progress and we don't have all pages - schedule a write. 242 } else if ((!(mCurrentCommand instanceof LayoutCommand) 243 || (!mCurrentCommand.isPending() && !mCurrentCommand.isRunning())) 244 && pages != null && !PageRangeUtils.contains(mUpdateSpec.pages, pages, 245 mDocumentInfo.info.getPageCount())) { 246 willUpdate = true; 247 248 // Cancel the current write as a new one is to be scheduled. 249 if (mCurrentCommand instanceof WriteCommand 250 && (mCurrentCommand.isPending() || mCurrentCommand.isRunning())) { 251 mCurrentCommand.cancel(false); 252 } 253 254 // Schedule a write command. 255 AsyncCommand command = new WriteCommand(mContext, mLooper, mPrintDocumentAdapter, 256 mDocumentInfo, mDocumentInfo.info.getPageCount(), pages, 257 mDocumentInfo.fileProvider, mCommandResultCallback); 258 scheduleCommand(command); 259 260 mDocumentInfo.updated = false; 261 mState = STATE_UPDATING; 262 } else { 263 willUpdate = false; 264 if (DEBUG) { 265 Log.i(LOG_TAG, "[SKIPPING] No update needed"); 266 } 267 } 268 269 // Keep track of what is requested. 270 mUpdateSpec.update(attributes, preview, pages); 271 272 runPendingCommand(); 273 274 return willUpdate; 275 } 276 277 public void finish() { 278 if (DEBUG) { 279 Log.i(LOG_TAG, "[CALLED] finish()"); 280 } 281 if (mState != STATE_STARTED && mState != STATE_UPDATED 282 && mState != STATE_FAILED && mState != STATE_CANCELING 283 && mState != STATE_CANCELED && mState != STATE_DESTROYED) { 284 throw new IllegalStateException("Cannot finish in state:" 285 + stateToString(mState)); 286 } 287 try { 288 mPrintDocumentAdapter.finish(); 289 mState = STATE_FINISHED; 290 } catch (RemoteException re) { 291 Log.e(LOG_TAG, "Error calling finish()"); 292 mState = STATE_FAILED; 293 } 294 } 295 296 public void cancel(boolean force) { 297 if (DEBUG) { 298 Log.i(LOG_TAG, "[CALLED] cancel(" + force + ")"); 299 } 300 301 mNextCommand = null; 302 303 if (mState != STATE_UPDATING) { 304 return; 305 } 306 307 mState = STATE_CANCELING; 308 309 mCurrentCommand.cancel(force); 310 } 311 312 public void destroy() { 313 if (DEBUG) { 314 Log.i(LOG_TAG, "[CALLED] destroy()"); 315 } 316 if (mState == STATE_DESTROYED) { 317 throw new IllegalStateException("Cannot destroy in state:" + stateToString(mState)); 318 } 319 320 mState = STATE_DESTROYED; 321 322 disconnectFromRemoteDocument(); 323 } 324 325 public void kill(String reason) { 326 if (DEBUG) { 327 Log.i(LOG_TAG, "[CALLED] kill()"); 328 } 329 330 try { 331 mPrintDocumentAdapter.kill(reason); 332 } catch (RemoteException re) { 333 Log.e(LOG_TAG, "Error calling kill()", re); 334 } 335 } 336 337 public boolean isUpdating() { 338 return mState == STATE_UPDATING || mState == STATE_CANCELING; 339 } 340 341 public boolean isDestroyed() { 342 return mState == STATE_DESTROYED; 343 } 344 345 public boolean hasUpdateError() { 346 return mState == STATE_FAILED; 347 } 348 349 public boolean hasLaidOutPages() { 350 return mDocumentInfo.info != null 351 && mDocumentInfo.info.getPageCount() > 0; 352 } 353 354 public void clearUpdateError() { 355 if (!hasUpdateError()) { 356 throw new IllegalStateException("No update error to clear"); 357 } 358 mState = STATE_STARTED; 359 } 360 361 public RemotePrintDocumentInfo getDocumentInfo() { 362 return mDocumentInfo; 363 } 364 365 public void writeContent(ContentResolver contentResolver, Uri uri) { 366 File file = null; 367 InputStream in = null; 368 OutputStream out = null; 369 try { 370 file = mDocumentInfo.fileProvider.acquireFile(null); 371 in = new FileInputStream(file); 372 out = contentResolver.openOutputStream(uri); 373 final byte[] buffer = new byte[8192]; 374 while (true) { 375 final int readByteCount = in.read(buffer); 376 if (readByteCount < 0) { 377 break; 378 } 379 out.write(buffer, 0, readByteCount); 380 } 381 } catch (IOException e) { 382 Log.e(LOG_TAG, "Error writing document content.", e); 383 } finally { 384 IoUtils.closeQuietly(in); 385 IoUtils.closeQuietly(out); 386 if (file != null) { 387 mDocumentInfo.fileProvider.releaseFile(); 388 } 389 } 390 } 391 392 private void notifyUpdateCanceled() { 393 if (DEBUG) { 394 Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()"); 395 } 396 mUpdateCallbacks.onUpdateCanceled(); 397 } 398 399 private void notifyUpdateCompleted() { 400 if (DEBUG) { 401 Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()"); 402 } 403 mUpdateCallbacks.onUpdateCompleted(mDocumentInfo); 404 } 405 406 private void notifyUpdateFailed(CharSequence error) { 407 if (DEBUG) { 408 Log.i(LOG_TAG, "[CALLING] notifyUpdateFailed()"); 409 } 410 mUpdateCallbacks.onUpdateFailed(error); 411 } 412 413 private void connectToRemoteDocument() { 414 try { 415 mPrintDocumentAdapter.asBinder().linkToDeath(mDeathRecipient, 0); 416 } catch (RemoteException re) { 417 Log.w(LOG_TAG, "The printing process is dead."); 418 destroy(); 419 return; 420 } 421 422 try { 423 mPrintDocumentAdapter.setObserver(new PrintDocumentAdapterObserver(this)); 424 } catch (RemoteException re) { 425 Log.w(LOG_TAG, "Error setting observer to the print adapter."); 426 destroy(); 427 } 428 } 429 430 private void disconnectFromRemoteDocument() { 431 try { 432 mPrintDocumentAdapter.setObserver(null); 433 } catch (RemoteException re) { 434 Log.w(LOG_TAG, "Error setting observer to the print adapter."); 435 // Keep going - best effort... 436 } 437 438 mPrintDocumentAdapter.asBinder().unlinkToDeath(mDeathRecipient, 0); 439 } 440 441 private void scheduleCommand(AsyncCommand command) { 442 if (mCurrentCommand == null) { 443 mCurrentCommand = command; 444 } else { 445 mNextCommand = command; 446 } 447 } 448 449 private void runPendingCommand() { 450 if (mCurrentCommand != null 451 && (mCurrentCommand.isCompleted() 452 || mCurrentCommand.isCanceled())) { 453 mCurrentCommand = mNextCommand; 454 mNextCommand = null; 455 } 456 457 if (mCurrentCommand != null) { 458 if (mCurrentCommand.isPending()) { 459 mCurrentCommand.run(); 460 461 mState = STATE_UPDATING; 462 } 463 } else { 464 mState = STATE_UPDATED; 465 } 466 } 467 468 private static String stateToString(int state) { 469 switch (state) { 470 case STATE_FINISHED: { 471 return "STATE_FINISHED"; 472 } 473 case STATE_FAILED: { 474 return "STATE_FAILED"; 475 } 476 case STATE_STARTED: { 477 return "STATE_STARTED"; 478 } 479 case STATE_UPDATING: { 480 return "STATE_UPDATING"; 481 } 482 case STATE_UPDATED: { 483 return "STATE_UPDATED"; 484 } 485 case STATE_CANCELING: { 486 return "STATE_CANCELING"; 487 } 488 case STATE_CANCELED: { 489 return "STATE_CANCELED"; 490 } 491 case STATE_DESTROYED: { 492 return "STATE_DESTROYED"; 493 } 494 default: { 495 return "STATE_UNKNOWN"; 496 } 497 } 498 } 499 500 static final class UpdateSpec { 501 final PrintAttributes attributes = new PrintAttributes.Builder().build(); 502 boolean preview; 503 PageRange[] pages; 504 505 public void update(PrintAttributes attributes, boolean preview, 506 PageRange[] pages) { 507 this.attributes.copyFrom(attributes); 508 this.preview = preview; 509 this.pages = (pages != null) ? Arrays.copyOf(pages, pages.length) : null; 510 } 511 512 public void reset() { 513 attributes.clear(); 514 preview = false; 515 pages = null; 516 } 517 518 public boolean hasSameConstraints(PrintAttributes attributes, boolean preview) { 519 return this.attributes.equals(attributes) && this.preview == preview; 520 } 521 } 522 523 public static final class RemotePrintDocumentInfo { 524 public PrintAttributes attributes; 525 public Bundle metadata; 526 public PrintDocumentInfo info; 527 528 /** 529 * Which pages out of the ones written to the file to print. This is not indexed by the 530 * document pages, but by the page number in the file. 531 * <p>E.g. if a document has 10 pages, we want pages 4-5 and 7, but only page 3-9 are in the 532 * file. This would contain 1-2 and 4.</p> 533 * 534 * @see PageRangeUtils#computeWhichPagesInFileToPrint 535 */ 536 public PageRange[] pagesInFileToPrint; 537 538 /** Pages of the whole document that are currently written to file */ 539 public PageRange[] pagesWrittenToFile; 540 541 public MutexFileProvider fileProvider; 542 public boolean changed; 543 public boolean updated; 544 public boolean laidout; 545 } 546 547 private interface CommandDoneCallback { 548 public void onDone(); 549 } 550 551 private static abstract class AsyncCommand implements Runnable { 552 /** Message indicated the desire to {@link #forceCancel} a command */ 553 static final int MSG_FORCE_CANCEL = 0; 554 555 private static final int STATE_PENDING = 0; 556 private static final int STATE_RUNNING = 1; 557 private static final int STATE_COMPLETED = 2; 558 private static final int STATE_CANCELED = 3; 559 private static final int STATE_CANCELING = 4; 560 private static final int STATE_FAILED = 5; 561 562 private static int sSequenceCounter; 563 564 protected final int mSequence = sSequenceCounter++; 565 protected final IPrintDocumentAdapter mAdapter; 566 protected final RemotePrintDocumentInfo mDocument; 567 568 protected final CommandDoneCallback mDoneCallback; 569 570 private final Handler mHandler; 571 572 protected ICancellationSignal mCancellation; 573 574 private CharSequence mError; 575 576 private int mState = STATE_PENDING; 577 578 public AsyncCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, 579 CommandDoneCallback doneCallback) { 580 mHandler = new Handler(looper); 581 mAdapter = adapter; 582 mDocument = document; 583 mDoneCallback = doneCallback; 584 } 585 586 protected final boolean isCanceling() { 587 return mState == STATE_CANCELING; 588 } 589 590 public final boolean isCanceled() { 591 return mState == STATE_CANCELED; 592 } 593 594 /** 595 * If a force cancel is pending, remove it. This is usually called when a command returns 596 * and thereby does not need to be canceled anymore. 597 */ 598 protected void removeForceCancel() { 599 if (DEBUG) { 600 if (mHandler.hasMessages(MSG_FORCE_CANCEL)) { 601 Log.i(LOG_TAG, "[FORCE CANCEL] Removed"); 602 } 603 } 604 605 mHandler.removeMessages(MSG_FORCE_CANCEL); 606 } 607 608 /** 609 * Cancel the current command. 610 * 611 * @param force If set, does not wait for the {@link PrintDocumentAdapter} to cancel. This 612 * should only be used if this is the last command send to the as otherwise the 613 * {@link PrintDocumentAdapter adapter} might get commands while it is still 614 * running the old one. 615 */ 616 public final void cancel(boolean force) { 617 if (isRunning()) { 618 canceling(); 619 if (mCancellation != null) { 620 try { 621 mCancellation.cancel(); 622 } catch (RemoteException re) { 623 Log.w(LOG_TAG, "Error while canceling", re); 624 } 625 } 626 } 627 628 if (isCanceling()) { 629 if (force) { 630 if (DEBUG) { 631 Log.i(LOG_TAG, "[FORCE CANCEL] queued"); 632 } 633 mHandler.sendMessageDelayed( 634 PooledLambda.obtainMessage(AsyncCommand::forceCancel, this) 635 .setWhat(MSG_FORCE_CANCEL), 636 FORCE_CANCEL_TIMEOUT); 637 } 638 639 return; 640 } 641 642 canceled(); 643 644 // Done. 645 mDoneCallback.onDone(); 646 } 647 648 protected final void canceling() { 649 if (mState != STATE_PENDING && mState != STATE_RUNNING) { 650 throw new IllegalStateException("Command not pending or running."); 651 } 652 mState = STATE_CANCELING; 653 } 654 655 protected final void canceled() { 656 if (mState != STATE_CANCELING) { 657 throw new IllegalStateException("Not canceling."); 658 } 659 mState = STATE_CANCELED; 660 } 661 662 public final boolean isPending() { 663 return mState == STATE_PENDING; 664 } 665 666 protected final void running() { 667 if (mState != STATE_PENDING) { 668 throw new IllegalStateException("Not pending."); 669 } 670 mState = STATE_RUNNING; 671 } 672 673 public final boolean isRunning() { 674 return mState == STATE_RUNNING; 675 } 676 677 protected final void completed() { 678 if (mState != STATE_RUNNING && mState != STATE_CANCELING) { 679 throw new IllegalStateException("Not running."); 680 } 681 mState = STATE_COMPLETED; 682 } 683 684 public final boolean isCompleted() { 685 return mState == STATE_COMPLETED; 686 } 687 688 protected final void failed(CharSequence error) { 689 if (mState != STATE_RUNNING && mState != STATE_CANCELING) { 690 throw new IllegalStateException("Not running."); 691 } 692 mState = STATE_FAILED; 693 694 mError = error; 695 } 696 697 public final boolean isFailed() { 698 return mState == STATE_FAILED; 699 } 700 701 public CharSequence getError() { 702 return mError; 703 } 704 705 private void forceCancel() { 706 if (isCanceling()) { 707 if (DEBUG) { 708 Log.i(LOG_TAG, "[FORCE CANCEL] executed"); 709 } 710 failed("Command did not respond to cancellation in " 711 + FORCE_CANCEL_TIMEOUT + " ms"); 712 713 mDoneCallback.onDone(); 714 } 715 } 716 } 717 718 private static final class LayoutCommand extends AsyncCommand { 719 private final PrintAttributes mOldAttributes = new PrintAttributes.Builder().build(); 720 private final PrintAttributes mNewAttributes = new PrintAttributes.Builder().build(); 721 private final Bundle mMetadata = new Bundle(); 722 723 private final ILayoutResultCallback mRemoteResultCallback; 724 725 private final Handler mHandler; 726 727 public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter, 728 RemotePrintDocumentInfo document, PrintAttributes oldAttributes, 729 PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) { 730 super(looper, adapter, document, callback); 731 mHandler = new LayoutHandler(looper); 732 mRemoteResultCallback = new LayoutResultCallback(mHandler); 733 mOldAttributes.copyFrom(oldAttributes); 734 mNewAttributes.copyFrom(newAttributes); 735 mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, preview); 736 } 737 738 @Override 739 public void run() { 740 running(); 741 742 try { 743 if (DEBUG) { 744 Log.i(LOG_TAG, "[PERFORMING] layout"); 745 } 746 mDocument.changed = false; 747 mAdapter.layout(mOldAttributes, mNewAttributes, mRemoteResultCallback, 748 mMetadata, mSequence); 749 } catch (RemoteException re) { 750 Log.e(LOG_TAG, "Error calling layout", re); 751 handleOnLayoutFailed(null, mSequence); 752 } 753 } 754 755 private void handleOnLayoutStarted(ICancellationSignal cancellation, int sequence) { 756 if (sequence != mSequence) { 757 return; 758 } 759 760 if (DEBUG) { 761 Log.i(LOG_TAG, "[CALLBACK] onLayoutStarted"); 762 } 763 764 if (isCanceling()) { 765 try { 766 cancellation.cancel(); 767 } catch (RemoteException re) { 768 Log.e(LOG_TAG, "Error cancelling", re); 769 handleOnLayoutFailed(null, mSequence); 770 } 771 } else { 772 mCancellation = cancellation; 773 } 774 } 775 776 private void handleOnLayoutFinished(PrintDocumentInfo info, 777 boolean changed, int sequence) { 778 if (sequence != mSequence) { 779 return; 780 } 781 782 if (DEBUG) { 783 Log.i(LOG_TAG, "[CALLBACK] onLayoutFinished"); 784 } 785 786 completed(); 787 788 // If the document description changed or the content in the 789 // document changed, the we need to invalidate the pages. 790 if (changed || !equalsIgnoreSize(mDocument.info, info)) { 791 // If the content changed we throw away all pages as 792 // we will request them again with the new content. 793 mDocument.pagesWrittenToFile = null; 794 mDocument.pagesInFileToPrint = null; 795 mDocument.changed = true; 796 } 797 798 // Update the document with data from the layout pass. 799 mDocument.attributes = mNewAttributes; 800 mDocument.metadata = mMetadata; 801 mDocument.laidout = true; 802 mDocument.info = info; 803 804 // Release the remote cancellation interface. 805 mCancellation = null; 806 807 // Done. 808 mDoneCallback.onDone(); 809 } 810 811 private void handleOnLayoutFailed(CharSequence error, int sequence) { 812 if (sequence != mSequence) { 813 return; 814 } 815 816 if (DEBUG) { 817 Log.i(LOG_TAG, "[CALLBACK] onLayoutFailed"); 818 } 819 820 mDocument.laidout = false; 821 822 failed(error); 823 824 // Release the remote cancellation interface. 825 mCancellation = null; 826 827 // Failed. 828 mDoneCallback.onDone(); 829 } 830 831 private void handleOnLayoutCanceled(int sequence) { 832 if (sequence != mSequence) { 833 return; 834 } 835 836 if (DEBUG) { 837 Log.i(LOG_TAG, "[CALLBACK] onLayoutCanceled"); 838 } 839 840 canceled(); 841 842 // Release the remote cancellation interface. 843 mCancellation = null; 844 845 // Done. 846 mDoneCallback.onDone(); 847 } 848 849 private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) { 850 if (lhs == rhs) { 851 return true; 852 } 853 if (lhs == null) { 854 return false; 855 } else { 856 if (rhs == null) { 857 return false; 858 } 859 if (lhs.getContentType() != rhs.getContentType() 860 || lhs.getPageCount() != rhs.getPageCount()) { 861 return false; 862 } 863 } 864 return true; 865 } 866 867 private final class LayoutHandler extends Handler { 868 public static final int MSG_ON_LAYOUT_STARTED = 1; 869 public static final int MSG_ON_LAYOUT_FINISHED = 2; 870 public static final int MSG_ON_LAYOUT_FAILED = 3; 871 public static final int MSG_ON_LAYOUT_CANCELED = 4; 872 873 public LayoutHandler(Looper looper) { 874 super(looper, null, false); 875 } 876 877 @Override 878 public void handleMessage(Message message) { 879 // The command might have been force canceled, see 880 // AsyncCommand.AsyncCommandHandler#handleMessage 881 if (isFailed()) { 882 if (DEBUG) { 883 Log.i(LOG_TAG, "[CALLBACK] on failed layout command"); 884 } 885 886 return; 887 } 888 889 int sequence; 890 int what = message.what; 891 CharSequence error = null; 892 switch (what) { 893 case MSG_ON_LAYOUT_FINISHED: 894 removeForceCancel(); 895 sequence = message.arg2; 896 break; 897 case MSG_ON_LAYOUT_FAILED: 898 error = (CharSequence) message.obj; 899 removeForceCancel(); 900 sequence = message.arg1; 901 break; 902 case MSG_ON_LAYOUT_CANCELED: 903 if (!isCanceling()) { 904 Log.w(LOG_TAG, "Unexpected cancel"); 905 what = MSG_ON_LAYOUT_FAILED; 906 } 907 removeForceCancel(); 908 sequence = message.arg1; 909 break; 910 case MSG_ON_LAYOUT_STARTED: 911 // Don't remote force-cancel as command is still running and might need to 912 // be canceled later 913 sequence = message.arg1; 914 break; 915 default: 916 // not reached 917 sequence = -1; 918 } 919 920 // If we are canceling any result is treated as a cancel 921 if (isCanceling() && what != MSG_ON_LAYOUT_STARTED) { 922 what = MSG_ON_LAYOUT_CANCELED; 923 } 924 925 switch (what) { 926 case MSG_ON_LAYOUT_STARTED: { 927 ICancellationSignal cancellation = (ICancellationSignal) message.obj; 928 handleOnLayoutStarted(cancellation, sequence); 929 } break; 930 931 case MSG_ON_LAYOUT_FINISHED: { 932 PrintDocumentInfo info = (PrintDocumentInfo) message.obj; 933 final boolean changed = (message.arg1 == 1); 934 handleOnLayoutFinished(info, changed, sequence); 935 } break; 936 937 case MSG_ON_LAYOUT_FAILED: { 938 handleOnLayoutFailed(error, sequence); 939 } break; 940 941 case MSG_ON_LAYOUT_CANCELED: { 942 handleOnLayoutCanceled(sequence); 943 } break; 944 } 945 } 946 } 947 948 private static final class LayoutResultCallback extends ILayoutResultCallback.Stub { 949 private final WeakReference<Handler> mWeakHandler; 950 951 public LayoutResultCallback(Handler handler) { 952 mWeakHandler = new WeakReference<>(handler); 953 } 954 955 @Override 956 public void onLayoutStarted(ICancellationSignal cancellation, int sequence) { 957 Handler handler = mWeakHandler.get(); 958 if (handler != null) { 959 handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_STARTED, 960 sequence, 0, cancellation).sendToTarget(); 961 } 962 } 963 964 @Override 965 public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) { 966 Handler handler = mWeakHandler.get(); 967 if (handler != null) { 968 handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FINISHED, 969 changed ? 1 : 0, sequence, info).sendToTarget(); 970 } 971 } 972 973 @Override 974 public void onLayoutFailed(CharSequence error, int sequence) { 975 Handler handler = mWeakHandler.get(); 976 if (handler != null) { 977 handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FAILED, 978 sequence, 0, error).sendToTarget(); 979 } 980 } 981 982 @Override 983 public void onLayoutCanceled(int sequence) { 984 Handler handler = mWeakHandler.get(); 985 if (handler != null) { 986 handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_CANCELED, 987 sequence, 0).sendToTarget(); 988 } 989 } 990 } 991 } 992 993 private static final class WriteCommand extends AsyncCommand { 994 private final int mPageCount; 995 private final PageRange[] mPages; 996 private final MutexFileProvider mFileProvider; 997 998 private final IWriteResultCallback mRemoteResultCallback; 999 private final CommandDoneCallback mWriteDoneCallback; 1000 1001 private final Context mContext; 1002 private final Handler mHandler; 1003 1004 public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter, 1005 RemotePrintDocumentInfo document, int pageCount, PageRange[] pages, 1006 MutexFileProvider fileProvider, CommandDoneCallback callback) { 1007 super(looper, adapter, document, callback); 1008 mContext = context; 1009 mHandler = new WriteHandler(looper); 1010 mRemoteResultCallback = new WriteResultCallback(mHandler); 1011 mPageCount = pageCount; 1012 mPages = Arrays.copyOf(pages, pages.length); 1013 mFileProvider = fileProvider; 1014 mWriteDoneCallback = callback; 1015 } 1016 1017 @Override 1018 public void run() { 1019 running(); 1020 1021 // This is a long running operation as we will be reading fully 1022 // the written data. In case of a cancellation, we ask the client 1023 // to stop writing data and close the file descriptor after 1024 // which we will reach the end of the stream, thus stop reading. 1025 new AsyncTask<Void, Void, Void>() { 1026 @Override 1027 protected Void doInBackground(Void... params) { 1028 File file = null; 1029 InputStream in = null; 1030 OutputStream out = null; 1031 ParcelFileDescriptor source = null; 1032 ParcelFileDescriptor sink = null; 1033 try { 1034 file = mFileProvider.acquireFile(null); 1035 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1036 source = pipe[0]; 1037 sink = pipe[1]; 1038 1039 in = new FileInputStream(source.getFileDescriptor()); 1040 out = new FileOutputStream(file); 1041 1042 // Async call to initiate the other process writing the data. 1043 if (DEBUG) { 1044 Log.i(LOG_TAG, "[PERFORMING] write"); 1045 } 1046 mAdapter.write(mPages, sink, mRemoteResultCallback, mSequence); 1047 1048 // Close the source. It is now held by the client. 1049 sink.close(); 1050 sink = null; 1051 1052 // Read the data. 1053 final byte[] buffer = new byte[8192]; 1054 while (true) { 1055 final int readByteCount = in.read(buffer); 1056 if (readByteCount < 0) { 1057 break; 1058 } 1059 out.write(buffer, 0, readByteCount); 1060 } 1061 } catch (RemoteException | IOException e) { 1062 Log.e(LOG_TAG, "Error calling write()", e); 1063 } finally { 1064 IoUtils.closeQuietly(in); 1065 IoUtils.closeQuietly(out); 1066 IoUtils.closeQuietly(sink); 1067 IoUtils.closeQuietly(source); 1068 if (file != null) { 1069 mFileProvider.releaseFile(); 1070 } 1071 } 1072 return null; 1073 } 1074 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); 1075 } 1076 1077 private void handleOnWriteStarted(ICancellationSignal cancellation, int sequence) { 1078 if (sequence != mSequence) { 1079 return; 1080 } 1081 1082 if (DEBUG) { 1083 Log.i(LOG_TAG, "[CALLBACK] onWriteStarted"); 1084 } 1085 1086 if (isCanceling()) { 1087 try { 1088 cancellation.cancel(); 1089 } catch (RemoteException re) { 1090 Log.e(LOG_TAG, "Error cancelling", re); 1091 handleOnWriteFailed(null, sequence); 1092 } 1093 } else { 1094 mCancellation = cancellation; 1095 } 1096 } 1097 1098 private void handleOnWriteFinished(PageRange[] pages, int sequence) { 1099 if (sequence != mSequence) { 1100 return; 1101 } 1102 1103 if (DEBUG) { 1104 Log.i(LOG_TAG, "[CALLBACK] onWriteFinished"); 1105 } 1106 1107 PageRange[] writtenPages = PageRangeUtils.normalize(pages); 1108 PageRange[] printedPages = PageRangeUtils.computeWhichPagesInFileToPrint( 1109 mPages, writtenPages, mPageCount); 1110 1111 // Handle if we got invalid pages 1112 if (printedPages != null) { 1113 mDocument.pagesWrittenToFile = writtenPages; 1114 mDocument.pagesInFileToPrint = printedPages; 1115 completed(); 1116 } else { 1117 mDocument.pagesWrittenToFile = null; 1118 mDocument.pagesInFileToPrint = null; 1119 failed(mContext.getString(R.string.print_error_default_message)); 1120 } 1121 1122 // Release the remote cancellation interface. 1123 mCancellation = null; 1124 1125 // Done. 1126 mWriteDoneCallback.onDone(); 1127 } 1128 1129 private void handleOnWriteFailed(CharSequence error, int sequence) { 1130 if (sequence != mSequence) { 1131 return; 1132 } 1133 1134 if (DEBUG) { 1135 Log.i(LOG_TAG, "[CALLBACK] onWriteFailed"); 1136 } 1137 1138 failed(error); 1139 1140 // Release the remote cancellation interface. 1141 mCancellation = null; 1142 1143 // Done. 1144 mWriteDoneCallback.onDone(); 1145 } 1146 1147 private void handleOnWriteCanceled(int sequence) { 1148 if (sequence != mSequence) { 1149 return; 1150 } 1151 1152 if (DEBUG) { 1153 Log.i(LOG_TAG, "[CALLBACK] onWriteCanceled"); 1154 } 1155 1156 canceled(); 1157 1158 // Release the remote cancellation interface. 1159 mCancellation = null; 1160 1161 // Done. 1162 mWriteDoneCallback.onDone(); 1163 } 1164 1165 private final class WriteHandler extends Handler { 1166 public static final int MSG_ON_WRITE_STARTED = 1; 1167 public static final int MSG_ON_WRITE_FINISHED = 2; 1168 public static final int MSG_ON_WRITE_FAILED = 3; 1169 public static final int MSG_ON_WRITE_CANCELED = 4; 1170 1171 public WriteHandler(Looper looper) { 1172 super(looper, null, false); 1173 } 1174 1175 @Override 1176 public void handleMessage(Message message) { 1177 // The command might have been force canceled, see 1178 // AsyncCommand.AsyncCommandHandler#handleMessage 1179 if (isFailed()) { 1180 if (DEBUG) { 1181 Log.i(LOG_TAG, "[CALLBACK] on failed write command"); 1182 } 1183 1184 return; 1185 } 1186 1187 int what = message.what; 1188 CharSequence error = null; 1189 int sequence = message.arg1; 1190 switch (what) { 1191 case MSG_ON_WRITE_CANCELED: 1192 if (!isCanceling()) { 1193 Log.w(LOG_TAG, "Unexpected cancel"); 1194 what = MSG_ON_WRITE_FAILED; 1195 } 1196 removeForceCancel(); 1197 break; 1198 case MSG_ON_WRITE_FAILED: 1199 error = (CharSequence) message.obj; 1200 // $FALL-THROUGH 1201 case MSG_ON_WRITE_FINISHED: 1202 removeForceCancel(); 1203 // $FALL-THROUGH 1204 case MSG_ON_WRITE_STARTED: 1205 // Don't remote force-cancel as command is still running and might need to 1206 // be canceled later 1207 break; 1208 } 1209 1210 // If we are canceling any result is treated as a cancel 1211 if (isCanceling() && what != MSG_ON_WRITE_STARTED) { 1212 what = MSG_ON_WRITE_CANCELED; 1213 } 1214 1215 switch (what) { 1216 case MSG_ON_WRITE_STARTED: { 1217 ICancellationSignal cancellation = (ICancellationSignal) message.obj; 1218 handleOnWriteStarted(cancellation, sequence); 1219 } break; 1220 1221 case MSG_ON_WRITE_FINISHED: { 1222 PageRange[] pages = (PageRange[]) message.obj; 1223 handleOnWriteFinished(pages, sequence); 1224 } break; 1225 1226 case MSG_ON_WRITE_FAILED: { 1227 handleOnWriteFailed(error, sequence); 1228 } break; 1229 1230 case MSG_ON_WRITE_CANCELED: { 1231 handleOnWriteCanceled(sequence); 1232 } break; 1233 } 1234 } 1235 } 1236 1237 private static final class WriteResultCallback extends IWriteResultCallback.Stub { 1238 private final WeakReference<Handler> mWeakHandler; 1239 1240 public WriteResultCallback(Handler handler) { 1241 mWeakHandler = new WeakReference<>(handler); 1242 } 1243 1244 @Override 1245 public void onWriteStarted(ICancellationSignal cancellation, int sequence) { 1246 Handler handler = mWeakHandler.get(); 1247 if (handler != null) { 1248 handler.obtainMessage(WriteHandler.MSG_ON_WRITE_STARTED, 1249 sequence, 0, cancellation).sendToTarget(); 1250 } 1251 } 1252 1253 @Override 1254 public void onWriteFinished(PageRange[] pages, int sequence) { 1255 Handler handler = mWeakHandler.get(); 1256 if (handler != null) { 1257 handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FINISHED, 1258 sequence, 0, pages).sendToTarget(); 1259 } 1260 } 1261 1262 @Override 1263 public void onWriteFailed(CharSequence error, int sequence) { 1264 Handler handler = mWeakHandler.get(); 1265 if (handler != null) { 1266 handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FAILED, 1267 sequence, 0, error).sendToTarget(); 1268 } 1269 } 1270 1271 @Override 1272 public void onWriteCanceled(int sequence) { 1273 Handler handler = mWeakHandler.get(); 1274 if (handler != null) { 1275 handler.obtainMessage(WriteHandler.MSG_ON_WRITE_CANCELED, 1276 sequence, 0).sendToTarget(); 1277 } 1278 } 1279 } 1280 } 1281 1282 private void onPrintingAppDied() { 1283 mState = STATE_FAILED; 1284 new Handler(mLooper).post(new Runnable() { 1285 @Override 1286 public void run() { 1287 mAdapterDeathObserver.onDied(); 1288 } 1289 }); 1290 } 1291 1292 private static final class PrintDocumentAdapterObserver 1293 extends IPrintDocumentAdapterObserver.Stub { 1294 private final WeakReference<RemotePrintDocument> mWeakDocument; 1295 1296 public PrintDocumentAdapterObserver(RemotePrintDocument document) { 1297 mWeakDocument = new WeakReference<>(document); 1298 } 1299 1300 @Override 1301 public void onDestroy() { 1302 final RemotePrintDocument document = mWeakDocument.get(); 1303 if (document != null) { 1304 document.onPrintingAppDied(); 1305 } 1306 } 1307 } 1308 } 1309