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