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