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