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