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