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