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