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 { onDied()150 public void onDied(); 151 } 152 153 public interface UpdateResultCallbacks { onUpdateCompleted(RemotePrintDocumentInfo document)154 public void onUpdateCompleted(RemotePrintDocumentInfo document); onUpdateCanceled()155 public void onUpdateCanceled(); onUpdateFailed(CharSequence error)156 public void onUpdateFailed(CharSequence error); 157 } 158 RemotePrintDocument(Context context, IPrintDocumentAdapter adapter, MutexFileProvider fileProvider, RemoteAdapterDeathObserver deathObserver, UpdateResultCallbacks callbacks)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 start()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 update(PrintAttributes attributes, PageRange[] pages, boolean preview)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 finish()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 cancel()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 destroy()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 kill(String reason)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 isUpdating()319 public boolean isUpdating() { 320 return mState == STATE_UPDATING || mState == STATE_CANCELING; 321 } 322 isDestroyed()323 public boolean isDestroyed() { 324 return mState == STATE_DESTROYED; 325 } 326 hasUpdateError()327 public boolean hasUpdateError() { 328 return mState == STATE_FAILED; 329 } 330 hasLaidOutPages()331 public boolean hasLaidOutPages() { 332 return mDocumentInfo.info != null 333 && mDocumentInfo.info.getPageCount() > 0; 334 } 335 clearUpdateError()336 public void clearUpdateError() { 337 if (!hasUpdateError()) { 338 throw new IllegalStateException("No update error to clear"); 339 } 340 mState = STATE_STARTED; 341 } 342 getDocumentInfo()343 public RemotePrintDocumentInfo getDocumentInfo() { 344 return mDocumentInfo; 345 } 346 writeContent(ContentResolver contentResolver, Uri uri)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 notifyUpdateCanceled()374 private void notifyUpdateCanceled() { 375 if (DEBUG) { 376 Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()"); 377 } 378 mUpdateCallbacks.onUpdateCanceled(); 379 } 380 notifyUpdateCompleted()381 private void notifyUpdateCompleted() { 382 if (DEBUG) { 383 Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()"); 384 } 385 mUpdateCallbacks.onUpdateCompleted(mDocumentInfo); 386 } 387 notifyUpdateFailed(CharSequence error)388 private void notifyUpdateFailed(CharSequence error) { 389 if (DEBUG) { 390 Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()"); 391 } 392 mUpdateCallbacks.onUpdateFailed(error); 393 } 394 connectToRemoteDocument()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 disconnectFromRemoteDocument()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 scheduleCommand(AsyncCommand command)423 private void scheduleCommand(AsyncCommand command) { 424 if (mCurrentCommand == null) { 425 mCurrentCommand = command; 426 } else { 427 mNextCommand = command; 428 } 429 } 430 runPendingCommand()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 stateToString(int state)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 update(PrintAttributes attributes, boolean preview, PageRange[] pages)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 reset()493 public void reset() { 494 attributes.clear(); 495 preview = false; 496 pages = null; 497 } 498 hasSameConstraints(PrintAttributes attributes, boolean preview)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 { onDone()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 AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, CommandDoneCallback doneCallback)542 public AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, 543 CommandDoneCallback doneCallback) { 544 mAdapter = adapter; 545 mDocument = document; 546 mDoneCallback = doneCallback; 547 } 548 isCanceling()549 protected final boolean isCanceling() { 550 return mState == STATE_CANCELING; 551 } 552 isCanceled()553 public final boolean isCanceled() { 554 return mState == STATE_CANCELED; 555 } 556 cancel()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 canceling()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 canceled()582 protected final void canceled() { 583 if (mState != STATE_CANCELING) { 584 throw new IllegalStateException("Not canceling."); 585 } 586 mState = STATE_CANCELED; 587 } 588 isPending()589 public final boolean isPending() { 590 return mState == STATE_PENDING; 591 } 592 running()593 protected final void running() { 594 if (mState != STATE_PENDING) { 595 throw new IllegalStateException("Not pending."); 596 } 597 mState = STATE_RUNNING; 598 } 599 isRunning()600 public final boolean isRunning() { 601 return mState == STATE_RUNNING; 602 } 603 completed()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 isCompleted()611 public final boolean isCompleted() { 612 return mState == STATE_COMPLETED; 613 } 614 failed(CharSequence error)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 isFailed()624 public final boolean isFailed() { 625 return mState == STATE_FAILED; 626 } 627 getError()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 LayoutCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, PrintAttributes oldAttributes, PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback)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 run()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 handleOnLayoutStarted(ICancellationSignal cancellation, int sequence)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 handleOnLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence)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 handleOnLayoutFailed(CharSequence error, int sequence)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 handleOnLayoutCanceled(int sequence)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 equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs)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 LayoutHandler(Looper looper)788 public LayoutHandler(Looper looper) { 789 super(looper, null, false); 790 } 791 792 @Override handleMessage(Message message)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 LayoutResultCallback(Handler handler)825 public LayoutResultCallback(Handler handler) { 826 mWeakHandler = new WeakReference<>(handler); 827 } 828 829 @Override onLayoutStarted(ICancellationSignal cancellation, int sequence)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 onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence)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 onLayoutFailed(CharSequence error, int sequence)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 onLayoutCanceled(int sequence)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 WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, int pageCount, PageRange[] pages, MutexFileProvider fileProvider, CommandDoneCallback callback)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 run()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 handleOnWriteStarted(ICancellationSignal cancellation, int sequence)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 handleOnWriteFinished(PageRange[] pages, int sequence)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 handleOnWriteFailed(CharSequence error, int sequence)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 handleOnWriteCanceled(int sequence)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 WriteHandler(Looper looper)1045 public WriteHandler(Looper looper) { 1046 super(looper, null, false); 1047 } 1048 1049 @Override handleMessage(Message message)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 WriteResultCallback(Handler handler)1081 public WriteResultCallback(Handler handler) { 1082 mWeakHandler = new WeakReference<>(handler); 1083 } 1084 1085 @Override onWriteStarted(ICancellationSignal cancellation, int sequence)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 onWriteFinished(PageRange[] pages, int sequence)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 onWriteFailed(CharSequence error, int sequence)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 onWriteCanceled(int sequence)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 onPrintingAppDied()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 PrintDocumentAdapterObserver(RemotePrintDocument document)1137 public PrintDocumentAdapterObserver(RemotePrintDocument document) { 1138 mWeakDocument = new WeakReference<>(document); 1139 } 1140 1141 @Override onDestroy()1142 public void onDestroy() { 1143 final RemotePrintDocument document = mWeakDocument.get(); 1144 if (document != null) { 1145 document.onPrintingAppDied(); 1146 } 1147 } 1148 } 1149 } 1150