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