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