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