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