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