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