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