Home | History | Annotate | Download | only in model
      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.annotation.NonNull;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.net.Uri;
     23 import android.os.AsyncTask;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.IBinder.DeathRecipient;
     27 import android.os.ICancellationSignal;
     28 import android.os.Looper;
     29 import android.os.Message;
     30 import android.os.ParcelFileDescriptor;
     31 import android.os.RemoteException;
     32 import android.print.ILayoutResultCallback;
     33 import android.print.IPrintDocumentAdapter;
     34 import android.print.IPrintDocumentAdapterObserver;
     35 import android.print.IWriteResultCallback;
     36 import android.print.PageRange;
     37 import android.print.PrintAttributes;
     38 import android.print.PrintDocumentAdapter;
     39 import android.print.PrintDocumentInfo;
     40 import android.util.Log;
     41 
     42 import com.android.printspooler.R;
     43 import com.android.printspooler.util.PageRangeUtils;
     44 
     45 import libcore.io.IoUtils;
     46 
     47 import java.io.File;
     48 import java.io.FileInputStream;
     49 import java.io.FileOutputStream;
     50 import java.io.IOException;
     51 import java.io.InputStream;
     52 import java.io.OutputStream;
     53 import java.lang.ref.WeakReference;
     54 import java.util.Arrays;
     55 
     56 public final class RemotePrintDocument {
     57     private static final String LOG_TAG = "RemotePrintDocument";
     58 
     59     private static final boolean DEBUG = false;
     60 
     61     private static final long FORCE_CANCEL_TIMEOUT = 1000; // ms
     62 
     63     private static final int STATE_INITIAL = 0;
     64     private static final int STATE_STARTED = 1;
     65     private static final int STATE_UPDATING = 2;
     66     private static final int STATE_UPDATED = 3;
     67     private static final int STATE_FAILED = 4;
     68     private static final int STATE_FINISHED = 5;
     69     private static final int STATE_CANCELING = 6;
     70     private static final int STATE_CANCELED = 7;
     71     private static final int STATE_DESTROYED = 8;
     72 
     73     private final Context mContext;
     74 
     75     private final RemotePrintDocumentInfo mDocumentInfo;
     76     private final UpdateSpec mUpdateSpec = new UpdateSpec();
     77 
     78     private final Looper mLooper;
     79     private final IPrintDocumentAdapter mPrintDocumentAdapter;
     80     private final RemoteAdapterDeathObserver mAdapterDeathObserver;
     81 
     82     private final UpdateResultCallbacks mUpdateCallbacks;
     83 
     84     private final CommandDoneCallback mCommandResultCallback =
     85             new CommandDoneCallback() {
     86         @Override
     87         public void onDone() {
     88             if (mCurrentCommand.isCompleted()) {
     89                 if (mCurrentCommand instanceof LayoutCommand) {
     90                     // If there is a next command after a layout is done, then another
     91                     // update was issued and the next command is another layout, so we
     92                     // do nothing. However, if there is no next command we may need to
     93                     // ask for some pages given we do not already have them or we do
     94                     // but the content has changed.
     95                     if (mNextCommand == null) {
     96                         if (mUpdateSpec.pages != null && (mDocumentInfo.changed
     97                                 || mDocumentInfo.pagesWrittenToFile == null
     98                                 || (mDocumentInfo.info.getPageCount()
     99                                         != PrintDocumentInfo.PAGE_COUNT_UNKNOWN
    100                                 && !PageRangeUtils.contains(mDocumentInfo.pagesWrittenToFile,
    101                                         mUpdateSpec.pages, mDocumentInfo.info.getPageCount())))) {
    102                             mNextCommand = new WriteCommand(mContext, mLooper,
    103                                     mPrintDocumentAdapter, mDocumentInfo,
    104                                     mDocumentInfo.info.getPageCount(), mUpdateSpec.pages,
    105                                     mDocumentInfo.fileProvider, mCommandResultCallback);
    106                         } else {
    107                             if (mUpdateSpec.pages != null) {
    108                                 // If we have the requested pages, update which ones to be printed.
    109                                 mDocumentInfo.pagesInFileToPrint =
    110                                         PageRangeUtils.computeWhichPagesInFileToPrint(
    111                                                 mUpdateSpec.pages, mDocumentInfo.pagesWrittenToFile,
    112                                                 mDocumentInfo.info.getPageCount());
    113                             }
    114                             // Notify we are done.
    115                             mState = STATE_UPDATED;
    116                             mDocumentInfo.updated = true;
    117                             notifyUpdateCompleted();
    118                         }
    119                     }
    120                 } else {
    121                     // We always notify after a write.
    122                     mState = STATE_UPDATED;
    123                     mDocumentInfo.updated = true;
    124                     notifyUpdateCompleted();
    125                 }
    126                 runPendingCommand();
    127             } else if (mCurrentCommand.isFailed()) {
    128                 mState = STATE_FAILED;
    129                 CharSequence error = mCurrentCommand.getError();
    130                 mCurrentCommand = null;
    131                 mNextCommand = null;
    132                 mUpdateSpec.reset();
    133                 notifyUpdateFailed(error);
    134             } else if (mCurrentCommand.isCanceled()) {
    135                 if (mState == STATE_CANCELING) {
    136                     mState = STATE_CANCELED;
    137                     notifyUpdateCanceled();
    138                 }
    139                 runPendingCommand();
    140             }
    141         }
    142     };
    143 
    144     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
    145         @Override
    146         public void binderDied() {
    147             onPrintingAppDied();
    148         }
    149     };
    150 
    151     private int mState = STATE_INITIAL;
    152 
    153     private AsyncCommand mCurrentCommand;
    154     private AsyncCommand mNextCommand;
    155 
    156     public interface RemoteAdapterDeathObserver {
    157         public void onDied();
    158     }
    159 
    160     public interface UpdateResultCallbacks {
    161         public void onUpdateCompleted(RemotePrintDocumentInfo document);
    162         public void onUpdateCanceled();
    163         public void onUpdateFailed(CharSequence error);
    164     }
    165 
    166     public RemotePrintDocument(Context context, IPrintDocumentAdapter adapter,
    167             MutexFileProvider fileProvider, RemoteAdapterDeathObserver deathObserver,
    168             UpdateResultCallbacks callbacks) {
    169         mPrintDocumentAdapter = adapter;
    170         mLooper = context.getMainLooper();
    171         mContext = context;
    172         mAdapterDeathObserver = deathObserver;
    173         mDocumentInfo = new RemotePrintDocumentInfo();
    174         mDocumentInfo.fileProvider = fileProvider;
    175         mUpdateCallbacks = callbacks;
    176         connectToRemoteDocument();
    177     }
    178 
    179     public void start() {
    180         if (DEBUG) {
    181             Log.i(LOG_TAG, "[CALLED] start()");
    182         }
    183         if (mState == STATE_FAILED) {
    184             Log.w(LOG_TAG, "Failed before start.");
    185         } else if (mState == STATE_DESTROYED) {
    186             Log.w(LOG_TAG, "Destroyed before start.");
    187         } else {
    188             if (mState != STATE_INITIAL) {
    189                 throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
    190             }
    191             try {
    192                 mPrintDocumentAdapter.start();
    193                 mState = STATE_STARTED;
    194             } catch (RemoteException re) {
    195                 Log.e(LOG_TAG, "Error calling start()", re);
    196                 mState = STATE_FAILED;
    197             }
    198         }
    199     }
    200 
    201     public boolean update(PrintAttributes attributes, PageRange[] pages, boolean preview) {
    202         boolean willUpdate;
    203 
    204         if (DEBUG) {
    205             Log.i(LOG_TAG, "[CALLED] update()");
    206         }
    207 
    208         if (hasUpdateError()) {
    209             throw new IllegalStateException("Cannot update without a clearing the failure");
    210         }
    211 
    212         if (mState == STATE_INITIAL || mState == STATE_FINISHED || mState == STATE_DESTROYED) {
    213             throw new IllegalStateException("Cannot update in state:" + stateToString(mState));
    214         }
    215 
    216         // We schedule a layout if the constraints changed.
    217         if (!mUpdateSpec.hasSameConstraints(attributes, preview)) {
    218             willUpdate = true;
    219 
    220             // If there is a current command that is running we ask for a
    221             // cancellation and start over.
    222             if (mCurrentCommand != null && (mCurrentCommand.isRunning()
    223                     || mCurrentCommand.isPending())) {
    224                 mCurrentCommand.cancel(false);
    225             }
    226 
    227             // Schedule a layout command.
    228             PrintAttributes oldAttributes = mDocumentInfo.attributes != null
    229                     ? mDocumentInfo.attributes : new PrintAttributes.Builder().build();
    230             AsyncCommand command = new LayoutCommand(mLooper, mPrintDocumentAdapter,
    231                   mDocumentInfo, oldAttributes, attributes, preview, mCommandResultCallback);
    232             scheduleCommand(command);
    233 
    234             mDocumentInfo.updated = false;
    235             mState = STATE_UPDATING;
    236         // If no layout in progress and we don't have all pages - schedule a write.
    237         } else if ((!(mCurrentCommand instanceof LayoutCommand)
    238                 || (!mCurrentCommand.isPending() && !mCurrentCommand.isRunning()))
    239                 && pages != null && !PageRangeUtils.contains(mUpdateSpec.pages, pages,
    240                 mDocumentInfo.info.getPageCount())) {
    241             willUpdate = true;
    242 
    243             // Cancel the current write as a new one is to be scheduled.
    244             if (mCurrentCommand instanceof WriteCommand
    245                     && (mCurrentCommand.isPending() || mCurrentCommand.isRunning())) {
    246                 mCurrentCommand.cancel(false);
    247             }
    248 
    249             // Schedule a write command.
    250             AsyncCommand command = new WriteCommand(mContext, mLooper, mPrintDocumentAdapter,
    251                     mDocumentInfo, mDocumentInfo.info.getPageCount(), pages,
    252                     mDocumentInfo.fileProvider, mCommandResultCallback);
    253             scheduleCommand(command);
    254 
    255             mDocumentInfo.updated = false;
    256             mState = STATE_UPDATING;
    257         } else {
    258             willUpdate = false;
    259             if (DEBUG) {
    260                 Log.i(LOG_TAG, "[SKIPPING] No update needed");
    261             }
    262         }
    263 
    264         // Keep track of what is requested.
    265         mUpdateSpec.update(attributes, preview, pages);
    266 
    267         runPendingCommand();
    268 
    269         return willUpdate;
    270     }
    271 
    272     public void finish() {
    273         if (DEBUG) {
    274             Log.i(LOG_TAG, "[CALLED] finish()");
    275         }
    276         if (mState != STATE_STARTED && mState != STATE_UPDATED
    277                 && mState != STATE_FAILED && mState != STATE_CANCELING
    278                 && mState != STATE_CANCELED && mState != STATE_DESTROYED) {
    279             throw new IllegalStateException("Cannot finish in state:"
    280                     + stateToString(mState));
    281         }
    282         try {
    283             mPrintDocumentAdapter.finish();
    284             mState = STATE_FINISHED;
    285         } catch (RemoteException re) {
    286             Log.e(LOG_TAG, "Error calling finish()");
    287             mState = STATE_FAILED;
    288         }
    289     }
    290 
    291     public void cancel(boolean force) {
    292         if (DEBUG) {
    293             Log.i(LOG_TAG, "[CALLED] cancel(" + force + ")");
    294         }
    295 
    296         mNextCommand = null;
    297 
    298         if (mState != STATE_UPDATING) {
    299             return;
    300         }
    301 
    302         mState = STATE_CANCELING;
    303 
    304         mCurrentCommand.cancel(force);
    305     }
    306 
    307     public void destroy() {
    308         if (DEBUG) {
    309             Log.i(LOG_TAG, "[CALLED] destroy()");
    310         }
    311         if (mState == STATE_DESTROYED) {
    312             throw new IllegalStateException("Cannot destroy in state:" + stateToString(mState));
    313         }
    314 
    315         mState = STATE_DESTROYED;
    316 
    317         disconnectFromRemoteDocument();
    318     }
    319 
    320     public void kill(String reason) {
    321         if (DEBUG) {
    322             Log.i(LOG_TAG, "[CALLED] kill()");
    323         }
    324 
    325         try {
    326             mPrintDocumentAdapter.kill(reason);
    327         } catch (RemoteException re) {
    328             Log.e(LOG_TAG, "Error calling kill()", re);
    329         }
    330     }
    331 
    332     public boolean isUpdating() {
    333         return mState == STATE_UPDATING || mState == STATE_CANCELING;
    334     }
    335 
    336     public boolean isDestroyed() {
    337         return mState == STATE_DESTROYED;
    338     }
    339 
    340     public boolean hasUpdateError() {
    341         return mState == STATE_FAILED;
    342     }
    343 
    344     public boolean hasLaidOutPages() {
    345         return mDocumentInfo.info != null
    346                 && mDocumentInfo.info.getPageCount() > 0;
    347     }
    348 
    349     public void clearUpdateError() {
    350         if (!hasUpdateError()) {
    351             throw new IllegalStateException("No update error to clear");
    352         }
    353         mState = STATE_STARTED;
    354     }
    355 
    356     public RemotePrintDocumentInfo getDocumentInfo() {
    357         return mDocumentInfo;
    358     }
    359 
    360     public void writeContent(ContentResolver contentResolver, Uri uri) {
    361         File file = null;
    362         InputStream in = null;
    363         OutputStream out = null;
    364         try {
    365             file = mDocumentInfo.fileProvider.acquireFile(null);
    366             in = new FileInputStream(file);
    367             out = contentResolver.openOutputStream(uri);
    368             final byte[] buffer = new byte[8192];
    369             while (true) {
    370                 final int readByteCount = in.read(buffer);
    371                 if (readByteCount < 0) {
    372                     break;
    373                 }
    374                 out.write(buffer, 0, readByteCount);
    375             }
    376         } catch (IOException e) {
    377             Log.e(LOG_TAG, "Error writing document content.", e);
    378         } finally {
    379             IoUtils.closeQuietly(in);
    380             IoUtils.closeQuietly(out);
    381             if (file != null) {
    382                 mDocumentInfo.fileProvider.releaseFile();
    383             }
    384         }
    385     }
    386 
    387     private void notifyUpdateCanceled() {
    388         if (DEBUG) {
    389             Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()");
    390         }
    391         mUpdateCallbacks.onUpdateCanceled();
    392     }
    393 
    394     private void notifyUpdateCompleted() {
    395         if (DEBUG) {
    396             Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
    397         }
    398         mUpdateCallbacks.onUpdateCompleted(mDocumentInfo);
    399     }
    400 
    401     private void notifyUpdateFailed(CharSequence error) {
    402         if (DEBUG) {
    403             Log.i(LOG_TAG, "[CALLING] notifyUpdateFailed()");
    404         }
    405         mUpdateCallbacks.onUpdateFailed(error);
    406     }
    407 
    408     private void connectToRemoteDocument() {
    409         try {
    410             mPrintDocumentAdapter.asBinder().linkToDeath(mDeathRecipient, 0);
    411         } catch (RemoteException re) {
    412             Log.w(LOG_TAG, "The printing process is dead.");
    413             destroy();
    414             return;
    415         }
    416 
    417         try {
    418             mPrintDocumentAdapter.setObserver(new PrintDocumentAdapterObserver(this));
    419         } catch (RemoteException re) {
    420             Log.w(LOG_TAG, "Error setting observer to the print adapter.");
    421             destroy();
    422         }
    423     }
    424 
    425     private void disconnectFromRemoteDocument() {
    426         try {
    427             mPrintDocumentAdapter.setObserver(null);
    428         } catch (RemoteException re) {
    429             Log.w(LOG_TAG, "Error setting observer to the print adapter.");
    430             // Keep going - best effort...
    431         }
    432 
    433         mPrintDocumentAdapter.asBinder().unlinkToDeath(mDeathRecipient, 0);
    434     }
    435 
    436     private void scheduleCommand(AsyncCommand command) {
    437         if (mCurrentCommand == null) {
    438             mCurrentCommand = command;
    439         } else {
    440             mNextCommand = command;
    441         }
    442     }
    443 
    444     private void runPendingCommand() {
    445         if (mCurrentCommand != null
    446                 && (mCurrentCommand.isCompleted()
    447                         || mCurrentCommand.isCanceled())) {
    448             mCurrentCommand = mNextCommand;
    449             mNextCommand = null;
    450         }
    451 
    452         if (mCurrentCommand != null) {
    453             if (mCurrentCommand.isPending()) {
    454                 mCurrentCommand.run();
    455 
    456                 mState = STATE_UPDATING;
    457             }
    458         } else {
    459             mState = STATE_UPDATED;
    460         }
    461     }
    462 
    463     private static String stateToString(int state) {
    464         switch (state) {
    465             case STATE_FINISHED: {
    466                 return "STATE_FINISHED";
    467             }
    468             case STATE_FAILED: {
    469                 return "STATE_FAILED";
    470             }
    471             case STATE_STARTED: {
    472                 return "STATE_STARTED";
    473             }
    474             case STATE_UPDATING: {
    475                 return "STATE_UPDATING";
    476             }
    477             case STATE_UPDATED: {
    478                 return "STATE_UPDATED";
    479             }
    480             case STATE_CANCELING: {
    481                 return "STATE_CANCELING";
    482             }
    483             case STATE_CANCELED: {
    484                 return "STATE_CANCELED";
    485             }
    486             case STATE_DESTROYED: {
    487                 return "STATE_DESTROYED";
    488             }
    489             default: {
    490                 return "STATE_UNKNOWN";
    491             }
    492         }
    493     }
    494 
    495     static final class UpdateSpec {
    496         final PrintAttributes attributes = new PrintAttributes.Builder().build();
    497         boolean preview;
    498         PageRange[] pages;
    499 
    500         public void update(PrintAttributes attributes, boolean preview,
    501                 PageRange[] pages) {
    502             this.attributes.copyFrom(attributes);
    503             this.preview = preview;
    504             this.pages = (pages != null) ? Arrays.copyOf(pages, pages.length) : null;
    505         }
    506 
    507         public void reset() {
    508             attributes.clear();
    509             preview = false;
    510             pages = null;
    511         }
    512 
    513         public boolean hasSameConstraints(PrintAttributes attributes, boolean preview) {
    514             return this.attributes.equals(attributes) && this.preview == preview;
    515         }
    516     }
    517 
    518     public static final class RemotePrintDocumentInfo {
    519         public PrintAttributes attributes;
    520         public Bundle metadata;
    521         public PrintDocumentInfo info;
    522 
    523         /**
    524          * Which pages out of the ones written to the file to print. This is not indexed by the
    525          * document pages, but by the page number in the file.
    526          * <p>E.g. if a document has 10 pages, we want pages 4-5 and 7, but only page 3-9 are in the
    527          * file. This would contain 1-2 and 4.</p>
    528          *
    529          * @see PageRangeUtils#computeWhichPagesInFileToPrint
    530          */
    531         public PageRange[] pagesInFileToPrint;
    532 
    533         /** Pages of the whole document that are currently written to file */
    534         public PageRange[] pagesWrittenToFile;
    535 
    536         public MutexFileProvider fileProvider;
    537         public boolean changed;
    538         public boolean updated;
    539         public boolean laidout;
    540     }
    541 
    542     private interface CommandDoneCallback {
    543         public void onDone();
    544     }
    545 
    546     private static abstract class AsyncCommand implements Runnable {
    547         private static final int STATE_PENDING = 0;
    548         private static final int STATE_RUNNING = 1;
    549         private static final int STATE_COMPLETED = 2;
    550         private static final int STATE_CANCELED = 3;
    551         private static final int STATE_CANCELING = 4;
    552         private static final int STATE_FAILED = 5;
    553 
    554         private static int sSequenceCounter;
    555 
    556         protected final int mSequence = sSequenceCounter++;
    557         protected final IPrintDocumentAdapter mAdapter;
    558         protected final RemotePrintDocumentInfo mDocument;
    559 
    560         protected final CommandDoneCallback mDoneCallback;
    561 
    562         private final Handler mHandler;
    563 
    564         protected ICancellationSignal mCancellation;
    565 
    566         private CharSequence mError;
    567 
    568         private int mState = STATE_PENDING;
    569 
    570         public AsyncCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document,
    571                 CommandDoneCallback doneCallback) {
    572             mHandler = new AsyncCommandHandler(looper);
    573             mAdapter = adapter;
    574             mDocument = document;
    575             mDoneCallback = doneCallback;
    576         }
    577 
    578         protected final boolean isCanceling() {
    579             return mState == STATE_CANCELING;
    580         }
    581 
    582         public final boolean isCanceled() {
    583             return mState == STATE_CANCELED;
    584         }
    585 
    586         /**
    587          * If a force cancel is pending, remove it. This is usually called when a command returns
    588          * and thereby does not need to be canceled anymore.
    589          */
    590         protected void removeForceCancel() {
    591             if (DEBUG) {
    592                 if (mHandler.hasMessages(AsyncCommandHandler.MSG_FORCE_CANCEL)) {
    593                     Log.i(LOG_TAG, "[FORCE CANCEL] Removed");
    594                 }
    595             }
    596 
    597             mHandler.removeMessages(AsyncCommandHandler.MSG_FORCE_CANCEL);
    598         }
    599 
    600         /**
    601          * Cancel the current command.
    602          *
    603          * @param force If set, does not wait for the {@link PrintDocumentAdapter} to cancel. This
    604          *              should only be used if this is the last command send to the as otherwise the
    605          *              {@link PrintDocumentAdapter adapter} might get commands while it is still
    606          *              running the old one.
    607          */
    608         public final void cancel(boolean force) {
    609             if (isRunning()) {
    610                 canceling();
    611                 if (mCancellation != null) {
    612                     try {
    613                         mCancellation.cancel();
    614                     } catch (RemoteException re) {
    615                         Log.w(LOG_TAG, "Error while canceling", re);
    616                     }
    617                 }
    618             }
    619 
    620             if (isCanceling()) {
    621                 if (force) {
    622                     if (DEBUG) {
    623                         Log.i(LOG_TAG, "[FORCE CANCEL] queued");
    624                     }
    625                     mHandler.sendMessageDelayed(
    626                             mHandler.obtainMessage(AsyncCommandHandler.MSG_FORCE_CANCEL),
    627                             FORCE_CANCEL_TIMEOUT);
    628                 }
    629 
    630                 return;
    631             }
    632 
    633             canceled();
    634 
    635             // Done.
    636             mDoneCallback.onDone();
    637         }
    638 
    639         protected final void canceling() {
    640             if (mState != STATE_PENDING && mState != STATE_RUNNING) {
    641                 throw new IllegalStateException("Command not pending or running.");
    642             }
    643             mState = STATE_CANCELING;
    644         }
    645 
    646         protected final void canceled() {
    647             if (mState != STATE_CANCELING) {
    648                 throw new IllegalStateException("Not canceling.");
    649             }
    650             mState = STATE_CANCELED;
    651         }
    652 
    653         public final boolean isPending() {
    654             return mState == STATE_PENDING;
    655         }
    656 
    657         protected final void running() {
    658             if (mState != STATE_PENDING) {
    659                 throw new IllegalStateException("Not pending.");
    660             }
    661             mState = STATE_RUNNING;
    662         }
    663 
    664         public final boolean isRunning() {
    665             return mState == STATE_RUNNING;
    666         }
    667 
    668         protected final void completed() {
    669             if (mState != STATE_RUNNING && mState != STATE_CANCELING) {
    670                 throw new IllegalStateException("Not running.");
    671             }
    672             mState = STATE_COMPLETED;
    673         }
    674 
    675         public final boolean isCompleted() {
    676             return mState == STATE_COMPLETED;
    677         }
    678 
    679         protected final void failed(CharSequence error) {
    680             if (mState != STATE_RUNNING && mState != STATE_CANCELING) {
    681                 throw new IllegalStateException("Not running.");
    682             }
    683             mState = STATE_FAILED;
    684 
    685             mError = error;
    686         }
    687 
    688         public final boolean isFailed() {
    689             return mState == STATE_FAILED;
    690         }
    691 
    692         public CharSequence getError() {
    693             return mError;
    694         }
    695 
    696         /**
    697          * Handler for the async command.
    698          */
    699         private class AsyncCommandHandler extends Handler {
    700             /** Message indicated the desire for to force cancel a command */
    701             final static int MSG_FORCE_CANCEL = 0;
    702 
    703             AsyncCommandHandler(@NonNull Looper looper) {
    704                 super(looper);
    705             }
    706 
    707             @Override
    708             public void handleMessage(Message msg) {
    709                 switch (msg.what) {
    710                     case MSG_FORCE_CANCEL:
    711                         if (isCanceling()) {
    712                             if (DEBUG) {
    713                                 Log.i(LOG_TAG, "[FORCE CANCEL] executed");
    714                             }
    715                             failed("Command did not respond to cancellation in "
    716                                     + FORCE_CANCEL_TIMEOUT + " ms");
    717 
    718                             mDoneCallback.onDone();
    719                         }
    720                         break;
    721                     default:
    722                         // not reached;
    723                 }
    724             }
    725         }
    726     }
    727 
    728     private static final class LayoutCommand extends AsyncCommand {
    729         private final PrintAttributes mOldAttributes = new PrintAttributes.Builder().build();
    730         private final PrintAttributes mNewAttributes = new PrintAttributes.Builder().build();
    731         private final Bundle mMetadata = new Bundle();
    732 
    733         private final ILayoutResultCallback mRemoteResultCallback;
    734 
    735         private final Handler mHandler;
    736 
    737         public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter,
    738                 RemotePrintDocumentInfo document, PrintAttributes oldAttributes,
    739                 PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) {
    740             super(looper, adapter, document, callback);
    741             mHandler = new LayoutHandler(looper);
    742             mRemoteResultCallback = new LayoutResultCallback(mHandler);
    743             mOldAttributes.copyFrom(oldAttributes);
    744             mNewAttributes.copyFrom(newAttributes);
    745             mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, preview);
    746         }
    747 
    748         @Override
    749         public void run() {
    750             running();
    751 
    752             try {
    753                 if (DEBUG) {
    754                     Log.i(LOG_TAG, "[PERFORMING] layout");
    755                 }
    756                 mDocument.changed = false;
    757                 mAdapter.layout(mOldAttributes, mNewAttributes, mRemoteResultCallback,
    758                         mMetadata, mSequence);
    759             } catch (RemoteException re) {
    760                 Log.e(LOG_TAG, "Error calling layout", re);
    761                 handleOnLayoutFailed(null, mSequence);
    762             }
    763         }
    764 
    765         private void handleOnLayoutStarted(ICancellationSignal cancellation, int sequence) {
    766             if (sequence != mSequence) {
    767                 return;
    768             }
    769 
    770             if (DEBUG) {
    771                 Log.i(LOG_TAG, "[CALLBACK] onLayoutStarted");
    772             }
    773 
    774             if (isCanceling()) {
    775                 try {
    776                     cancellation.cancel();
    777                 } catch (RemoteException re) {
    778                     Log.e(LOG_TAG, "Error cancelling", re);
    779                     handleOnLayoutFailed(null, mSequence);
    780                 }
    781             } else {
    782                 mCancellation = cancellation;
    783             }
    784         }
    785 
    786         private void handleOnLayoutFinished(PrintDocumentInfo info,
    787                 boolean changed, int sequence) {
    788             if (sequence != mSequence) {
    789                 return;
    790             }
    791 
    792             if (DEBUG) {
    793                 Log.i(LOG_TAG, "[CALLBACK] onLayoutFinished");
    794             }
    795 
    796             completed();
    797 
    798             // If the document description changed or the content in the
    799             // document changed, the we need to invalidate the pages.
    800             if (changed || !equalsIgnoreSize(mDocument.info, info)) {
    801                 // If the content changed we throw away all pages as
    802                 // we will request them again with the new content.
    803                 mDocument.pagesWrittenToFile = null;
    804                 mDocument.pagesInFileToPrint = null;
    805                 mDocument.changed = true;
    806             }
    807 
    808             // Update the document with data from the layout pass.
    809             mDocument.attributes = mNewAttributes;
    810             mDocument.metadata = mMetadata;
    811             mDocument.laidout = true;
    812             mDocument.info = info;
    813 
    814             // Release the remote cancellation interface.
    815             mCancellation = null;
    816 
    817             // Done.
    818             mDoneCallback.onDone();
    819         }
    820 
    821         private void handleOnLayoutFailed(CharSequence error, int sequence) {
    822             if (sequence != mSequence) {
    823                 return;
    824             }
    825 
    826             if (DEBUG) {
    827                 Log.i(LOG_TAG, "[CALLBACK] onLayoutFailed");
    828             }
    829 
    830             mDocument.laidout = false;
    831 
    832             failed(error);
    833 
    834             // Release the remote cancellation interface.
    835             mCancellation = null;
    836 
    837             // Failed.
    838             mDoneCallback.onDone();
    839         }
    840 
    841         private void handleOnLayoutCanceled(int sequence) {
    842             if (sequence != mSequence) {
    843                 return;
    844             }
    845 
    846             if (DEBUG) {
    847                 Log.i(LOG_TAG, "[CALLBACK] onLayoutCanceled");
    848             }
    849 
    850             canceled();
    851 
    852             // Release the remote cancellation interface.
    853             mCancellation = null;
    854 
    855             // Done.
    856             mDoneCallback.onDone();
    857         }
    858 
    859         private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
    860             if (lhs == rhs) {
    861                 return true;
    862             }
    863             if (lhs == null) {
    864                 return false;
    865             } else {
    866                 if (rhs == null) {
    867                     return false;
    868                 }
    869                 if (lhs.getContentType() != rhs.getContentType()
    870                         || lhs.getPageCount() != rhs.getPageCount()) {
    871                     return false;
    872                 }
    873             }
    874             return true;
    875         }
    876 
    877         private final class LayoutHandler extends Handler {
    878             public static final int MSG_ON_LAYOUT_STARTED = 1;
    879             public static final int MSG_ON_LAYOUT_FINISHED = 2;
    880             public static final int MSG_ON_LAYOUT_FAILED = 3;
    881             public static final int MSG_ON_LAYOUT_CANCELED = 4;
    882 
    883             public LayoutHandler(Looper looper) {
    884                 super(looper, null, false);
    885             }
    886 
    887             @Override
    888             public void handleMessage(Message message) {
    889                 // The command might have been force canceled, see
    890                 // AsyncCommand.AsyncCommandHandler#handleMessage
    891                 if (isFailed()) {
    892                     if (DEBUG) {
    893                         Log.i(LOG_TAG, "[CALLBACK] on failed layout command");
    894                     }
    895 
    896                     return;
    897                 }
    898 
    899                 int sequence;
    900                 int what = message.what;
    901                 CharSequence error = null;
    902                 switch (what) {
    903                     case MSG_ON_LAYOUT_FINISHED:
    904                         removeForceCancel();
    905                         sequence = message.arg2;
    906                         break;
    907                     case MSG_ON_LAYOUT_FAILED:
    908                         error = (CharSequence) message.obj;
    909                         removeForceCancel();
    910                         sequence = message.arg1;
    911                         break;
    912                     case MSG_ON_LAYOUT_CANCELED:
    913                         if (!isCanceling()) {
    914                             Log.w(LOG_TAG, "Unexpected cancel");
    915                             what = MSG_ON_LAYOUT_FAILED;
    916                         }
    917                         removeForceCancel();
    918                         sequence = message.arg1;
    919                         break;
    920                     case MSG_ON_LAYOUT_STARTED:
    921                         // Don't remote force-cancel as command is still running and might need to
    922                         // be canceled later
    923                         sequence = message.arg1;
    924                         break;
    925                     default:
    926                         // not reached
    927                         sequence = -1;
    928                 }
    929 
    930                 // If we are canceling any result is treated as a cancel
    931                 if (isCanceling() && what != MSG_ON_LAYOUT_STARTED) {
    932                     what = MSG_ON_LAYOUT_CANCELED;
    933                 }
    934 
    935                 switch (what) {
    936                     case MSG_ON_LAYOUT_STARTED: {
    937                         ICancellationSignal cancellation = (ICancellationSignal) message.obj;
    938                         handleOnLayoutStarted(cancellation, sequence);
    939                     } break;
    940 
    941                     case MSG_ON_LAYOUT_FINISHED: {
    942                         PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
    943                         final boolean changed = (message.arg1 == 1);
    944                         handleOnLayoutFinished(info, changed, sequence);
    945                     } break;
    946 
    947                     case MSG_ON_LAYOUT_FAILED: {
    948                         handleOnLayoutFailed(error, sequence);
    949                     } break;
    950 
    951                     case MSG_ON_LAYOUT_CANCELED: {
    952                         handleOnLayoutCanceled(sequence);
    953                     } break;
    954                 }
    955             }
    956         }
    957 
    958         private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
    959             private final WeakReference<Handler> mWeakHandler;
    960 
    961             public LayoutResultCallback(Handler handler) {
    962                 mWeakHandler = new WeakReference<>(handler);
    963             }
    964 
    965             @Override
    966             public void onLayoutStarted(ICancellationSignal cancellation, int sequence) {
    967                 Handler handler = mWeakHandler.get();
    968                 if (handler != null) {
    969                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_STARTED,
    970                             sequence, 0, cancellation).sendToTarget();
    971                 }
    972             }
    973 
    974             @Override
    975             public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
    976                 Handler handler = mWeakHandler.get();
    977                 if (handler != null) {
    978                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FINISHED,
    979                             changed ? 1 : 0, sequence, info).sendToTarget();
    980                 }
    981             }
    982 
    983             @Override
    984             public void onLayoutFailed(CharSequence error, int sequence) {
    985                 Handler handler = mWeakHandler.get();
    986                 if (handler != null) {
    987                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FAILED,
    988                             sequence, 0, error).sendToTarget();
    989                 }
    990             }
    991 
    992             @Override
    993             public void onLayoutCanceled(int sequence) {
    994                 Handler handler = mWeakHandler.get();
    995                 if (handler != null) {
    996                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_CANCELED,
    997                             sequence, 0).sendToTarget();
    998                 }
    999             }
   1000         }
   1001     }
   1002 
   1003     private static final class WriteCommand extends AsyncCommand {
   1004         private final int mPageCount;
   1005         private final PageRange[] mPages;
   1006         private final MutexFileProvider mFileProvider;
   1007 
   1008         private final IWriteResultCallback mRemoteResultCallback;
   1009         private final CommandDoneCallback mWriteDoneCallback;
   1010 
   1011         private final Context mContext;
   1012         private final Handler mHandler;
   1013 
   1014         public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter,
   1015                 RemotePrintDocumentInfo document, int pageCount, PageRange[] pages,
   1016                 MutexFileProvider fileProvider, CommandDoneCallback callback) {
   1017             super(looper, adapter, document, callback);
   1018             mContext = context;
   1019             mHandler = new WriteHandler(looper);
   1020             mRemoteResultCallback = new WriteResultCallback(mHandler);
   1021             mPageCount = pageCount;
   1022             mPages = Arrays.copyOf(pages, pages.length);
   1023             mFileProvider = fileProvider;
   1024             mWriteDoneCallback = callback;
   1025         }
   1026 
   1027         @Override
   1028         public void run() {
   1029             running();
   1030 
   1031             // This is a long running operation as we will be reading fully
   1032             // the written data. In case of a cancellation, we ask the client
   1033             // to stop writing data and close the file descriptor after
   1034             // which we will reach the end of the stream, thus stop reading.
   1035             new AsyncTask<Void, Void, Void>() {
   1036                 @Override
   1037                 protected Void doInBackground(Void... params) {
   1038                     File file = null;
   1039                     InputStream in = null;
   1040                     OutputStream out = null;
   1041                     ParcelFileDescriptor source = null;
   1042                     ParcelFileDescriptor sink = null;
   1043                     try {
   1044                         file = mFileProvider.acquireFile(null);
   1045                         ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
   1046                         source = pipe[0];
   1047                         sink = pipe[1];
   1048 
   1049                         in = new FileInputStream(source.getFileDescriptor());
   1050                         out = new FileOutputStream(file);
   1051 
   1052                         // Async call to initiate the other process writing the data.
   1053                         if (DEBUG) {
   1054                             Log.i(LOG_TAG, "[PERFORMING] write");
   1055                         }
   1056                         mAdapter.write(mPages, sink, mRemoteResultCallback, mSequence);
   1057 
   1058                         // Close the source. It is now held by the client.
   1059                         sink.close();
   1060                         sink = null;
   1061 
   1062                         // Read the data.
   1063                         final byte[] buffer = new byte[8192];
   1064                         while (true) {
   1065                             final int readByteCount = in.read(buffer);
   1066                             if (readByteCount < 0) {
   1067                                 break;
   1068                             }
   1069                             out.write(buffer, 0, readByteCount);
   1070                         }
   1071                     } catch (RemoteException | IOException e) {
   1072                         Log.e(LOG_TAG, "Error calling write()", e);
   1073                     } finally {
   1074                         IoUtils.closeQuietly(in);
   1075                         IoUtils.closeQuietly(out);
   1076                         IoUtils.closeQuietly(sink);
   1077                         IoUtils.closeQuietly(source);
   1078                         if (file != null) {
   1079                             mFileProvider.releaseFile();
   1080                         }
   1081                     }
   1082                     return null;
   1083                 }
   1084             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
   1085         }
   1086 
   1087         private void handleOnWriteStarted(ICancellationSignal cancellation, int sequence) {
   1088             if (sequence != mSequence) {
   1089                 return;
   1090             }
   1091 
   1092             if (DEBUG) {
   1093                 Log.i(LOG_TAG, "[CALLBACK] onWriteStarted");
   1094             }
   1095 
   1096             if (isCanceling()) {
   1097                 try {
   1098                     cancellation.cancel();
   1099                 } catch (RemoteException re) {
   1100                     Log.e(LOG_TAG, "Error cancelling", re);
   1101                     handleOnWriteFailed(null, sequence);
   1102                 }
   1103             } else {
   1104                 mCancellation = cancellation;
   1105             }
   1106         }
   1107 
   1108         private void handleOnWriteFinished(PageRange[] pages, int sequence) {
   1109             if (sequence != mSequence) {
   1110                 return;
   1111             }
   1112 
   1113             if (DEBUG) {
   1114                 Log.i(LOG_TAG, "[CALLBACK] onWriteFinished");
   1115             }
   1116 
   1117             PageRange[] writtenPages = PageRangeUtils.normalize(pages);
   1118             PageRange[] printedPages = PageRangeUtils.computeWhichPagesInFileToPrint(
   1119                     mPages, writtenPages, mPageCount);
   1120 
   1121             // Handle if we got invalid pages
   1122             if (printedPages != null) {
   1123                 mDocument.pagesWrittenToFile = writtenPages;
   1124                 mDocument.pagesInFileToPrint = printedPages;
   1125                 completed();
   1126             } else {
   1127                 mDocument.pagesWrittenToFile = null;
   1128                 mDocument.pagesInFileToPrint = null;
   1129                 failed(mContext.getString(R.string.print_error_default_message));
   1130             }
   1131 
   1132             // Release the remote cancellation interface.
   1133             mCancellation = null;
   1134 
   1135             // Done.
   1136             mWriteDoneCallback.onDone();
   1137         }
   1138 
   1139         private void handleOnWriteFailed(CharSequence error, int sequence) {
   1140             if (sequence != mSequence) {
   1141                 return;
   1142             }
   1143 
   1144             if (DEBUG) {
   1145                 Log.i(LOG_TAG, "[CALLBACK] onWriteFailed");
   1146             }
   1147 
   1148             failed(error);
   1149 
   1150             // Release the remote cancellation interface.
   1151             mCancellation = null;
   1152 
   1153             // Done.
   1154             mWriteDoneCallback.onDone();
   1155         }
   1156 
   1157         private void handleOnWriteCanceled(int sequence) {
   1158             if (sequence != mSequence) {
   1159                 return;
   1160             }
   1161 
   1162             if (DEBUG) {
   1163                 Log.i(LOG_TAG, "[CALLBACK] onWriteCanceled");
   1164             }
   1165 
   1166             canceled();
   1167 
   1168             // Release the remote cancellation interface.
   1169             mCancellation = null;
   1170 
   1171             // Done.
   1172             mWriteDoneCallback.onDone();
   1173         }
   1174 
   1175         private final class WriteHandler extends Handler {
   1176             public static final int MSG_ON_WRITE_STARTED = 1;
   1177             public static final int MSG_ON_WRITE_FINISHED = 2;
   1178             public static final int MSG_ON_WRITE_FAILED = 3;
   1179             public static final int MSG_ON_WRITE_CANCELED = 4;
   1180 
   1181             public WriteHandler(Looper looper) {
   1182                 super(looper, null, false);
   1183             }
   1184 
   1185             @Override
   1186             public void handleMessage(Message message) {
   1187                 // The command might have been force canceled, see
   1188                 // AsyncCommand.AsyncCommandHandler#handleMessage
   1189                 if (isFailed()) {
   1190                     if (DEBUG) {
   1191                         Log.i(LOG_TAG, "[CALLBACK] on failed write command");
   1192                     }
   1193 
   1194                     return;
   1195                 }
   1196 
   1197                 int what = message.what;
   1198                 CharSequence error = null;
   1199                 int sequence = message.arg1;
   1200                 switch (what) {
   1201                     case MSG_ON_WRITE_CANCELED:
   1202                         if (!isCanceling()) {
   1203                             Log.w(LOG_TAG, "Unexpected cancel");
   1204                             what = MSG_ON_WRITE_FAILED;
   1205                         }
   1206                         removeForceCancel();
   1207                         break;
   1208                     case MSG_ON_WRITE_FAILED:
   1209                         error = (CharSequence) message.obj;
   1210                         // $FALL-THROUGH
   1211                     case MSG_ON_WRITE_FINISHED:
   1212                         removeForceCancel();
   1213                         // $FALL-THROUGH
   1214                     case MSG_ON_WRITE_STARTED:
   1215                         // Don't remote force-cancel as command is still running and might need to
   1216                         // be canceled later
   1217                         break;
   1218                 }
   1219 
   1220                 // If we are canceling any result is treated as a cancel
   1221                 if (isCanceling() && what != MSG_ON_WRITE_STARTED) {
   1222                     what = MSG_ON_WRITE_CANCELED;
   1223                 }
   1224 
   1225                 switch (what) {
   1226                     case MSG_ON_WRITE_STARTED: {
   1227                         ICancellationSignal cancellation = (ICancellationSignal) message.obj;
   1228                         handleOnWriteStarted(cancellation, sequence);
   1229                     } break;
   1230 
   1231                     case MSG_ON_WRITE_FINISHED: {
   1232                         PageRange[] pages = (PageRange[]) message.obj;
   1233                         handleOnWriteFinished(pages, sequence);
   1234                     } break;
   1235 
   1236                     case MSG_ON_WRITE_FAILED: {
   1237                         handleOnWriteFailed(error, sequence);
   1238                     } break;
   1239 
   1240                     case MSG_ON_WRITE_CANCELED: {
   1241                         handleOnWriteCanceled(sequence);
   1242                     } break;
   1243                 }
   1244             }
   1245         }
   1246 
   1247         private static final class WriteResultCallback extends IWriteResultCallback.Stub {
   1248             private final WeakReference<Handler> mWeakHandler;
   1249 
   1250             public WriteResultCallback(Handler handler) {
   1251                 mWeakHandler = new WeakReference<>(handler);
   1252             }
   1253 
   1254             @Override
   1255             public void onWriteStarted(ICancellationSignal cancellation, int sequence) {
   1256                 Handler handler = mWeakHandler.get();
   1257                 if (handler != null) {
   1258                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_STARTED,
   1259                             sequence, 0, cancellation).sendToTarget();
   1260                 }
   1261             }
   1262 
   1263             @Override
   1264             public void onWriteFinished(PageRange[] pages, int sequence) {
   1265                 Handler handler = mWeakHandler.get();
   1266                 if (handler != null) {
   1267                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FINISHED,
   1268                             sequence, 0, pages).sendToTarget();
   1269                 }
   1270             }
   1271 
   1272             @Override
   1273             public void onWriteFailed(CharSequence error, int sequence) {
   1274                 Handler handler = mWeakHandler.get();
   1275                 if (handler != null) {
   1276                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FAILED,
   1277                         sequence, 0, error).sendToTarget();
   1278                 }
   1279             }
   1280 
   1281             @Override
   1282             public void onWriteCanceled(int sequence) {
   1283                 Handler handler = mWeakHandler.get();
   1284                 if (handler != null) {
   1285                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_CANCELED,
   1286                         sequence, 0).sendToTarget();
   1287                 }
   1288             }
   1289         }
   1290     }
   1291 
   1292     private void onPrintingAppDied() {
   1293         mState = STATE_FAILED;
   1294         new Handler(mLooper).post(new Runnable() {
   1295             @Override
   1296             public void run() {
   1297                 mAdapterDeathObserver.onDied();
   1298             }
   1299         });
   1300     }
   1301 
   1302     private static final class PrintDocumentAdapterObserver
   1303             extends IPrintDocumentAdapterObserver.Stub {
   1304         private final WeakReference<RemotePrintDocument> mWeakDocument;
   1305 
   1306         public PrintDocumentAdapterObserver(RemotePrintDocument document) {
   1307             mWeakDocument = new WeakReference<>(document);
   1308         }
   1309 
   1310         @Override
   1311         public void onDestroy() {
   1312             final RemotePrintDocument document = mWeakDocument.get();
   1313             if (document != null) {
   1314                 document.onPrintingAppDied();
   1315             }
   1316         }
   1317     }
   1318 }
   1319