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