PrintManager.java revision d270cb9264f762257d1aadbeba9c4b38866e171c
1/*
2 * Copyright (C) 2013 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 android.print;
18
19import android.app.Activity;
20import android.app.Application.ActivityLifecycleCallbacks;
21import android.content.Context;
22import android.content.IntentSender;
23import android.content.IntentSender.SendIntentException;
24import android.os.Bundle;
25import android.os.CancellationSignal;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.os.ParcelFileDescriptor;
30import android.os.RemoteException;
31import android.print.PrintDocumentAdapter.LayoutResultCallback;
32import android.print.PrintDocumentAdapter.WriteResultCallback;
33import android.printservice.PrintServiceInfo;
34import android.text.TextUtils;
35import android.util.ArrayMap;
36import android.util.Log;
37
38import com.android.internal.os.SomeArgs;
39
40import libcore.io.IoUtils;
41
42import java.lang.ref.WeakReference;
43import java.util.ArrayList;
44import java.util.Collections;
45import java.util.List;
46import java.util.Map;
47
48/**
49 * System level service for accessing the printing capabilities of the platform.
50 * <p>
51 * To obtain a handle to the print manager do the following:
52 * </p>
53 *
54 * <pre>
55 * PrintManager printManager =
56 *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
57 * </pre>
58 *
59 * <h3>Print mechanics</h3>
60 * <p>
61 * The key idea behind printing on the platform is that the content to be printed
62 * should be laid out for the currently selected print options resulting in an
63 * optimized output and higher user satisfaction. To achieve this goal the platform
64 * declares a contract that the printing application has to follow which is defined
65 * by the {@link PrintDocumentAdapter} class. At a higher level the contract is that
66 * when the user selects some options from the print UI that may affect the way
67 * content is laid out, for example page size, the application receives a callback
68 * allowing it to layout the content to better fit these new constraints. After a
69 * layout pass the system may ask the application to render one or more pages one
70 * or more times. For example, an application may produce a single column list for
71 * smaller page sizes and a multi-column table for larger page sizes.
72 * </p>
73 * <h3>Print jobs</h3>
74 * <p>
75 * Print jobs are started by calling the {@link #print(String, PrintDocumentAdapter,
76 * PrintAttributes)} from an activity which results in bringing up the system print
77 * UI. Once the print UI is up, when the user changes a selected print option that
78 * affects the way content is laid out the system starts to interact with the
79 * application following the mechanics described the section above.
80 * </p>
81 * <p>
82 * Print jobs can be in {@link PrintJobInfo#STATE_CREATED created}, {@link
83 * PrintJobInfo#STATE_QUEUED queued}, {@link PrintJobInfo#STATE_STARTED started},
84 * {@link PrintJobInfo#STATE_BLOCKED blocked}, {@link PrintJobInfo#STATE_COMPLETED
85 * completed}, {@link PrintJobInfo#STATE_FAILED failed}, and {@link
86 * PrintJobInfo#STATE_CANCELED canceled} state. Print jobs are stored in dedicated
87 * system spooler until they are handled which is they are cancelled or completed.
88 * Active print jobs, ones that are not cancelled or completed, are considered failed
89 * if the device reboots as the new boot may be after a very long time. The user may
90 * choose to restart such print jobs. Once a print job is queued all relevant content
91 * is stored in the system spooler and its lifecycle becomes detached from this of
92 * the application that created it.
93 * </p>
94 * <p>
95 * An applications can query the print spooler for current print jobs it created
96 * but not print jobs created by other applications.
97 * </p>
98 *
99 * @see PrintJob
100 * @see PrintJobInfo
101 */
102public final class PrintManager {
103
104    private static final String LOG_TAG = "PrintManager";
105
106    private static final boolean DEBUG = false;
107
108    private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
109
110    /**
111     * The action for launching the print dialog activity.
112     *
113     * @hide
114     */
115    public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
116
117    /**
118     * Extra with the intent for starting the print dialog.
119     * <p>
120     * <strong>Type:</strong> {@link android.content.IntentSender}
121     * </p>
122     *
123     * @hide
124     */
125    public static final String EXTRA_PRINT_DIALOG_INTENT =
126            "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
127
128    /**
129     * Extra with a print job.
130     * <p>
131     * <strong>Type:</strong> {@link android.print.PrintJobInfo}
132     * </p>
133     *
134     * @hide
135     */
136    public static final String EXTRA_PRINT_JOB =
137            "android.print.intent.extra.EXTRA_PRINT_JOB";
138
139    /**
140     * Extra with the print document adapter to be printed.
141     * <p>
142     * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
143     * </p>
144     *
145     * @hide
146     */
147    public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
148            "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
149
150    /** @hide */
151    public static final int APP_ID_ANY = -2;
152
153    private final Context mContext;
154
155    private final IPrintManager mService;
156
157    private final int mUserId;
158
159    private final int mAppId;
160
161    private final Handler mHandler;
162
163    private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
164
165    /** @hide */
166    public interface PrintJobStateChangeListener {
167
168        /**
169         * Callback notifying that a print job state changed.
170         *
171         * @param printJobId The print job id.
172         */
173        public void onPrintJobStateChanged(PrintJobId printJobId);
174    }
175
176    /**
177     * Creates a new instance.
178     *
179     * @param context The current context in which to operate.
180     * @param service The backing system service.
181     * @hide
182     */
183    public PrintManager(Context context, IPrintManager service, int userId, int appId) {
184        mContext = context;
185        mService = service;
186        mUserId = userId;
187        mAppId = appId;
188        mHandler = new Handler(context.getMainLooper(), null, false) {
189            @Override
190            public void handleMessage(Message message) {
191                switch (message.what) {
192                    case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
193                        SomeArgs args = (SomeArgs) message.obj;
194                        PrintJobStateChangeListenerWrapper wrapper =
195                                (PrintJobStateChangeListenerWrapper) args.arg1;
196                        PrintJobStateChangeListener listener = wrapper.getListener();
197                        if (listener != null) {
198                            PrintJobId printJobId = (PrintJobId) args.arg2;
199                            listener.onPrintJobStateChanged(printJobId);
200                        }
201                        args.recycle();
202                    } break;
203                }
204            }
205        };
206    }
207
208    /**
209     * Creates an instance that can access all print jobs.
210     *
211     * @param userId The user id for which to get all print jobs.
212     * @return An instance if the caller has the permission to access all print
213     *         jobs, null otherwise.
214     * @hide
215     */
216    public PrintManager getGlobalPrintManagerForUser(int userId) {
217        return new PrintManager(mContext, mService, userId, APP_ID_ANY);
218    }
219
220    PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
221        try {
222            return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
223        } catch (RemoteException re) {
224            Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
225        }
226        return null;
227    }
228
229    /**
230     * Adds a listener for observing the state of print jobs.
231     *
232     * @param listener The listener to add.
233     * @hide
234     */
235    public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
236        if (mPrintJobStateChangeListeners == null) {
237            mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
238                    PrintJobStateChangeListenerWrapper>();
239        }
240        PrintJobStateChangeListenerWrapper wrappedListener =
241                new PrintJobStateChangeListenerWrapper(listener, mHandler);
242        try {
243            mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
244            mPrintJobStateChangeListeners.put(listener, wrappedListener);
245        } catch (RemoteException re) {
246            Log.e(LOG_TAG, "Error adding print job state change listener", re);
247        }
248    }
249
250    /**
251     * Removes a listener for observing the state of print jobs.
252     *
253     * @param listener The listener to remove.
254     * @hide
255     */
256    public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
257        if (mPrintJobStateChangeListeners == null) {
258            return;
259        }
260        PrintJobStateChangeListenerWrapper wrappedListener =
261                mPrintJobStateChangeListeners.remove(listener);
262        if (wrappedListener == null) {
263            return;
264        }
265        if (mPrintJobStateChangeListeners.isEmpty()) {
266            mPrintJobStateChangeListeners = null;
267        }
268        wrappedListener.destroy();
269        try {
270            mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
271        } catch (RemoteException re) {
272            Log.e(LOG_TAG, "Error removing print job state change listener", re);
273        }
274    }
275
276    /**
277     * Gets a print job given its id.
278     *
279     * @return The print job list.
280     * @see PrintJob
281     * @hide
282     */
283    public PrintJob getPrintJob(PrintJobId printJobId) {
284        try {
285            PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
286            if (printJob != null) {
287                return new PrintJob(printJob, this);
288            }
289        } catch (RemoteException re) {
290            Log.e(LOG_TAG, "Error getting print job", re);
291        }
292        return null;
293    }
294
295    /**
296     * Gets the print jobs for this application.
297     *
298     * @return The print job list.
299     * @see PrintJob
300     */
301    public List<PrintJob> getPrintJobs() {
302        try {
303            List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
304            if (printJobInfos == null) {
305                return Collections.emptyList();
306            }
307            final int printJobCount = printJobInfos.size();
308            List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
309            for (int i = 0; i < printJobCount; i++) {
310                printJobs.add(new PrintJob(printJobInfos.get(i), this));
311            }
312            return printJobs;
313        } catch (RemoteException re) {
314            Log.e(LOG_TAG, "Error getting print jobs", re);
315        }
316        return Collections.emptyList();
317    }
318
319    void cancelPrintJob(PrintJobId printJobId) {
320        try {
321            mService.cancelPrintJob(printJobId, mAppId, mUserId);
322        } catch (RemoteException re) {
323            Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
324        }
325    }
326
327    void restartPrintJob(PrintJobId printJobId) {
328        try {
329            mService.restartPrintJob(printJobId, mAppId, mUserId);
330        } catch (RemoteException re) {
331            Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re);
332        }
333    }
334
335    /**
336     * Creates a print job for printing a {@link PrintDocumentAdapter} with
337     * default print attributes.
338     * <p>
339     * Calling this method brings the print UI allowing the user to customize
340     * the print job and returns a {@link PrintJob} object without waiting for the
341     * user to customize or confirm the print job. The returned print job instance
342     * is in a {@link PrintJobInfo#STATE_CREATED created} state.
343     * <p>
344     * This method can be called only from an {@link Activity}. The rationale is that
345     * printing from a service will create an inconsistent user experience as the print
346     * UI would appear without any context.
347     * </p>
348     * <p>
349     * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
350     * your activity is finished. The rationale is that once the activity that
351     * initiated printing is finished, the provided adapter may be in an inconsistent
352     * state as it may depend on the UI presented by the activity.
353     * </p>
354     * <p>
355     * The default print attributes are a hint to the system how the data is to
356     * be printed. For example, a photo editor may look at the photo aspect ratio
357     * to determine the default orientation and provide a hint whether the printing
358     * should be in portrait or landscape. The system will do a best effort to
359     * selected the hinted options in the print dialog, given the current printer
360     * supports them.
361     * </p>
362     *
363     * @param printJobName A name for the new print job which is shown to the user.
364     * @param documentAdapter An adapter that emits the document to print.
365     * @param attributes The default print job attributes or <code>null</code>.
366     * @return The created print job on success or null on failure.
367     * @throws IllegalStateException If not called from an {@link Activity}.
368     * @throws IllegalArgumentException If the print job name is empty or the
369     * document adapter is null.
370     *
371     * @see PrintJob
372     */
373    public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
374            PrintAttributes attributes) {
375        if (!(mContext instanceof Activity)) {
376            throw new IllegalStateException("Can print only from an activity");
377        }
378        if (TextUtils.isEmpty(printJobName)) {
379            throw new IllegalArgumentException("printJobName cannot be empty");
380        }
381        if (documentAdapter == null) {
382            throw new IllegalArgumentException("documentAdapter cannot be null");
383        }
384        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
385                (Activity) mContext, documentAdapter);
386        try {
387            Bundle result = mService.print(printJobName, delegate,
388                    attributes, mContext.getPackageName(), mAppId, mUserId);
389            if (result != null) {
390                PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
391                IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
392                if (printJob == null || intent == null) {
393                    return null;
394                }
395                try {
396                    mContext.startIntentSender(intent, null, 0, 0, 0);
397                    return new PrintJob(printJob, this);
398                } catch (SendIntentException sie) {
399                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
400                }
401            }
402        } catch (RemoteException re) {
403            Log.e(LOG_TAG, "Error creating a print job", re);
404        }
405        return null;
406    }
407
408    /**
409     * Gets the list of enabled print services.
410     *
411     * @return The enabled service list or an empty list.
412     * @hide
413     */
414    public List<PrintServiceInfo> getEnabledPrintServices() {
415        try {
416            List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
417            if (enabledServices != null) {
418                return enabledServices;
419            }
420        } catch (RemoteException re) {
421            Log.e(LOG_TAG, "Error getting the enabled print services", re);
422        }
423        return Collections.emptyList();
424    }
425
426    /**
427     * Gets the list of installed print services.
428     *
429     * @return The installed service list or an empty list.
430     * @hide
431     */
432    public List<PrintServiceInfo> getInstalledPrintServices() {
433        try {
434            List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
435            if (installedServices != null) {
436                return installedServices;
437            }
438        } catch (RemoteException re) {
439            Log.e(LOG_TAG, "Error getting the installed print services", re);
440        }
441        return Collections.emptyList();
442    }
443
444    /**
445     * @hide
446     */
447    public PrinterDiscoverySession createPrinterDiscoverySession() {
448        return new PrinterDiscoverySession(mService, mContext, mUserId);
449    }
450
451    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
452            implements ActivityLifecycleCallbacks {
453
454        private final Object mLock = new Object();
455
456        private CancellationSignal mLayoutOrWriteCancellation;
457
458        private Activity mActivity; // Strong reference OK - cleared in finish()
459
460        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
461
462        private Handler mHandler; // Strong reference OK - cleared in finish()
463
464        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
465
466        private LayoutSpec mLastLayoutSpec;
467
468        private WriteSpec mLastWriteSpec;
469
470        private boolean mStartReqeusted;
471        private boolean mStarted;
472
473        private boolean mFinishRequested;
474        private boolean mFinished;
475
476        private boolean mDestroyed;
477
478        public PrintDocumentAdapterDelegate(Activity activity,
479                PrintDocumentAdapter documentAdapter) {
480            mActivity = activity;
481            mDocumentAdapter = documentAdapter;
482            mHandler = new MyHandler(mActivity.getMainLooper());
483            mActivity.getApplication().registerActivityLifecycleCallbacks(this);
484        }
485
486        @Override
487        public void setObserver(IPrintDocumentAdapterObserver observer) {
488            final boolean destroyed;
489            synchronized (mLock) {
490                if (!mDestroyed) {
491                    mObserver = observer;
492                }
493                destroyed = mDestroyed;
494            }
495            if (destroyed) {
496                try {
497                    observer.onDestroy();
498                } catch (RemoteException re) {
499                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
500                }
501            }
502        }
503
504        @Override
505        public void start() {
506            synchronized (mLock) {
507                // Started called or finish called or destroyed - nothing to do.
508                if (mStartReqeusted || mFinishRequested || mDestroyed) {
509                    return;
510                }
511
512                mStartReqeusted = true;
513
514                doPendingWorkLocked();
515            }
516        }
517
518        @Override
519        public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
520                ILayoutResultCallback callback, Bundle metadata, int sequence) {
521            final boolean destroyed;
522            synchronized (mLock) {
523                destroyed = mDestroyed;
524                // If start called and not finished called and not destroyed - do some work.
525                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
526                    // Layout cancels write and overrides layout.
527                    if (mLastWriteSpec != null) {
528                        IoUtils.closeQuietly(mLastWriteSpec.fd);
529                        mLastWriteSpec = null;
530                    }
531
532                    mLastLayoutSpec = new LayoutSpec();
533                    mLastLayoutSpec.callback = callback;
534                    mLastLayoutSpec.oldAttributes = oldAttributes;
535                    mLastLayoutSpec.newAttributes = newAttributes;
536                    mLastLayoutSpec.metadata = metadata;
537                    mLastLayoutSpec.sequence = sequence;
538
539                    // Cancel the previous cancellable operation.When the
540                    // cancellation completes we will do the pending work.
541                    if (cancelPreviousCancellableOperationLocked()) {
542                        return;
543                    }
544
545                    doPendingWorkLocked();
546                }
547            }
548            if (destroyed) {
549                try {
550                    callback.onLayoutFailed(null, sequence);
551                } catch (RemoteException re) {
552                    Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
553                }
554            }
555        }
556
557        @Override
558        public void write(PageRange[] pages, ParcelFileDescriptor fd,
559                IWriteResultCallback callback, int sequence) {
560            final boolean destroyed;
561            synchronized (mLock) {
562                destroyed = mDestroyed;
563                // If start called and not finished called and not destroyed - do some work.
564                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
565                    // Write cancels previous writes.
566                    if (mLastWriteSpec != null) {
567                        IoUtils.closeQuietly(mLastWriteSpec.fd);
568                        mLastWriteSpec = null;
569                    }
570
571                    mLastWriteSpec = new WriteSpec();
572                    mLastWriteSpec.callback = callback;
573                    mLastWriteSpec.pages = pages;
574                    mLastWriteSpec.fd = fd;
575                    mLastWriteSpec.sequence = sequence;
576
577                    // Cancel the previous cancellable operation.When the
578                    // cancellation completes we will do the pending work.
579                    if (cancelPreviousCancellableOperationLocked()) {
580                        return;
581                    }
582
583                    doPendingWorkLocked();
584                }
585            }
586            if (destroyed) {
587                try {
588                    callback.onWriteFailed(null, sequence);
589                } catch (RemoteException re) {
590                    Log.i(LOG_TAG, "Error notifying for cancelled write", re);
591                }
592            }
593        }
594
595        @Override
596        public void finish() {
597            synchronized (mLock) {
598                // Start not called or finish called or destroyed - nothing to do.
599                if (!mStartReqeusted || mFinishRequested || mDestroyed) {
600                    return;
601                }
602
603                mFinishRequested = true;
604
605                // When the current write or layout complete we
606                // will do the pending work.
607                if (mLastLayoutSpec != null || mLastWriteSpec != null) {
608                    if (DEBUG) {
609                        Log.i(LOG_TAG, "Waiting for current operation");
610                    }
611                    return;
612                }
613
614                doPendingWorkLocked();
615            }
616        }
617
618        @Override
619        public void cancel() {
620            // Start not called or finish called or destroyed - nothing to do.
621            if (!mStartReqeusted || mFinishRequested || mDestroyed) {
622                return;
623            }
624            // Request cancellation of pending work if needed.
625            synchronized (mLock) {
626                cancelPreviousCancellableOperationLocked();
627            }
628        }
629
630        @Override
631        public void onActivityPaused(Activity activity) {
632            /* do nothing */
633        }
634
635        @Override
636        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
637            /* do nothing */
638        }
639
640        @Override
641        public void onActivityStarted(Activity activity) {
642            /* do nothing */
643        }
644
645        @Override
646        public void onActivityResumed(Activity activity) {
647            /* do nothing */
648        }
649
650        @Override
651        public void onActivityStopped(Activity activity) {
652            /* do nothing */
653        }
654
655        @Override
656        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
657            /* do nothing */
658        }
659
660        @Override
661        public void onActivityDestroyed(Activity activity) {
662            // We really care only if the activity is being destroyed to
663            // notify the the print spooler so it can close the print dialog.
664            // Note the the spooler has a death recipient that observes if
665            // this process gets killed so we cover the case of onDestroy not
666            // being called due to this process being killed to reclaim memory.
667            final IPrintDocumentAdapterObserver observer;
668            synchronized (mLock) {
669                if (activity == mActivity) {
670                    mDestroyed = true;
671                    observer = mObserver;
672                    clearLocked();
673                } else {
674                    observer = null;
675                    activity = null;
676                }
677            }
678            if (observer != null) {
679                activity.getApplication().unregisterActivityLifecycleCallbacks(
680                        PrintDocumentAdapterDelegate.this);
681                try {
682                    observer.onDestroy();
683                } catch (RemoteException re) {
684                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
685                }
686            }
687        }
688
689        private boolean isFinished() {
690            return mDocumentAdapter == null;
691        }
692
693        private void clearLocked() {
694            mActivity = null;
695            mDocumentAdapter = null;
696            mHandler = null;
697            mLayoutOrWriteCancellation = null;
698            mLastLayoutSpec = null;
699            if (mLastWriteSpec != null) {
700                IoUtils.closeQuietly(mLastWriteSpec.fd);
701                mLastWriteSpec = null;
702            }
703        }
704
705        private boolean cancelPreviousCancellableOperationLocked() {
706            if (mLayoutOrWriteCancellation != null) {
707                mLayoutOrWriteCancellation.cancel();
708                if (DEBUG) {
709                    Log.i(LOG_TAG, "Cancelling previous operation");
710                }
711                return true;
712            }
713            return false;
714        }
715
716        private void doPendingWorkLocked() {
717            if (mStartReqeusted && !mStarted) {
718                mStarted = true;
719                mHandler.sendEmptyMessage(MyHandler.MSG_START);
720            } else if (mLastLayoutSpec != null) {
721                mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
722            } else if (mLastWriteSpec != null) {
723                mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
724            } else if (mFinishRequested && !mFinished) {
725                mFinished = true;
726                mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
727            }
728        }
729
730        private class LayoutSpec {
731            ILayoutResultCallback callback;
732            PrintAttributes oldAttributes;
733            PrintAttributes newAttributes;
734            Bundle metadata;
735            int sequence;
736        }
737
738        private class WriteSpec {
739            IWriteResultCallback callback;
740            PageRange[] pages;
741            ParcelFileDescriptor fd;
742            int sequence;
743        }
744
745        private final class MyHandler extends Handler {
746            public static final int MSG_START = 1;
747            public static final int MSG_LAYOUT = 2;
748            public static final int MSG_WRITE = 3;
749            public static final int MSG_FINISH = 4;
750
751            public MyHandler(Looper looper) {
752                super(looper, null, true);
753            }
754
755            @Override
756            public void handleMessage(Message message) {
757                if (isFinished()) {
758                    return;
759                }
760                switch (message.what) {
761                    case MSG_START: {
762                        final PrintDocumentAdapter adapter;
763                        synchronized (mLock) {
764                            adapter = mDocumentAdapter;
765                        }
766                        if (adapter != null) {
767                            adapter.onStart();
768                        }
769                    } break;
770
771                    case MSG_LAYOUT: {
772                        final PrintDocumentAdapter adapter;
773                        final CancellationSignal cancellation;
774                        final LayoutSpec layoutSpec;
775
776                        synchronized (mLock) {
777                            adapter = mDocumentAdapter;
778                            layoutSpec = mLastLayoutSpec;
779                            mLastLayoutSpec = null;
780                            cancellation = new CancellationSignal();
781                            mLayoutOrWriteCancellation = cancellation;
782                        }
783
784                        if (layoutSpec != null && adapter != null) {
785                            if (DEBUG) {
786                                Log.i(LOG_TAG, "Performing layout");
787                            }
788                            adapter.onLayout(layoutSpec.oldAttributes,
789                                    layoutSpec.newAttributes, cancellation,
790                                    new MyLayoutResultCallback(layoutSpec.callback,
791                                            layoutSpec.sequence), layoutSpec.metadata);
792                        }
793                    } break;
794
795                    case MSG_WRITE: {
796                        final PrintDocumentAdapter adapter;
797                        final CancellationSignal cancellation;
798                        final WriteSpec writeSpec;
799
800                        synchronized (mLock) {
801                            adapter = mDocumentAdapter;
802                            writeSpec = mLastWriteSpec;
803                            mLastWriteSpec = null;
804                            cancellation = new CancellationSignal();
805                            mLayoutOrWriteCancellation = cancellation;
806                        }
807
808                        if (writeSpec != null && adapter != null) {
809                            if (DEBUG) {
810                                Log.i(LOG_TAG, "Performing write");
811                            }
812                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
813                                    cancellation, new MyWriteResultCallback(writeSpec.callback,
814                                            writeSpec.fd, writeSpec.sequence));
815                        }
816                    } break;
817
818                    case MSG_FINISH: {
819                        if (DEBUG) {
820                            Log.i(LOG_TAG, "Performing finish");
821                        }
822                        final PrintDocumentAdapter adapter;
823                        final Activity activity;
824                        synchronized (mLock) {
825                            adapter = mDocumentAdapter;
826                            activity = mActivity;
827                            clearLocked();
828                        }
829                        if (adapter != null) {
830                            adapter.onFinish();
831                        }
832                        if (activity != null) {
833                            activity.getApplication().unregisterActivityLifecycleCallbacks(
834                                    PrintDocumentAdapterDelegate.this);
835                        }
836                    } break;
837
838                    default: {
839                        throw new IllegalArgumentException("Unknown message: "
840                                + message.what);
841                    }
842                }
843            }
844        }
845
846        private final class MyLayoutResultCallback extends LayoutResultCallback {
847            private ILayoutResultCallback mCallback;
848            private final int mSequence;
849
850            public MyLayoutResultCallback(ILayoutResultCallback callback,
851                    int sequence) {
852                mCallback = callback;
853                mSequence = sequence;
854            }
855
856            @Override
857            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
858                if (info == null) {
859                    throw new NullPointerException("document info cannot be null");
860                }
861                final ILayoutResultCallback callback;
862                synchronized (mLock) {
863                    callback = mCallback;
864                    clearLocked();
865                }
866                if (callback != null) {
867                    try {
868                        callback.onLayoutFinished(info, changed, mSequence);
869                    } catch (RemoteException re) {
870                        Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
871                    }
872                }
873            }
874
875            @Override
876            public void onLayoutFailed(CharSequence error) {
877                final ILayoutResultCallback callback;
878                synchronized (mLock) {
879                    callback = mCallback;
880                    clearLocked();
881                }
882                if (callback != null) {
883                    try {
884                        callback.onLayoutFailed(error, mSequence);
885                    } catch (RemoteException re) {
886                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
887                    }
888                }
889            }
890
891            @Override
892            public void onLayoutCancelled() {
893                synchronized (mLock) {
894                    clearLocked();
895                }
896            }
897
898            private void clearLocked() {
899                mLayoutOrWriteCancellation = null;
900                mCallback = null;
901                doPendingWorkLocked();
902            }
903        }
904
905        private final class MyWriteResultCallback extends WriteResultCallback {
906            private ParcelFileDescriptor mFd;
907            private int mSequence;
908            private IWriteResultCallback mCallback;
909
910            public MyWriteResultCallback(IWriteResultCallback callback,
911                    ParcelFileDescriptor fd, int sequence) {
912                mFd = fd;
913                mSequence = sequence;
914                mCallback = callback;
915            }
916
917            @Override
918            public void onWriteFinished(PageRange[] pages) {
919                final IWriteResultCallback callback;
920                synchronized (mLock) {
921                    callback = mCallback;
922                    clearLocked();
923                }
924                if (pages == null) {
925                    throw new IllegalArgumentException("pages cannot be null");
926                }
927                if (pages.length == 0) {
928                    throw new IllegalArgumentException("pages cannot be empty");
929                }
930                if (callback != null) {
931                    try {
932                        callback.onWriteFinished(pages, mSequence);
933                    } catch (RemoteException re) {
934                        Log.e(LOG_TAG, "Error calling onWriteFinished", re);
935                    }
936                }
937            }
938
939            @Override
940            public void onWriteFailed(CharSequence error) {
941                final IWriteResultCallback callback;
942                synchronized (mLock) {
943                    callback = mCallback;
944                    clearLocked();
945                }
946                if (callback != null) {
947                    try {
948                        callback.onWriteFailed(error, mSequence);
949                    } catch (RemoteException re) {
950                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
951                    }
952                }
953            }
954
955            @Override
956            public void onWriteCancelled() {
957                synchronized (mLock) {
958                    clearLocked();
959                }
960            }
961
962            private void clearLocked() {
963                mLayoutOrWriteCancellation = null;
964                IoUtils.closeQuietly(mFd);
965                mCallback = null;
966                mFd = null;
967                doPendingWorkLocked();
968            }
969        }
970    }
971
972    private static final class PrintJobStateChangeListenerWrapper extends
973            IPrintJobStateChangeListener.Stub {
974        private final WeakReference<PrintJobStateChangeListener> mWeakListener;
975        private final WeakReference<Handler> mWeakHandler;
976
977        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
978                Handler handler) {
979            mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
980            mWeakHandler = new WeakReference<Handler>(handler);
981        }
982
983        @Override
984        public void onPrintJobStateChanged(PrintJobId printJobId) {
985            Handler handler = mWeakHandler.get();
986            PrintJobStateChangeListener listener = mWeakListener.get();
987            if (handler != null && listener != null) {
988                SomeArgs args = SomeArgs.obtain();
989                args.arg1 = this;
990                args.arg2 = printJobId;
991                handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
992                        args).sendToTarget();
993            }
994        }
995
996        public void destroy() {
997            mWeakListener.clear();
998        }
999
1000        public PrintJobStateChangeListener getListener() {
1001            return mWeakListener.get();
1002        }
1003    }
1004}
1005