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