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