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