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