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