PrintManager.java revision 4d4c66dd38e940082e385b49a33f4022ab04c738
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 onActivityPaused(Activity activity) {
620            /* do nothing */
621        }
622
623        @Override
624        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
625            /* do nothing */
626        }
627
628        @Override
629        public void onActivityStarted(Activity activity) {
630            /* do nothing */
631        }
632
633        @Override
634        public void onActivityResumed(Activity activity) {
635            /* do nothing */
636        }
637
638        @Override
639        public void onActivityStopped(Activity activity) {
640            /* do nothing */
641        }
642
643        @Override
644        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
645            /* do nothing */
646        }
647
648        @Override
649        public void onActivityDestroyed(Activity activity) {
650            // We really care only if the activity is being destroyed to
651            // notify the the print spooler so it can close the print dialog.
652            // Note the the spooler has a death recipient that observes if
653            // this process gets killed so we cover the case of onDestroy not
654            // being called due to this process being killed to reclaim memory.
655            final IPrintDocumentAdapterObserver observer;
656            synchronized (mLock) {
657                if (activity == mActivity) {
658                    mDestroyed = true;
659                    observer = mObserver;
660                    clearLocked();
661                } else {
662                    observer = null;
663                    activity = null;
664                }
665            }
666            if (observer != null) {
667                activity.getApplication().unregisterActivityLifecycleCallbacks(
668                        PrintDocumentAdapterDelegate.this);
669                try {
670                    observer.onDestroy();
671                } catch (RemoteException re) {
672                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
673                }
674            }
675        }
676
677        private boolean isFinished() {
678            return mDocumentAdapter == null;
679        }
680
681        private void clearLocked() {
682            mActivity = null;
683            mDocumentAdapter = null;
684            mHandler = null;
685            mLayoutOrWriteCancellation = null;
686            mLastLayoutSpec = null;
687            if (mLastWriteSpec != null) {
688                IoUtils.closeQuietly(mLastWriteSpec.fd);
689                mLastWriteSpec = null;
690            }
691        }
692
693        private boolean cancelPreviousCancellableOperationLocked() {
694            if (mLayoutOrWriteCancellation != null) {
695                mLayoutOrWriteCancellation.cancel();
696                if (DEBUG) {
697                    Log.i(LOG_TAG, "Cancelling previous operation");
698                }
699                return true;
700            }
701            return false;
702        }
703
704        private void doPendingWorkLocked() {
705            if (mStartReqeusted && !mStarted) {
706                mStarted = true;
707                mHandler.sendEmptyMessage(MyHandler.MSG_START);
708            } else if (mLastLayoutSpec != null) {
709                mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
710            } else if (mLastWriteSpec != null) {
711                mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
712            } else if (mFinishRequested && !mFinished) {
713                mFinished = true;
714                mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
715            }
716        }
717
718        private class LayoutSpec {
719            ILayoutResultCallback callback;
720            PrintAttributes oldAttributes;
721            PrintAttributes newAttributes;
722            Bundle metadata;
723            int sequence;
724        }
725
726        private class WriteSpec {
727            IWriteResultCallback callback;
728            PageRange[] pages;
729            ParcelFileDescriptor fd;
730            int sequence;
731        }
732
733        private final class MyHandler extends Handler {
734            public static final int MSG_START = 1;
735            public static final int MSG_LAYOUT = 2;
736            public static final int MSG_WRITE = 3;
737            public static final int MSG_FINISH = 4;
738
739            public MyHandler(Looper looper) {
740                super(looper, null, true);
741            }
742
743            @Override
744            public void handleMessage(Message message) {
745                if (isFinished()) {
746                    return;
747                }
748                switch (message.what) {
749                    case MSG_START: {
750                        final PrintDocumentAdapter adapter;
751                        synchronized (mLock) {
752                            adapter = mDocumentAdapter;
753                        }
754                        if (adapter != null) {
755                            adapter.onStart();
756                        }
757                    } break;
758
759                    case MSG_LAYOUT: {
760                        final PrintDocumentAdapter adapter;
761                        final CancellationSignal cancellation;
762                        final LayoutSpec layoutSpec;
763
764                        synchronized (mLock) {
765                            adapter = mDocumentAdapter;
766                            layoutSpec = mLastLayoutSpec;
767                            mLastLayoutSpec = null;
768                            cancellation = new CancellationSignal();
769                            mLayoutOrWriteCancellation = cancellation;
770                        }
771
772                        if (layoutSpec != null && adapter != null) {
773                            if (DEBUG) {
774                                Log.i(LOG_TAG, "Performing layout");
775                            }
776                            adapter.onLayout(layoutSpec.oldAttributes,
777                                    layoutSpec.newAttributes, cancellation,
778                                    new MyLayoutResultCallback(layoutSpec.callback,
779                                            layoutSpec.sequence), layoutSpec.metadata);
780                        }
781                    } break;
782
783                    case MSG_WRITE: {
784                        final PrintDocumentAdapter adapter;
785                        final CancellationSignal cancellation;
786                        final WriteSpec writeSpec;
787
788                        synchronized (mLock) {
789                            adapter = mDocumentAdapter;
790                            writeSpec = mLastWriteSpec;
791                            mLastWriteSpec = null;
792                            cancellation = new CancellationSignal();
793                            mLayoutOrWriteCancellation = cancellation;
794                        }
795
796                        if (writeSpec != null && adapter != null) {
797                            if (DEBUG) {
798                                Log.i(LOG_TAG, "Performing write");
799                            }
800                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
801                                    cancellation, new MyWriteResultCallback(writeSpec.callback,
802                                            writeSpec.fd, writeSpec.sequence));
803                        }
804                    } break;
805
806                    case MSG_FINISH: {
807                        if (DEBUG) {
808                            Log.i(LOG_TAG, "Performing finish");
809                        }
810                        final PrintDocumentAdapter adapter;
811                        final Activity activity;
812                        synchronized (mLock) {
813                            adapter = mDocumentAdapter;
814                            activity = mActivity;
815                            clearLocked();
816                        }
817                        if (adapter != null) {
818                            adapter.onFinish();
819                        }
820                        if (activity != null) {
821                            activity.getApplication().unregisterActivityLifecycleCallbacks(
822                                    PrintDocumentAdapterDelegate.this);
823                        }
824                    } break;
825
826                    default: {
827                        throw new IllegalArgumentException("Unknown message: "
828                                + message.what);
829                    }
830                }
831            }
832        }
833
834        private final class MyLayoutResultCallback extends LayoutResultCallback {
835            private ILayoutResultCallback mCallback;
836            private final int mSequence;
837
838            public MyLayoutResultCallback(ILayoutResultCallback callback,
839                    int sequence) {
840                mCallback = callback;
841                mSequence = sequence;
842            }
843
844            @Override
845            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
846                if (info == null) {
847                    throw new NullPointerException("document info cannot be null");
848                }
849                final ILayoutResultCallback callback;
850                synchronized (mLock) {
851                    callback = mCallback;
852                    clearLocked();
853                }
854                if (callback != null) {
855                    try {
856                        callback.onLayoutFinished(info, changed, mSequence);
857                    } catch (RemoteException re) {
858                        Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
859                    }
860                }
861            }
862
863            @Override
864            public void onLayoutFailed(CharSequence error) {
865                final ILayoutResultCallback callback;
866                synchronized (mLock) {
867                    callback = mCallback;
868                    clearLocked();
869                }
870                if (callback != null) {
871                    try {
872                        callback.onLayoutFailed(error, mSequence);
873                    } catch (RemoteException re) {
874                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
875                    }
876                }
877            }
878
879            @Override
880            public void onLayoutCancelled() {
881                synchronized (mLock) {
882                    clearLocked();
883                }
884            }
885
886            private void clearLocked() {
887                mLayoutOrWriteCancellation = null;
888                mCallback = null;
889                doPendingWorkLocked();
890            }
891        }
892
893        private final class MyWriteResultCallback extends WriteResultCallback {
894            private ParcelFileDescriptor mFd;
895            private int mSequence;
896            private IWriteResultCallback mCallback;
897
898            public MyWriteResultCallback(IWriteResultCallback callback,
899                    ParcelFileDescriptor fd, int sequence) {
900                mFd = fd;
901                mSequence = sequence;
902                mCallback = callback;
903            }
904
905            @Override
906            public void onWriteFinished(PageRange[] pages) {
907                final IWriteResultCallback callback;
908                synchronized (mLock) {
909                    callback = mCallback;
910                    clearLocked();
911                }
912                if (pages == null) {
913                    throw new IllegalArgumentException("pages cannot be null");
914                }
915                if (pages.length == 0) {
916                    throw new IllegalArgumentException("pages cannot be empty");
917                }
918                if (callback != null) {
919                    try {
920                        callback.onWriteFinished(pages, mSequence);
921                    } catch (RemoteException re) {
922                        Log.e(LOG_TAG, "Error calling onWriteFinished", re);
923                    }
924                }
925            }
926
927            @Override
928            public void onWriteFailed(CharSequence error) {
929                final IWriteResultCallback callback;
930                synchronized (mLock) {
931                    callback = mCallback;
932                    clearLocked();
933                }
934                if (callback != null) {
935                    try {
936                        callback.onWriteFailed(error, mSequence);
937                    } catch (RemoteException re) {
938                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
939                    }
940                }
941            }
942
943            @Override
944            public void onWriteCancelled() {
945                synchronized (mLock) {
946                    clearLocked();
947                }
948            }
949
950            private void clearLocked() {
951                mLayoutOrWriteCancellation = null;
952                IoUtils.closeQuietly(mFd);
953                mCallback = null;
954                mFd = null;
955                doPendingWorkLocked();
956            }
957        }
958    }
959
960    private static final class PrintJobStateChangeListenerWrapper extends
961            IPrintJobStateChangeListener.Stub {
962        private final WeakReference<PrintJobStateChangeListener> mWeakListener;
963        private final WeakReference<Handler> mWeakHandler;
964
965        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
966                Handler handler) {
967            mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
968            mWeakHandler = new WeakReference<Handler>(handler);
969        }
970
971        @Override
972        public void onPrintJobStateChanged(PrintJobId printJobId) {
973            Handler handler = mWeakHandler.get();
974            PrintJobStateChangeListener listener = mWeakListener.get();
975            if (handler != null && listener != null) {
976                SomeArgs args = SomeArgs.obtain();
977                args.arg1 = this;
978                args.arg2 = printJobId;
979                handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
980                        args).sendToTarget();
981            }
982        }
983
984        public void destroy() {
985            mWeakListener.clear();
986        }
987
988        public PrintJobStateChangeListener getListener() {
989            return mWeakListener.get();
990        }
991    }
992}
993