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