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             onPrintingAppDied();
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()");
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 
kill(String reason)307     public void kill(String reason) {
308         if (DEBUG) {
309             Log.i(LOG_TAG, "[CALLED] kill()");
310         }
311 
312         try {
313             mPrintDocumentAdapter.kill(reason);
314         } catch (RemoteException re) {
315             Log.e(LOG_TAG, "Error calling kill()", re);
316         }
317     }
318 
isUpdating()319     public boolean isUpdating() {
320         return mState == STATE_UPDATING || mState == STATE_CANCELING;
321     }
322 
isDestroyed()323     public boolean isDestroyed() {
324         return mState == STATE_DESTROYED;
325     }
326 
hasUpdateError()327     public boolean hasUpdateError() {
328         return mState == STATE_FAILED;
329     }
330 
hasLaidOutPages()331     public boolean hasLaidOutPages() {
332         return mDocumentInfo.info != null
333                 && mDocumentInfo.info.getPageCount() > 0;
334     }
335 
clearUpdateError()336     public void clearUpdateError() {
337         if (!hasUpdateError()) {
338             throw new IllegalStateException("No update error to clear");
339         }
340         mState = STATE_STARTED;
341     }
342 
getDocumentInfo()343     public RemotePrintDocumentInfo getDocumentInfo() {
344         return mDocumentInfo;
345     }
346 
writeContent(ContentResolver contentResolver, Uri uri)347     public void writeContent(ContentResolver contentResolver, Uri uri) {
348         File file = null;
349         InputStream in = null;
350         OutputStream out = null;
351         try {
352             file = mDocumentInfo.fileProvider.acquireFile(null);
353             in = new FileInputStream(file);
354             out = contentResolver.openOutputStream(uri);
355             final byte[] buffer = new byte[8192];
356             while (true) {
357                 final int readByteCount = in.read(buffer);
358                 if (readByteCount < 0) {
359                     break;
360                 }
361                 out.write(buffer, 0, readByteCount);
362             }
363         } catch (IOException e) {
364             Log.e(LOG_TAG, "Error writing document content.", e);
365         } finally {
366             IoUtils.closeQuietly(in);
367             IoUtils.closeQuietly(out);
368             if (file != null) {
369                 mDocumentInfo.fileProvider.releaseFile();
370             }
371         }
372     }
373 
notifyUpdateCanceled()374     private void notifyUpdateCanceled() {
375         if (DEBUG) {
376             Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()");
377         }
378         mUpdateCallbacks.onUpdateCanceled();
379     }
380 
notifyUpdateCompleted()381     private void notifyUpdateCompleted() {
382         if (DEBUG) {
383             Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
384         }
385         mUpdateCallbacks.onUpdateCompleted(mDocumentInfo);
386     }
387 
notifyUpdateFailed(CharSequence error)388     private void notifyUpdateFailed(CharSequence error) {
389         if (DEBUG) {
390             Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
391         }
392         mUpdateCallbacks.onUpdateFailed(error);
393     }
394 
connectToRemoteDocument()395     private void connectToRemoteDocument() {
396         try {
397             mPrintDocumentAdapter.asBinder().linkToDeath(mDeathRecipient, 0);
398         } catch (RemoteException re) {
399             Log.w(LOG_TAG, "The printing process is dead.");
400             destroy();
401             return;
402         }
403 
404         try {
405             mPrintDocumentAdapter.setObserver(new PrintDocumentAdapterObserver(this));
406         } catch (RemoteException re) {
407             Log.w(LOG_TAG, "Error setting observer to the print adapter.");
408             destroy();
409         }
410     }
411 
disconnectFromRemoteDocument()412     private void disconnectFromRemoteDocument() {
413         try {
414             mPrintDocumentAdapter.setObserver(null);
415         } catch (RemoteException re) {
416             Log.w(LOG_TAG, "Error setting observer to the print adapter.");
417             // Keep going - best effort...
418         }
419 
420         mPrintDocumentAdapter.asBinder().unlinkToDeath(mDeathRecipient, 0);
421     }
422 
scheduleCommand(AsyncCommand command)423     private void scheduleCommand(AsyncCommand command) {
424         if (mCurrentCommand == null) {
425             mCurrentCommand = command;
426         } else {
427             mNextCommand = command;
428         }
429     }
430 
runPendingCommand()431     private void runPendingCommand() {
432         if (mCurrentCommand != null
433                 && (mCurrentCommand.isCompleted()
434                         || mCurrentCommand.isCanceled())) {
435             mCurrentCommand = mNextCommand;
436             mNextCommand = null;
437         }
438 
439         if (mCurrentCommand != null) {
440             if (mCurrentCommand.isPending()) {
441                 mCurrentCommand.run();
442             }
443             mState = STATE_UPDATING;
444         } else {
445             mState = STATE_UPDATED;
446         }
447     }
448 
stateToString(int state)449     private static String stateToString(int state) {
450         switch (state) {
451             case STATE_FINISHED: {
452                 return "STATE_FINISHED";
453             }
454             case STATE_FAILED: {
455                 return "STATE_FAILED";
456             }
457             case STATE_STARTED: {
458                 return "STATE_STARTED";
459             }
460             case STATE_UPDATING: {
461                 return "STATE_UPDATING";
462             }
463             case STATE_UPDATED: {
464                 return "STATE_UPDATED";
465             }
466             case STATE_CANCELING: {
467                 return "STATE_CANCELING";
468             }
469             case STATE_CANCELED: {
470                 return "STATE_CANCELED";
471             }
472             case STATE_DESTROYED: {
473                 return "STATE_DESTROYED";
474             }
475             default: {
476                 return "STATE_UNKNOWN";
477             }
478         }
479     }
480 
481     static final class UpdateSpec {
482         final PrintAttributes attributes = new PrintAttributes.Builder().build();
483         boolean preview;
484         PageRange[] pages;
485 
update(PrintAttributes attributes, boolean preview, PageRange[] pages)486         public void update(PrintAttributes attributes, boolean preview,
487                 PageRange[] pages) {
488             this.attributes.copyFrom(attributes);
489             this.preview = preview;
490             this.pages = (pages != null) ? Arrays.copyOf(pages, pages.length) : null;
491         }
492 
reset()493         public void reset() {
494             attributes.clear();
495             preview = false;
496             pages = null;
497         }
498 
hasSameConstraints(PrintAttributes attributes, boolean preview)499         public boolean hasSameConstraints(PrintAttributes attributes, boolean preview) {
500             return this.attributes.equals(attributes) && this.preview == preview;
501         }
502     }
503 
504     public static final class RemotePrintDocumentInfo {
505         public PrintAttributes attributes;
506         public Bundle metadata;
507         public PrintDocumentInfo info;
508         public PageRange[] printedPages;
509         public PageRange[] writtenPages;
510         public MutexFileProvider fileProvider;
511         public boolean changed;
512         public boolean updated;
513         public boolean laidout;
514     }
515 
516     private interface CommandDoneCallback {
onDone()517         public void onDone();
518     }
519 
520     private static abstract class AsyncCommand implements Runnable {
521         private static final int STATE_PENDING = 0;
522         private static final int STATE_RUNNING = 1;
523         private static final int STATE_COMPLETED = 2;
524         private static final int STATE_CANCELED = 3;
525         private static final int STATE_CANCELING = 4;
526         private static final int STATE_FAILED = 5;
527 
528         private static int sSequenceCounter;
529 
530         protected final int mSequence = sSequenceCounter++;
531         protected final IPrintDocumentAdapter mAdapter;
532         protected final RemotePrintDocumentInfo mDocument;
533 
534         protected final CommandDoneCallback mDoneCallback;
535 
536         protected ICancellationSignal mCancellation;
537 
538         private CharSequence mError;
539 
540         private int mState = STATE_PENDING;
541 
AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, CommandDoneCallback doneCallback)542         public AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document,
543                 CommandDoneCallback doneCallback) {
544             mAdapter = adapter;
545             mDocument = document;
546             mDoneCallback = doneCallback;
547         }
548 
isCanceling()549         protected final boolean isCanceling() {
550             return mState == STATE_CANCELING;
551         }
552 
isCanceled()553         public final boolean isCanceled() {
554             return mState == STATE_CANCELED;
555         }
556 
cancel()557         public final void cancel() {
558             if (isRunning()) {
559                 canceling();
560                 if (mCancellation != null) {
561                     try {
562                         mCancellation.cancel();
563                     } catch (RemoteException re) {
564                         Log.w(LOG_TAG, "Error while canceling", re);
565                     }
566                 }
567             } else {
568                 canceled();
569 
570                 // Done.
571                 mDoneCallback.onDone();
572             }
573         }
574 
canceling()575         protected final void canceling() {
576             if (mState != STATE_PENDING && mState != STATE_RUNNING) {
577                 throw new IllegalStateException("Command not pending or running.");
578             }
579             mState = STATE_CANCELING;
580         }
581 
canceled()582         protected final void canceled() {
583             if (mState != STATE_CANCELING) {
584                 throw new IllegalStateException("Not canceling.");
585             }
586             mState = STATE_CANCELED;
587         }
588 
isPending()589         public final boolean isPending() {
590             return mState == STATE_PENDING;
591         }
592 
running()593         protected final void running() {
594             if (mState != STATE_PENDING) {
595                 throw new IllegalStateException("Not pending.");
596             }
597             mState = STATE_RUNNING;
598         }
599 
isRunning()600         public final boolean isRunning() {
601             return mState == STATE_RUNNING;
602         }
603 
completed()604         protected final void completed() {
605             if (mState != STATE_RUNNING && mState != STATE_CANCELING) {
606                 throw new IllegalStateException("Not running.");
607             }
608             mState = STATE_COMPLETED;
609         }
610 
isCompleted()611         public final boolean isCompleted() {
612             return mState == STATE_COMPLETED;
613         }
614 
failed(CharSequence error)615         protected final void failed(CharSequence error) {
616             if (mState != STATE_RUNNING) {
617                 throw new IllegalStateException("Not running.");
618             }
619             mState = STATE_FAILED;
620 
621             mError = error;
622         }
623 
isFailed()624         public final boolean isFailed() {
625             return mState == STATE_FAILED;
626         }
627 
getError()628         public CharSequence getError() {
629             return mError;
630         }
631     }
632 
633     private static final class LayoutCommand extends AsyncCommand {
634         private final PrintAttributes mOldAttributes = new PrintAttributes.Builder().build();
635         private final PrintAttributes mNewAttributes = new PrintAttributes.Builder().build();
636         private final Bundle mMetadata = new Bundle();
637 
638         private final ILayoutResultCallback mRemoteResultCallback;
639 
640         private final Handler mHandler;
641 
LayoutCommand(Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, PrintAttributes oldAttributes, PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback)642         public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter,
643                 RemotePrintDocumentInfo document, PrintAttributes oldAttributes,
644                 PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) {
645             super(adapter, document, callback);
646             mHandler = new LayoutHandler(looper);
647             mRemoteResultCallback = new LayoutResultCallback(mHandler);
648             mOldAttributes.copyFrom(oldAttributes);
649             mNewAttributes.copyFrom(newAttributes);
650             mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, preview);
651         }
652 
653         @Override
run()654         public void run() {
655             running();
656 
657             try {
658                 if (DEBUG) {
659                     Log.i(LOG_TAG, "[PERFORMING] layout");
660                 }
661                 mDocument.changed = false;
662                 mAdapter.layout(mOldAttributes, mNewAttributes, mRemoteResultCallback,
663                         mMetadata, mSequence);
664             } catch (RemoteException re) {
665                 Log.e(LOG_TAG, "Error calling layout", re);
666                 handleOnLayoutFailed(null, mSequence);
667             }
668         }
669 
handleOnLayoutStarted(ICancellationSignal cancellation, int sequence)670         private void handleOnLayoutStarted(ICancellationSignal cancellation, int sequence) {
671             if (sequence != mSequence) {
672                 return;
673             }
674 
675             if (DEBUG) {
676                 Log.i(LOG_TAG, "[CALLBACK] onLayoutStarted");
677             }
678 
679             if (isCanceling()) {
680                 try {
681                     cancellation.cancel();
682                 } catch (RemoteException re) {
683                     Log.e(LOG_TAG, "Error cancelling", re);
684                     handleOnLayoutFailed(null, mSequence);
685                 }
686             } else {
687                 mCancellation = cancellation;
688             }
689         }
690 
handleOnLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence)691         private void handleOnLayoutFinished(PrintDocumentInfo info,
692                 boolean changed, int sequence) {
693             if (sequence != mSequence) {
694                 return;
695             }
696 
697             if (DEBUG) {
698                 Log.i(LOG_TAG, "[CALLBACK] onLayoutFinished");
699             }
700 
701             completed();
702 
703             // If the document description changed or the content in the
704             // document changed, the we need to invalidate the pages.
705             if (changed || !equalsIgnoreSize(mDocument.info, info)) {
706                 // If the content changed we throw away all pages as
707                 // we will request them again with the new content.
708                 mDocument.writtenPages = null;
709                 mDocument.printedPages = null;
710                 mDocument.changed = true;
711             }
712 
713             // Update the document with data from the layout pass.
714             mDocument.attributes = mNewAttributes;
715             mDocument.metadata = mMetadata;
716             mDocument.laidout = true;
717             mDocument.info = info;
718 
719             // Release the remote cancellation interface.
720             mCancellation = null;
721 
722             // Done.
723             mDoneCallback.onDone();
724         }
725 
handleOnLayoutFailed(CharSequence error, int sequence)726         private void handleOnLayoutFailed(CharSequence error, int sequence) {
727             if (sequence != mSequence) {
728                 return;
729             }
730 
731             if (DEBUG) {
732                 Log.i(LOG_TAG, "[CALLBACK] onLayoutFailed");
733             }
734 
735             mDocument.laidout = false;
736 
737             failed(error);
738 
739             // Release the remote cancellation interface.
740             mCancellation = null;
741 
742             // Failed.
743             mDoneCallback.onDone();
744         }
745 
handleOnLayoutCanceled(int sequence)746         private void handleOnLayoutCanceled(int sequence) {
747             if (sequence != mSequence) {
748                 return;
749             }
750 
751             if (DEBUG) {
752                 Log.i(LOG_TAG, "[CALLBACK] onLayoutCanceled");
753             }
754 
755             canceled();
756 
757             // Release the remote cancellation interface.
758             mCancellation = null;
759 
760             // Done.
761             mDoneCallback.onDone();
762         }
763 
equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs)764         private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
765             if (lhs == rhs) {
766                 return true;
767             }
768             if (lhs == null) {
769                 return false;
770             } else {
771                 if (rhs == null) {
772                     return false;
773                 }
774                 if (lhs.getContentType() != rhs.getContentType()
775                         || lhs.getPageCount() != rhs.getPageCount()) {
776                     return false;
777                 }
778             }
779             return true;
780         }
781 
782         private final class LayoutHandler extends Handler {
783             public static final int MSG_ON_LAYOUT_STARTED = 1;
784             public static final int MSG_ON_LAYOUT_FINISHED = 2;
785             public static final int MSG_ON_LAYOUT_FAILED = 3;
786             public static final int MSG_ON_LAYOUT_CANCELED = 4;
787 
LayoutHandler(Looper looper)788             public LayoutHandler(Looper looper) {
789                 super(looper, null, false);
790             }
791 
792             @Override
handleMessage(Message message)793             public void handleMessage(Message message) {
794                 switch (message.what) {
795                     case MSG_ON_LAYOUT_STARTED: {
796                         ICancellationSignal cancellation = (ICancellationSignal) message.obj;
797                         final int sequence = message.arg1;
798                         handleOnLayoutStarted(cancellation, sequence);
799                     } break;
800 
801                     case MSG_ON_LAYOUT_FINISHED: {
802                         PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
803                         final boolean changed = (message.arg1 == 1);
804                         final int sequence = message.arg2;
805                         handleOnLayoutFinished(info, changed, sequence);
806                     } break;
807 
808                     case MSG_ON_LAYOUT_FAILED: {
809                         CharSequence error = (CharSequence) message.obj;
810                         final int sequence = message.arg1;
811                         handleOnLayoutFailed(error, sequence);
812                     } break;
813 
814                     case MSG_ON_LAYOUT_CANCELED: {
815                         final int sequence = message.arg1;
816                         handleOnLayoutCanceled(sequence);
817                     } break;
818                 }
819             }
820         }
821 
822         private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
823             private final WeakReference<Handler> mWeakHandler;
824 
LayoutResultCallback(Handler handler)825             public LayoutResultCallback(Handler handler) {
826                 mWeakHandler = new WeakReference<>(handler);
827             }
828 
829             @Override
onLayoutStarted(ICancellationSignal cancellation, int sequence)830             public void onLayoutStarted(ICancellationSignal cancellation, int sequence) {
831                 Handler handler = mWeakHandler.get();
832                 if (handler != null) {
833                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_STARTED,
834                             sequence, 0, cancellation).sendToTarget();
835                 }
836             }
837 
838             @Override
onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence)839             public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
840                 Handler handler = mWeakHandler.get();
841                 if (handler != null) {
842                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FINISHED,
843                             changed ? 1 : 0, sequence, info).sendToTarget();
844                 }
845             }
846 
847             @Override
onLayoutFailed(CharSequence error, int sequence)848             public void onLayoutFailed(CharSequence error, int sequence) {
849                 Handler handler = mWeakHandler.get();
850                 if (handler != null) {
851                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FAILED,
852                             sequence, 0, error).sendToTarget();
853                 }
854             }
855 
856             @Override
onLayoutCanceled(int sequence)857             public void onLayoutCanceled(int sequence) {
858                 Handler handler = mWeakHandler.get();
859                 if (handler != null) {
860                     handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_CANCELED,
861                             sequence, 0).sendToTarget();
862                 }
863             }
864         }
865     }
866 
867     private static final class WriteCommand extends AsyncCommand {
868         private final int mPageCount;
869         private final PageRange[] mPages;
870         private final MutexFileProvider mFileProvider;
871 
872         private final IWriteResultCallback mRemoteResultCallback;
873         private final CommandDoneCallback mDoneCallback;
874 
875         private final Context mContext;
876         private final Handler mHandler;
877 
WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document, int pageCount, PageRange[] pages, MutexFileProvider fileProvider, CommandDoneCallback callback)878         public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter,
879                 RemotePrintDocumentInfo document, int pageCount, PageRange[] pages,
880                 MutexFileProvider fileProvider, CommandDoneCallback callback) {
881             super(adapter, document, callback);
882             mContext = context;
883             mHandler = new WriteHandler(looper);
884             mRemoteResultCallback = new WriteResultCallback(mHandler);
885             mPageCount = pageCount;
886             mPages = Arrays.copyOf(pages, pages.length);
887             mFileProvider = fileProvider;
888             mDoneCallback = callback;
889         }
890 
891         @Override
run()892         public void run() {
893             running();
894 
895             // This is a long running operation as we will be reading fully
896             // the written data. In case of a cancellation, we ask the client
897             // to stop writing data and close the file descriptor after
898             // which we will reach the end of the stream, thus stop reading.
899             new AsyncTask<Void, Void, Void>() {
900                 @Override
901                 protected Void doInBackground(Void... params) {
902                     File file = null;
903                     InputStream in = null;
904                     OutputStream out = null;
905                     ParcelFileDescriptor source = null;
906                     ParcelFileDescriptor sink = null;
907                     try {
908                         file = mFileProvider.acquireFile(null);
909                         ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
910                         source = pipe[0];
911                         sink = pipe[1];
912 
913                         in = new FileInputStream(source.getFileDescriptor());
914                         out = new FileOutputStream(file);
915 
916                         // Async call to initiate the other process writing the data.
917                         if (DEBUG) {
918                             Log.i(LOG_TAG, "[PERFORMING] write");
919                         }
920                         mAdapter.write(mPages, sink, mRemoteResultCallback, mSequence);
921 
922                         // Close the source. It is now held by the client.
923                         sink.close();
924                         sink = null;
925 
926                         // Read the data.
927                         final byte[] buffer = new byte[8192];
928                         while (true) {
929                             final int readByteCount = in.read(buffer);
930                             if (readByteCount < 0) {
931                                 break;
932                             }
933                             out.write(buffer, 0, readByteCount);
934                         }
935                     } catch (RemoteException | IOException e) {
936                         Log.e(LOG_TAG, "Error calling write()", e);
937                     } finally {
938                         IoUtils.closeQuietly(in);
939                         IoUtils.closeQuietly(out);
940                         IoUtils.closeQuietly(sink);
941                         IoUtils.closeQuietly(source);
942                         if (file != null) {
943                             mFileProvider.releaseFile();
944                         }
945                     }
946                     return null;
947                 }
948             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
949         }
950 
handleOnWriteStarted(ICancellationSignal cancellation, int sequence)951         private void handleOnWriteStarted(ICancellationSignal cancellation, int sequence) {
952             if (sequence != mSequence) {
953                 return;
954             }
955 
956             if (DEBUG) {
957                 Log.i(LOG_TAG, "[CALLBACK] onWriteStarted");
958             }
959 
960             if (isCanceling()) {
961                 try {
962                     cancellation.cancel();
963                 } catch (RemoteException re) {
964                     Log.e(LOG_TAG, "Error cancelling", re);
965                     handleOnWriteFailed(null, sequence);
966                 }
967             } else {
968                 mCancellation = cancellation;
969             }
970         }
971 
handleOnWriteFinished(PageRange[] pages, int sequence)972         private void handleOnWriteFinished(PageRange[] pages, int sequence) {
973             if (sequence != mSequence) {
974                 return;
975             }
976 
977             if (DEBUG) {
978                 Log.i(LOG_TAG, "[CALLBACK] onWriteFinished");
979             }
980 
981             PageRange[] writtenPages = PageRangeUtils.normalize(pages);
982             PageRange[] printedPages = PageRangeUtils.computePrintedPages(
983                     mPages, writtenPages, mPageCount);
984 
985             // Handle if we got invalid pages
986             if (printedPages != null) {
987                 mDocument.writtenPages = writtenPages;
988                 mDocument.printedPages = printedPages;
989                 completed();
990             } else {
991                 mDocument.writtenPages = null;
992                 mDocument.printedPages = null;
993                 failed(mContext.getString(R.string.print_error_default_message));
994             }
995 
996             // Release the remote cancellation interface.
997             mCancellation = null;
998 
999             // Done.
1000             mDoneCallback.onDone();
1001         }
1002 
handleOnWriteFailed(CharSequence error, int sequence)1003         private void handleOnWriteFailed(CharSequence error, int sequence) {
1004             if (sequence != mSequence) {
1005                 return;
1006             }
1007 
1008             if (DEBUG) {
1009                 Log.i(LOG_TAG, "[CALLBACK] onWriteFailed");
1010             }
1011 
1012             failed(error);
1013 
1014             // Release the remote cancellation interface.
1015             mCancellation = null;
1016 
1017             // Done.
1018             mDoneCallback.onDone();
1019         }
1020 
handleOnWriteCanceled(int sequence)1021         private void handleOnWriteCanceled(int sequence) {
1022             if (sequence != mSequence) {
1023                 return;
1024             }
1025 
1026             if (DEBUG) {
1027                 Log.i(LOG_TAG, "[CALLBACK] onWriteCanceled");
1028             }
1029 
1030             canceled();
1031 
1032             // Release the remote cancellation interface.
1033             mCancellation = null;
1034 
1035             // Done.
1036             mDoneCallback.onDone();
1037         }
1038 
1039         private final class WriteHandler extends Handler {
1040             public static final int MSG_ON_WRITE_STARTED = 1;
1041             public static final int MSG_ON_WRITE_FINISHED = 2;
1042             public static final int MSG_ON_WRITE_FAILED = 3;
1043             public static final int MSG_ON_WRITE_CANCELED = 4;
1044 
WriteHandler(Looper looper)1045             public WriteHandler(Looper looper) {
1046                 super(looper, null, false);
1047             }
1048 
1049             @Override
handleMessage(Message message)1050             public void handleMessage(Message message) {
1051                 switch (message.what) {
1052                     case MSG_ON_WRITE_STARTED: {
1053                         ICancellationSignal cancellation = (ICancellationSignal) message.obj;
1054                         final int sequence = message.arg1;
1055                         handleOnWriteStarted(cancellation, sequence);
1056                     } break;
1057 
1058                     case MSG_ON_WRITE_FINISHED: {
1059                         PageRange[] pages = (PageRange[]) message.obj;
1060                         final int sequence = message.arg1;
1061                         handleOnWriteFinished(pages, sequence);
1062                     } break;
1063 
1064                     case MSG_ON_WRITE_FAILED: {
1065                         CharSequence error = (CharSequence) message.obj;
1066                         final int sequence = message.arg1;
1067                         handleOnWriteFailed(error, sequence);
1068                     } break;
1069 
1070                     case MSG_ON_WRITE_CANCELED: {
1071                         final int sequence = message.arg1;
1072                         handleOnWriteCanceled(sequence);
1073                     } break;
1074                 }
1075             }
1076         }
1077 
1078         private static final class WriteResultCallback extends IWriteResultCallback.Stub {
1079             private final WeakReference<Handler> mWeakHandler;
1080 
WriteResultCallback(Handler handler)1081             public WriteResultCallback(Handler handler) {
1082                 mWeakHandler = new WeakReference<>(handler);
1083             }
1084 
1085             @Override
onWriteStarted(ICancellationSignal cancellation, int sequence)1086             public void onWriteStarted(ICancellationSignal cancellation, int sequence) {
1087                 Handler handler = mWeakHandler.get();
1088                 if (handler != null) {
1089                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_STARTED,
1090                             sequence, 0, cancellation).sendToTarget();
1091                 }
1092             }
1093 
1094             @Override
onWriteFinished(PageRange[] pages, int sequence)1095             public void onWriteFinished(PageRange[] pages, int sequence) {
1096                 Handler handler = mWeakHandler.get();
1097                 if (handler != null) {
1098                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FINISHED,
1099                             sequence, 0, pages).sendToTarget();
1100                 }
1101             }
1102 
1103             @Override
onWriteFailed(CharSequence error, int sequence)1104             public void onWriteFailed(CharSequence error, int sequence) {
1105                 Handler handler = mWeakHandler.get();
1106                 if (handler != null) {
1107                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FAILED,
1108                         sequence, 0, error).sendToTarget();
1109                 }
1110             }
1111 
1112             @Override
onWriteCanceled(int sequence)1113             public void onWriteCanceled(int sequence) {
1114                 Handler handler = mWeakHandler.get();
1115                 if (handler != null) {
1116                     handler.obtainMessage(WriteHandler.MSG_ON_WRITE_CANCELED,
1117                         sequence, 0).sendToTarget();
1118                 }
1119             }
1120         }
1121     }
1122 
onPrintingAppDied()1123     private void onPrintingAppDied() {
1124         mState = STATE_FAILED;
1125         new Handler(mLooper).post(new Runnable() {
1126             @Override
1127             public void run() {
1128                 mAdapterDeathObserver.onDied();
1129             }
1130         });
1131     }
1132 
1133     private static final class PrintDocumentAdapterObserver
1134             extends IPrintDocumentAdapterObserver.Stub {
1135         private final WeakReference<RemotePrintDocument> mWeakDocument;
1136 
PrintDocumentAdapterObserver(RemotePrintDocument document)1137         public PrintDocumentAdapterObserver(RemotePrintDocument document) {
1138             mWeakDocument = new WeakReference<>(document);
1139         }
1140 
1141         @Override
onDestroy()1142         public void onDestroy() {
1143             final RemotePrintDocument document = mWeakDocument.get();
1144             if (document != null) {
1145                 document.onPrintingAppDied();
1146             }
1147         }
1148     }
1149 }
1150