PrintManager.java revision 81f14b96899afa90330e2652acb629345acf17b1
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     * <p>
363     * <strong>Note:</strong> Calling this method will bring the print dialog and
364     * the system will connect to the provided {@link PrintDocumentAdapter}. If a
365     * configuration change occurs, such as rotation, the system will drop the
366     * connection to the adapter as the activity has to be recreated and the old
367     * adapter may be invalid in this context. As a consequence, if your activity
368     * does not handle rotation, you have to save the state that you were printing
369     * and call this method again when your activity is recreated.
370     * </p>
371     *
372     * @param printJobName A name for the new print job which is shown to the user.
373     * @param documentAdapter An adapter that emits the document to print.
374     * @param attributes The default print job attributes or <code>null</code>.
375     * @return The created print job on success or null on failure.
376     * @throws IllegalStateException If not called from an {@link Activity}.
377     * @throws IllegalArgumentException If the print job name is empty or the
378     * document adapter is null.
379     *
380     * @see PrintJob
381     */
382    public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
383            PrintAttributes attributes) {
384        if (!(mContext instanceof Activity)) {
385            throw new IllegalStateException("Can print only from an activity");
386        }
387        if (TextUtils.isEmpty(printJobName)) {
388            throw new IllegalArgumentException("printJobName cannot be empty");
389        }
390        if (documentAdapter == null) {
391            throw new IllegalArgumentException("documentAdapter cannot be null");
392        }
393        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
394                (Activity) mContext, documentAdapter);
395        try {
396            Bundle result = mService.print(printJobName, delegate,
397                    attributes, mContext.getPackageName(), mAppId, mUserId);
398            if (result != null) {
399                PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
400                IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
401                if (printJob == null || intent == null) {
402                    return null;
403                }
404                try {
405                    mContext.startIntentSender(intent, null, 0, 0, 0);
406                    return new PrintJob(printJob, this);
407                } catch (SendIntentException sie) {
408                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
409                }
410            }
411        } catch (RemoteException re) {
412            Log.e(LOG_TAG, "Error creating a print job", re);
413        }
414        return null;
415    }
416
417    /**
418     * Gets the list of enabled print services.
419     *
420     * @return The enabled service list or an empty list.
421     * @hide
422     */
423    public List<PrintServiceInfo> getEnabledPrintServices() {
424        try {
425            List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
426            if (enabledServices != null) {
427                return enabledServices;
428            }
429        } catch (RemoteException re) {
430            Log.e(LOG_TAG, "Error getting the enabled print services", re);
431        }
432        return Collections.emptyList();
433    }
434
435    /**
436     * Gets the list of installed print services.
437     *
438     * @return The installed service list or an empty list.
439     * @hide
440     */
441    public List<PrintServiceInfo> getInstalledPrintServices() {
442        try {
443            List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
444            if (installedServices != null) {
445                return installedServices;
446            }
447        } catch (RemoteException re) {
448            Log.e(LOG_TAG, "Error getting the installed print services", re);
449        }
450        return Collections.emptyList();
451    }
452
453    /**
454     * @hide
455     */
456    public PrinterDiscoverySession createPrinterDiscoverySession() {
457        return new PrinterDiscoverySession(mService, mContext, mUserId);
458    }
459
460    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
461            implements ActivityLifecycleCallbacks {
462
463        private final Object mLock = new Object();
464
465        private CancellationSignal mLayoutOrWriteCancellation;
466
467        private Activity mActivity; // Strong reference OK - cleared in finish()
468
469        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
470
471        private Handler mHandler; // Strong reference OK - cleared in finish()
472
473        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
474
475        private LayoutSpec mLastLayoutSpec;
476
477        private WriteSpec mLastWriteSpec;
478
479        private boolean mStartReqeusted;
480        private boolean mStarted;
481
482        private boolean mFinishRequested;
483        private boolean mFinished;
484
485        private boolean mDestroyed;
486
487        public PrintDocumentAdapterDelegate(Activity activity,
488                PrintDocumentAdapter documentAdapter) {
489            mActivity = activity;
490            mDocumentAdapter = documentAdapter;
491            mHandler = new MyHandler(mActivity.getMainLooper());
492            mActivity.getApplication().registerActivityLifecycleCallbacks(this);
493        }
494
495        @Override
496        public void setObserver(IPrintDocumentAdapterObserver observer) {
497            final boolean destroyed;
498            synchronized (mLock) {
499                if (!mDestroyed) {
500                    mObserver = observer;
501                }
502                destroyed = mDestroyed;
503            }
504            if (destroyed) {
505                try {
506                    observer.onDestroy();
507                } catch (RemoteException re) {
508                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
509                }
510            }
511        }
512
513        @Override
514        public void start() {
515            synchronized (mLock) {
516                // Started called or finish called or destroyed - nothing to do.
517                if (mStartReqeusted || mFinishRequested || mDestroyed) {
518                    return;
519                }
520
521                mStartReqeusted = true;
522
523                doPendingWorkLocked();
524            }
525        }
526
527        @Override
528        public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
529                ILayoutResultCallback callback, Bundle metadata, int sequence) {
530            final boolean destroyed;
531            synchronized (mLock) {
532                destroyed = mDestroyed;
533                // If start called and not finished called and not destroyed - do some work.
534                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
535                    // Layout cancels write and overrides layout.
536                    if (mLastWriteSpec != null) {
537                        IoUtils.closeQuietly(mLastWriteSpec.fd);
538                        mLastWriteSpec = null;
539                    }
540
541                    mLastLayoutSpec = new LayoutSpec();
542                    mLastLayoutSpec.callback = callback;
543                    mLastLayoutSpec.oldAttributes = oldAttributes;
544                    mLastLayoutSpec.newAttributes = newAttributes;
545                    mLastLayoutSpec.metadata = metadata;
546                    mLastLayoutSpec.sequence = sequence;
547
548                    // Cancel the previous cancellable operation.When the
549                    // cancellation completes we will do the pending work.
550                    if (cancelPreviousCancellableOperationLocked()) {
551                        return;
552                    }
553
554                    doPendingWorkLocked();
555                }
556            }
557            if (destroyed) {
558                try {
559                    callback.onLayoutFailed(null, sequence);
560                } catch (RemoteException re) {
561                    Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
562                }
563            }
564        }
565
566        @Override
567        public void write(PageRange[] pages, ParcelFileDescriptor fd,
568                IWriteResultCallback callback, int sequence) {
569            final boolean destroyed;
570            synchronized (mLock) {
571                destroyed = mDestroyed;
572                // If start called and not finished called and not destroyed - do some work.
573                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
574                    // Write cancels previous writes.
575                    if (mLastWriteSpec != null) {
576                        IoUtils.closeQuietly(mLastWriteSpec.fd);
577                        mLastWriteSpec = null;
578                    }
579
580                    mLastWriteSpec = new WriteSpec();
581                    mLastWriteSpec.callback = callback;
582                    mLastWriteSpec.pages = pages;
583                    mLastWriteSpec.fd = fd;
584                    mLastWriteSpec.sequence = sequence;
585
586                    // Cancel the previous cancellable operation.When the
587                    // cancellation completes we will do the pending work.
588                    if (cancelPreviousCancellableOperationLocked()) {
589                        return;
590                    }
591
592                    doPendingWorkLocked();
593                }
594            }
595            if (destroyed) {
596                try {
597                    callback.onWriteFailed(null, sequence);
598                } catch (RemoteException re) {
599                    Log.i(LOG_TAG, "Error notifying for cancelled write", re);
600                }
601            }
602        }
603
604        @Override
605        public void finish() {
606            synchronized (mLock) {
607                // Start not called or finish called or destroyed - nothing to do.
608                if (!mStartReqeusted || mFinishRequested || mDestroyed) {
609                    return;
610                }
611
612                mFinishRequested = true;
613
614                // When the current write or layout complete we
615                // will do the pending work.
616                if (mLastLayoutSpec != null || mLastWriteSpec != null) {
617                    if (DEBUG) {
618                        Log.i(LOG_TAG, "Waiting for current operation");
619                    }
620                    return;
621                }
622
623                doPendingWorkLocked();
624            }
625        }
626
627        @Override
628        public void cancel() {
629            // Start not called or finish called or destroyed - nothing to do.
630            if (!mStartReqeusted || mFinishRequested || mDestroyed) {
631                return;
632            }
633            // Request cancellation of pending work if needed.
634            synchronized (mLock) {
635                cancelPreviousCancellableOperationLocked();
636            }
637        }
638
639        @Override
640        public void onActivityPaused(Activity activity) {
641            /* do nothing */
642        }
643
644        @Override
645        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
646            /* do nothing */
647        }
648
649        @Override
650        public void onActivityStarted(Activity activity) {
651            /* do nothing */
652        }
653
654        @Override
655        public void onActivityResumed(Activity activity) {
656            /* do nothing */
657        }
658
659        @Override
660        public void onActivityStopped(Activity activity) {
661            /* do nothing */
662        }
663
664        @Override
665        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
666            /* do nothing */
667        }
668
669        @Override
670        public void onActivityDestroyed(Activity activity) {
671            // We really care only if the activity is being destroyed to
672            // notify the the print spooler so it can close the print dialog.
673            // Note the the spooler has a death recipient that observes if
674            // this process gets killed so we cover the case of onDestroy not
675            // being called due to this process being killed to reclaim memory.
676            final IPrintDocumentAdapterObserver observer;
677            synchronized (mLock) {
678                if (activity == mActivity) {
679                    mDestroyed = true;
680                    observer = mObserver;
681                    clearLocked();
682                } else {
683                    observer = null;
684                    activity = null;
685                }
686            }
687            if (observer != null) {
688                activity.getApplication().unregisterActivityLifecycleCallbacks(
689                        PrintDocumentAdapterDelegate.this);
690                try {
691                    observer.onDestroy();
692                } catch (RemoteException re) {
693                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
694                }
695            }
696        }
697
698        private boolean isFinished() {
699            return mDocumentAdapter == null;
700        }
701
702        private void clearLocked() {
703            mActivity = null;
704            mDocumentAdapter = null;
705            mHandler = null;
706            mLayoutOrWriteCancellation = null;
707            mLastLayoutSpec = null;
708            if (mLastWriteSpec != null) {
709                IoUtils.closeQuietly(mLastWriteSpec.fd);
710                mLastWriteSpec = null;
711            }
712        }
713
714        private boolean cancelPreviousCancellableOperationLocked() {
715            if (mLayoutOrWriteCancellation != null) {
716                mLayoutOrWriteCancellation.cancel();
717                if (DEBUG) {
718                    Log.i(LOG_TAG, "Cancelling previous operation");
719                }
720                return true;
721            }
722            return false;
723        }
724
725        private void doPendingWorkLocked() {
726            if (mStartReqeusted && !mStarted) {
727                mStarted = true;
728                mHandler.sendEmptyMessage(MyHandler.MSG_START);
729            } else if (mLastLayoutSpec != null) {
730                mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
731            } else if (mLastWriteSpec != null) {
732                mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
733            } else if (mFinishRequested && !mFinished) {
734                mFinished = true;
735                mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
736            }
737        }
738
739        private class LayoutSpec {
740            ILayoutResultCallback callback;
741            PrintAttributes oldAttributes;
742            PrintAttributes newAttributes;
743            Bundle metadata;
744            int sequence;
745        }
746
747        private class WriteSpec {
748            IWriteResultCallback callback;
749            PageRange[] pages;
750            ParcelFileDescriptor fd;
751            int sequence;
752        }
753
754        private final class MyHandler extends Handler {
755            public static final int MSG_START = 1;
756            public static final int MSG_LAYOUT = 2;
757            public static final int MSG_WRITE = 3;
758            public static final int MSG_FINISH = 4;
759
760            public MyHandler(Looper looper) {
761                super(looper, null, true);
762            }
763
764            @Override
765            public void handleMessage(Message message) {
766                if (isFinished()) {
767                    return;
768                }
769                switch (message.what) {
770                    case MSG_START: {
771                        final PrintDocumentAdapter adapter;
772                        synchronized (mLock) {
773                            adapter = mDocumentAdapter;
774                        }
775                        if (adapter != null) {
776                            adapter.onStart();
777                        }
778                    } break;
779
780                    case MSG_LAYOUT: {
781                        final PrintDocumentAdapter adapter;
782                        final CancellationSignal cancellation;
783                        final LayoutSpec layoutSpec;
784
785                        synchronized (mLock) {
786                            adapter = mDocumentAdapter;
787                            layoutSpec = mLastLayoutSpec;
788                            mLastLayoutSpec = null;
789                            cancellation = new CancellationSignal();
790                            mLayoutOrWriteCancellation = cancellation;
791                        }
792
793                        if (layoutSpec != null && adapter != null) {
794                            if (DEBUG) {
795                                Log.i(LOG_TAG, "Performing layout");
796                            }
797                            adapter.onLayout(layoutSpec.oldAttributes,
798                                    layoutSpec.newAttributes, cancellation,
799                                    new MyLayoutResultCallback(layoutSpec.callback,
800                                            layoutSpec.sequence), layoutSpec.metadata);
801                        }
802                    } break;
803
804                    case MSG_WRITE: {
805                        final PrintDocumentAdapter adapter;
806                        final CancellationSignal cancellation;
807                        final WriteSpec writeSpec;
808
809                        synchronized (mLock) {
810                            adapter = mDocumentAdapter;
811                            writeSpec = mLastWriteSpec;
812                            mLastWriteSpec = null;
813                            cancellation = new CancellationSignal();
814                            mLayoutOrWriteCancellation = cancellation;
815                        }
816
817                        if (writeSpec != null && adapter != null) {
818                            if (DEBUG) {
819                                Log.i(LOG_TAG, "Performing write");
820                            }
821                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
822                                    cancellation, new MyWriteResultCallback(writeSpec.callback,
823                                            writeSpec.fd, writeSpec.sequence));
824                        }
825                    } break;
826
827                    case MSG_FINISH: {
828                        if (DEBUG) {
829                            Log.i(LOG_TAG, "Performing finish");
830                        }
831                        final PrintDocumentAdapter adapter;
832                        final Activity activity;
833                        synchronized (mLock) {
834                            adapter = mDocumentAdapter;
835                            activity = mActivity;
836                            clearLocked();
837                        }
838                        if (adapter != null) {
839                            adapter.onFinish();
840                        }
841                        if (activity != null) {
842                            activity.getApplication().unregisterActivityLifecycleCallbacks(
843                                    PrintDocumentAdapterDelegate.this);
844                        }
845                    } break;
846
847                    default: {
848                        throw new IllegalArgumentException("Unknown message: "
849                                + message.what);
850                    }
851                }
852            }
853        }
854
855        private final class MyLayoutResultCallback extends LayoutResultCallback {
856            private ILayoutResultCallback mCallback;
857            private final int mSequence;
858
859            public MyLayoutResultCallback(ILayoutResultCallback callback,
860                    int sequence) {
861                mCallback = callback;
862                mSequence = sequence;
863            }
864
865            @Override
866            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
867                if (info == null) {
868                    throw new NullPointerException("document info cannot be null");
869                }
870                final ILayoutResultCallback callback;
871                synchronized (mLock) {
872                    if (mDestroyed) {
873                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
874                                + "finish the printing activity before print completion?");
875                        return;
876                    }
877                    callback = mCallback;
878                    clearLocked();
879                }
880                if (callback != null) {
881                    try {
882                        callback.onLayoutFinished(info, changed, mSequence);
883                    } catch (RemoteException re) {
884                        Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
885                    }
886                }
887            }
888
889            @Override
890            public void onLayoutFailed(CharSequence error) {
891                final ILayoutResultCallback callback;
892                synchronized (mLock) {
893                    if (mDestroyed) {
894                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
895                                + "finish the printing activity before print completion?");
896                        return;
897                    }
898                    callback = mCallback;
899                    clearLocked();
900                }
901                if (callback != null) {
902                    try {
903                        callback.onLayoutFailed(error, mSequence);
904                    } catch (RemoteException re) {
905                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
906                    }
907                }
908            }
909
910            @Override
911            public void onLayoutCancelled() {
912                synchronized (mLock) {
913                    if (mDestroyed) {
914                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
915                                + "finish the printing activity before print completion?");
916                        return;
917                    }
918                    clearLocked();
919                }
920            }
921
922            private void clearLocked() {
923                mLayoutOrWriteCancellation = null;
924                mCallback = null;
925                doPendingWorkLocked();
926            }
927        }
928
929        private final class MyWriteResultCallback extends WriteResultCallback {
930            private ParcelFileDescriptor mFd;
931            private int mSequence;
932            private IWriteResultCallback mCallback;
933
934            public MyWriteResultCallback(IWriteResultCallback callback,
935                    ParcelFileDescriptor fd, int sequence) {
936                mFd = fd;
937                mSequence = sequence;
938                mCallback = callback;
939            }
940
941            @Override
942            public void onWriteFinished(PageRange[] pages) {
943                final IWriteResultCallback callback;
944                synchronized (mLock) {
945                    if (mDestroyed) {
946                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
947                                + "finish the printing activity before print completion?");
948                        return;
949                    }
950                    callback = mCallback;
951                    clearLocked();
952                }
953                if (pages == null) {
954                    throw new IllegalArgumentException("pages cannot be null");
955                }
956                if (pages.length == 0) {
957                    throw new IllegalArgumentException("pages cannot be empty");
958                }
959                if (callback != null) {
960                    try {
961                        callback.onWriteFinished(pages, mSequence);
962                    } catch (RemoteException re) {
963                        Log.e(LOG_TAG, "Error calling onWriteFinished", re);
964                    }
965                }
966            }
967
968            @Override
969            public void onWriteFailed(CharSequence error) {
970                final IWriteResultCallback callback;
971                synchronized (mLock) {
972                    if (mDestroyed) {
973                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
974                                + "finish the printing activity before print completion?");
975                        return;
976                    }
977                    callback = mCallback;
978                    clearLocked();
979                }
980                if (callback != null) {
981                    try {
982                        callback.onWriteFailed(error, mSequence);
983                    } catch (RemoteException re) {
984                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
985                    }
986                }
987            }
988
989            @Override
990            public void onWriteCancelled() {
991                synchronized (mLock) {
992                    if (mDestroyed) {
993                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
994                                + "finish the printing activity before print completion?");
995                        return;
996                    }
997                    clearLocked();
998                }
999            }
1000
1001            private void clearLocked() {
1002                mLayoutOrWriteCancellation = null;
1003                IoUtils.closeQuietly(mFd);
1004                mCallback = null;
1005                mFd = null;
1006                doPendingWorkLocked();
1007            }
1008        }
1009    }
1010
1011    private static final class PrintJobStateChangeListenerWrapper extends
1012            IPrintJobStateChangeListener.Stub {
1013        private final WeakReference<PrintJobStateChangeListener> mWeakListener;
1014        private final WeakReference<Handler> mWeakHandler;
1015
1016        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1017                Handler handler) {
1018            mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
1019            mWeakHandler = new WeakReference<Handler>(handler);
1020        }
1021
1022        @Override
1023        public void onPrintJobStateChanged(PrintJobId printJobId) {
1024            Handler handler = mWeakHandler.get();
1025            PrintJobStateChangeListener listener = mWeakListener.get();
1026            if (handler != null && listener != null) {
1027                SomeArgs args = SomeArgs.obtain();
1028                args.arg1 = this;
1029                args.arg2 = printJobId;
1030                handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
1031                        args).sendToTarget();
1032            }
1033        }
1034
1035        public void destroy() {
1036            mWeakListener.clear();
1037        }
1038
1039        public PrintJobStateChangeListener getListener() {
1040            return mWeakListener.get();
1041        }
1042    }
1043}
1044