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