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