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