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 { 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()", re); 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 isUpdating()307 public boolean isUpdating() { 308 return mState == STATE_UPDATING || mState == STATE_CANCELING; 309 } 310 isDestroyed()311 public boolean isDestroyed() { 312 return mState == STATE_DESTROYED; 313 } 314 hasUpdateError()315 public boolean hasUpdateError() { 316 return mState == STATE_FAILED; 317 } 318 hasLaidOutPages()319 public boolean hasLaidOutPages() { 320 return mDocumentInfo.info != null 321 && mDocumentInfo.info.getPageCount() > 0; 322 } 323 clearUpdateError()324 public void clearUpdateError() { 325 if (!hasUpdateError()) { 326 throw new IllegalStateException("No update error to clear"); 327 } 328 mState = STATE_STARTED; 329 } 330 getDocumentInfo()331 public RemotePrintDocumentInfo getDocumentInfo() { 332 return mDocumentInfo; 333 } 334 writeContent(ContentResolver contentResolver, Uri uri)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 notifyUpdateCanceled()362 private void notifyUpdateCanceled() { 363 if (DEBUG) { 364 Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()"); 365 } 366 mUpdateCallbacks.onUpdateCanceled(); 367 } 368 notifyUpdateCompleted()369 private void notifyUpdateCompleted() { 370 if (DEBUG) { 371 Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()"); 372 } 373 mUpdateCallbacks.onUpdateCompleted(mDocumentInfo); 374 } 375 notifyUpdateFailed(CharSequence error)376 private void notifyUpdateFailed(CharSequence error) { 377 if (DEBUG) { 378 Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()"); 379 } 380 mUpdateCallbacks.onUpdateFailed(error); 381 } 382 connectToRemoteDocument()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 disconnectFromRemoteDocument()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 scheduleCommand(AsyncCommand command)411 private void scheduleCommand(AsyncCommand command) { 412 if (mCurrentCommand == null) { 413 mCurrentCommand = command; 414 } else { 415 mNextCommand = command; 416 } 417 } 418 runPendingCommand()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 stateToString(int state)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 update(PrintAttributes attributes, boolean preview, PageRange[] pages)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 reset()481 public void reset() { 482 attributes.clear(); 483 preview = false; 484 pages = null; 485 } 486 hasSameConstraints(PrintAttributes attributes, boolean preview)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 { onDone()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 AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, CommandDoneCallback doneCallback)530 public AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, 531 CommandDoneCallback doneCallback) { 532 mAdapter = adapter; 533 mDocument = document; 534 mDoneCallback = doneCallback; 535 } 536 isCanceling()537 protected final boolean isCanceling() { 538 return mState == STATE_CANCELING; 539 } 540 isCanceled()541 public final boolean isCanceled() { 542 return mState == STATE_CANCELED; 543 } 544 cancel()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 canceling()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 canceled()570 protected final void canceled() { 571 if (mState != STATE_CANCELING) { 572 throw new IllegalStateException("Not canceling."); 573 } 574 mState = STATE_CANCELED; 575 } 576 isPending()577 public final boolean isPending() { 578 return mState == STATE_PENDING; 579 } 580 running()581 protected final void running() { 582 if (mState != STATE_PENDING) { 583 throw new IllegalStateException("Not pending."); 584 } 585 mState = STATE_RUNNING; 586 } 587 isRunning()588 public final boolean isRunning() { 589 return mState == STATE_RUNNING; 590 } 591 completed()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 isCompleted()599 public final boolean isCompleted() { 600 return mState == STATE_COMPLETED; 601 } 602 failed(CharSequence error)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 isFailed()612 public final boolean isFailed() { 613 return mState == STATE_FAILED; 614 } 615 getError()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 LayoutCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, PrintAttributes oldAttributes, PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback)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 run()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 handleOnLayoutStarted(ICancellationSignal cancellation, int sequence)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 handleOnLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence)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 handleOnLayoutFailed(CharSequence error, int sequence)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 handleOnLayoutCanceled(int sequence)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 equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs)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 LayoutHandler(Looper looper)776 public LayoutHandler(Looper looper) { 777 super(looper, null, false); 778 } 779 780 @Override handleMessage(Message message)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 LayoutResultCallback(Handler handler)813 public LayoutResultCallback(Handler handler) { 814 mWeakHandler = new WeakReference<>(handler); 815 } 816 817 @Override onLayoutStarted(ICancellationSignal cancellation, int sequence)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 onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence)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 onLayoutFailed(CharSequence error, int sequence)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 onLayoutCanceled(int sequence)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 WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, int pageCount, PageRange[] pages, MutexFileProvider fileProvider, CommandDoneCallback callback)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 run()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 handleOnWriteStarted(ICancellationSignal cancellation, int sequence)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 handleOnWriteFinished(PageRange[] pages, int sequence)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 handleOnWriteFailed(CharSequence error, int sequence)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 handleOnWriteCanceled(int sequence)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 WriteHandler(Looper looper)1033 public WriteHandler(Looper looper) { 1034 super(looper, null, false); 1035 } 1036 1037 @Override handleMessage(Message message)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 WriteResultCallback(Handler handler)1069 public WriteResultCallback(Handler handler) { 1070 mWeakHandler = new WeakReference<>(handler); 1071 } 1072 1073 @Override onWriteStarted(ICancellationSignal cancellation, int sequence)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 onWriteFinished(PageRange[] pages, int sequence)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 onWriteFailed(CharSequence error, int sequence)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 onWriteCanceled(int sequence)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 notifyPrintingAppDied()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 PrintDocumentAdapterObserver(RemotePrintDocument document)1124 public PrintDocumentAdapterObserver(RemotePrintDocument document) { 1125 mWeakDocument = new WeakReference<>(document); 1126 } 1127 1128 @Override onDestroy()1129 public void onDestroy() { 1130 final RemotePrintDocument document = mWeakDocument.get(); 1131 if (document != null) { 1132 document.notifyPrintingAppDied(); 1133 } 1134 } 1135 } 1136 } 1137