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