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