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