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