PrintManager.java revision 2235a1772fc3c72b5c1795310e221d613cae01da
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        if (mService == null) {
218            Log.w(LOG_TAG, "Feature android.software.print not available");
219            return null;
220        }
221        return new PrintManager(mContext, mService, userId, APP_ID_ANY);
222    }
223
224    PrintJobInfo getPrintJobInfo(PrintJobId printJobId) {
225        try {
226            return mService.getPrintJobInfo(printJobId, mAppId, mUserId);
227        } catch (RemoteException re) {
228            Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re);
229        }
230        return null;
231    }
232
233    /**
234     * Adds a listener for observing the state of print jobs.
235     *
236     * @param listener The listener to add.
237     * @hide
238     */
239    public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
240        if (mService == null) {
241            Log.w(LOG_TAG, "Feature android.software.print not available");
242            return;
243        }
244        if (mPrintJobStateChangeListeners == null) {
245            mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
246                    PrintJobStateChangeListenerWrapper>();
247        }
248        PrintJobStateChangeListenerWrapper wrappedListener =
249                new PrintJobStateChangeListenerWrapper(listener, mHandler);
250        try {
251            mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
252            mPrintJobStateChangeListeners.put(listener, wrappedListener);
253        } catch (RemoteException re) {
254            Log.e(LOG_TAG, "Error adding print job state change listener", re);
255        }
256    }
257
258    /**
259     * Removes a listener for observing the state of print jobs.
260     *
261     * @param listener The listener to remove.
262     * @hide
263     */
264    public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
265        if (mService == null) {
266            Log.w(LOG_TAG, "Feature android.software.print not available");
267            return;
268        }
269        if (mPrintJobStateChangeListeners == null) {
270            return;
271        }
272        PrintJobStateChangeListenerWrapper wrappedListener =
273                mPrintJobStateChangeListeners.remove(listener);
274        if (wrappedListener == null) {
275            return;
276        }
277        if (mPrintJobStateChangeListeners.isEmpty()) {
278            mPrintJobStateChangeListeners = null;
279        }
280        wrappedListener.destroy();
281        try {
282            mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
283        } catch (RemoteException re) {
284            Log.e(LOG_TAG, "Error removing print job state change listener", re);
285        }
286    }
287
288    /**
289     * Gets a print job given its id.
290     *
291     * @return The print job list.
292     * @see PrintJob
293     * @hide
294     */
295    public PrintJob getPrintJob(PrintJobId printJobId) {
296        if (mService == null) {
297            Log.w(LOG_TAG, "Feature android.software.print not available");
298            return null;
299        }
300        try {
301            PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
302            if (printJob != null) {
303                return new PrintJob(printJob, this);
304            }
305        } catch (RemoteException re) {
306            Log.e(LOG_TAG, "Error getting print job", re);
307        }
308        return null;
309    }
310
311    /**
312     * Gets the print jobs for this application.
313     *
314     * @return The print job list.
315     * @see PrintJob
316     */
317    public List<PrintJob> getPrintJobs() {
318        if (mService == null) {
319            Log.w(LOG_TAG, "Feature android.software.print not available");
320            return Collections.emptyList();
321        }
322        try {
323            List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId);
324            if (printJobInfos == null) {
325                return Collections.emptyList();
326            }
327            final int printJobCount = printJobInfos.size();
328            List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount);
329            for (int i = 0; i < printJobCount; i++) {
330                printJobs.add(new PrintJob(printJobInfos.get(i), this));
331            }
332            return printJobs;
333        } catch (RemoteException re) {
334            Log.e(LOG_TAG, "Error getting print jobs", re);
335        }
336        return Collections.emptyList();
337    }
338
339    void cancelPrintJob(PrintJobId printJobId) {
340        if (mService == null) {
341            Log.w(LOG_TAG, "Feature android.software.print not available");
342            return;
343        }
344        try {
345            mService.cancelPrintJob(printJobId, mAppId, mUserId);
346        } catch (RemoteException re) {
347            Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
348        }
349    }
350
351    void restartPrintJob(PrintJobId printJobId) {
352        if (mService == null) {
353            Log.w(LOG_TAG, "Feature android.software.print not available");
354            return;
355        }
356        try {
357            mService.restartPrintJob(printJobId, mAppId, mUserId);
358        } catch (RemoteException re) {
359            Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re);
360        }
361    }
362
363    /**
364     * Creates a print job for printing a {@link PrintDocumentAdapter} with
365     * default print attributes.
366     * <p>
367     * Calling this method brings the print UI allowing the user to customize
368     * the print job and returns a {@link PrintJob} object without waiting for the
369     * user to customize or confirm the print job. The returned print job instance
370     * is in a {@link PrintJobInfo#STATE_CREATED created} state.
371     * <p>
372     * This method can be called only from an {@link Activity}. The rationale is that
373     * printing from a service will create an inconsistent user experience as the print
374     * UI would appear without any context.
375     * </p>
376     * <p>
377     * Also the passed in {@link PrintDocumentAdapter} will be considered invalid if
378     * your activity is finished. The rationale is that once the activity that
379     * initiated printing is finished, the provided adapter may be in an inconsistent
380     * state as it may depend on the UI presented by the activity.
381     * </p>
382     * <p>
383     * The default print attributes are a hint to the system how the data is to
384     * be printed. For example, a photo editor may look at the photo aspect ratio
385     * to determine the default orientation and provide a hint whether the printing
386     * should be in portrait or landscape. The system will do a best effort to
387     * selected the hinted options in the print dialog, given the current printer
388     * supports them.
389     * </p>
390     * <p>
391     * <strong>Note:</strong> Calling this method will bring the print dialog and
392     * the system will connect to the provided {@link PrintDocumentAdapter}. If a
393     * configuration change occurs that you application does not handle, for example
394     * a rotation change, the system will drop the connection to the adapter as the
395     * activity has to be recreated and the old adapter may be invalid in this context,
396     * hence a new adapter instance is required. As a consequence, if your activity
397     * does not handle configuration changes (default behavior), you have to save the
398     * state that you were printing and call this method again when your activity
399     * is recreated.
400     * </p>
401     *
402     * @param printJobName A name for the new print job which is shown to the user.
403     * @param documentAdapter An adapter that emits the document to print.
404     * @param attributes The default print job attributes or <code>null</code>.
405     * @return The created print job on success or null on failure.
406     * @throws IllegalStateException If not called from an {@link Activity}.
407     * @throws IllegalArgumentException If the print job name is empty or the
408     * document adapter is null.
409     *
410     * @see PrintJob
411     */
412    public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
413            PrintAttributes attributes) {
414        if (mService == null) {
415            Log.w(LOG_TAG, "Feature android.software.print not available");
416            return null;
417        }
418        if (!(mContext instanceof Activity)) {
419            throw new IllegalStateException("Can print only from an activity");
420        }
421        if (TextUtils.isEmpty(printJobName)) {
422            throw new IllegalArgumentException("printJobName cannot be empty");
423        }
424        if (documentAdapter == null) {
425            throw new IllegalArgumentException("documentAdapter cannot be null");
426        }
427        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
428                (Activity) mContext, documentAdapter);
429        try {
430            Bundle result = mService.print(printJobName, delegate,
431                    attributes, mContext.getPackageName(), mAppId, mUserId);
432            if (result != null) {
433                PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
434                IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
435                if (printJob == null || intent == null) {
436                    return null;
437                }
438                try {
439                    mContext.startIntentSender(intent, null, 0, 0, 0);
440                    return new PrintJob(printJob, this);
441                } catch (SendIntentException sie) {
442                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
443                }
444            }
445        } catch (RemoteException re) {
446            Log.e(LOG_TAG, "Error creating a print job", re);
447        }
448        return null;
449    }
450
451    /**
452     * Gets the list of enabled print services.
453     *
454     * @return The enabled service list or an empty list.
455     * @hide
456     */
457    public List<PrintServiceInfo> getEnabledPrintServices() {
458        if (mService == null) {
459            Log.w(LOG_TAG, "Feature android.software.print not available");
460            return Collections.emptyList();
461        }
462        try {
463            List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
464            if (enabledServices != null) {
465                return enabledServices;
466            }
467        } catch (RemoteException re) {
468            Log.e(LOG_TAG, "Error getting the enabled print services", re);
469        }
470        return Collections.emptyList();
471    }
472
473    /**
474     * Gets the list of installed print services.
475     *
476     * @return The installed service list or an empty list.
477     * @hide
478     */
479    public List<PrintServiceInfo> getInstalledPrintServices() {
480        if (mService == null) {
481            Log.w(LOG_TAG, "Feature android.software.print not available");
482            return Collections.emptyList();
483        }
484        try {
485            List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
486            if (installedServices != null) {
487                return installedServices;
488            }
489        } catch (RemoteException re) {
490            Log.e(LOG_TAG, "Error getting the installed print services", re);
491        }
492        return Collections.emptyList();
493    }
494
495    /**
496     * @hide
497     */
498    public PrinterDiscoverySession createPrinterDiscoverySession() {
499        if (mService == null) {
500            Log.w(LOG_TAG, "Feature android.software.print not available");
501            return null;
502        }
503        return new PrinterDiscoverySession(mService, mContext, mUserId);
504    }
505
506    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
507            implements ActivityLifecycleCallbacks {
508
509        private final Object mLock = new Object();
510
511        private CancellationSignal mLayoutOrWriteCancellation;
512
513        private Activity mActivity; // Strong reference OK - cleared in finish()
514
515        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
516
517        private Handler mHandler; // Strong reference OK - cleared in finish()
518
519        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
520
521        private LayoutSpec mLastLayoutSpec;
522
523        private WriteSpec mLastWriteSpec;
524
525        private boolean mStartReqeusted;
526        private boolean mStarted;
527
528        private boolean mFinishRequested;
529        private boolean mFinished;
530
531        private boolean mDestroyed;
532
533        public PrintDocumentAdapterDelegate(Activity activity,
534                PrintDocumentAdapter documentAdapter) {
535            mActivity = activity;
536            mDocumentAdapter = documentAdapter;
537            mHandler = new MyHandler(mActivity.getMainLooper());
538            mActivity.getApplication().registerActivityLifecycleCallbacks(this);
539        }
540
541        @Override
542        public void setObserver(IPrintDocumentAdapterObserver observer) {
543            final boolean destroyed;
544            synchronized (mLock) {
545                if (!mDestroyed) {
546                    mObserver = observer;
547                }
548                destroyed = mDestroyed;
549            }
550            if (destroyed) {
551                try {
552                    observer.onDestroy();
553                } catch (RemoteException re) {
554                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
555                }
556            }
557        }
558
559        @Override
560        public void start() {
561            synchronized (mLock) {
562                // Started called or finish called or destroyed - nothing to do.
563                if (mStartReqeusted || mFinishRequested || mDestroyed) {
564                    return;
565                }
566
567                mStartReqeusted = true;
568
569                doPendingWorkLocked();
570            }
571        }
572
573        @Override
574        public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
575                ILayoutResultCallback callback, Bundle metadata, int sequence) {
576            final boolean destroyed;
577            synchronized (mLock) {
578                destroyed = mDestroyed;
579                // If start called and not finished called and not destroyed - do some work.
580                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
581                    // Layout cancels write and overrides layout.
582                    if (mLastWriteSpec != null) {
583                        IoUtils.closeQuietly(mLastWriteSpec.fd);
584                        mLastWriteSpec = null;
585                    }
586
587                    mLastLayoutSpec = new LayoutSpec();
588                    mLastLayoutSpec.callback = callback;
589                    mLastLayoutSpec.oldAttributes = oldAttributes;
590                    mLastLayoutSpec.newAttributes = newAttributes;
591                    mLastLayoutSpec.metadata = metadata;
592                    mLastLayoutSpec.sequence = sequence;
593
594                    // Cancel the previous cancellable operation.When the
595                    // cancellation completes we will do the pending work.
596                    if (cancelPreviousCancellableOperationLocked()) {
597                        return;
598                    }
599
600                    doPendingWorkLocked();
601                }
602            }
603            if (destroyed) {
604                try {
605                    callback.onLayoutFailed(null, sequence);
606                } catch (RemoteException re) {
607                    Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
608                }
609            }
610        }
611
612        @Override
613        public void write(PageRange[] pages, ParcelFileDescriptor fd,
614                IWriteResultCallback callback, int sequence) {
615            final boolean destroyed;
616            synchronized (mLock) {
617                destroyed = mDestroyed;
618                // If start called and not finished called and not destroyed - do some work.
619                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
620                    // Write cancels previous writes.
621                    if (mLastWriteSpec != null) {
622                        IoUtils.closeQuietly(mLastWriteSpec.fd);
623                        mLastWriteSpec = null;
624                    }
625
626                    mLastWriteSpec = new WriteSpec();
627                    mLastWriteSpec.callback = callback;
628                    mLastWriteSpec.pages = pages;
629                    mLastWriteSpec.fd = fd;
630                    mLastWriteSpec.sequence = sequence;
631
632                    // Cancel the previous cancellable operation.When the
633                    // cancellation completes we will do the pending work.
634                    if (cancelPreviousCancellableOperationLocked()) {
635                        return;
636                    }
637
638                    doPendingWorkLocked();
639                }
640            }
641            if (destroyed) {
642                try {
643                    callback.onWriteFailed(null, sequence);
644                } catch (RemoteException re) {
645                    Log.i(LOG_TAG, "Error notifying for cancelled write", re);
646                }
647            }
648        }
649
650        @Override
651        public void finish() {
652            synchronized (mLock) {
653                // Start not called or finish called or destroyed - nothing to do.
654                if (!mStartReqeusted || mFinishRequested || mDestroyed) {
655                    return;
656                }
657
658                mFinishRequested = true;
659
660                // When the current write or layout complete we
661                // will do the pending work.
662                if (mLastLayoutSpec != null || mLastWriteSpec != null) {
663                    if (DEBUG) {
664                        Log.i(LOG_TAG, "Waiting for current operation");
665                    }
666                    return;
667                }
668
669                doPendingWorkLocked();
670            }
671        }
672
673        @Override
674        public void cancel() {
675            // Start not called or finish called or destroyed - nothing to do.
676            if (!mStartReqeusted || mFinishRequested || mDestroyed) {
677                return;
678            }
679            // Request cancellation of pending work if needed.
680            synchronized (mLock) {
681                cancelPreviousCancellableOperationLocked();
682            }
683        }
684
685        @Override
686        public void onActivityPaused(Activity activity) {
687            /* do nothing */
688        }
689
690        @Override
691        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
692            /* do nothing */
693        }
694
695        @Override
696        public void onActivityStarted(Activity activity) {
697            /* do nothing */
698        }
699
700        @Override
701        public void onActivityResumed(Activity activity) {
702            /* do nothing */
703        }
704
705        @Override
706        public void onActivityStopped(Activity activity) {
707            /* do nothing */
708        }
709
710        @Override
711        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
712            /* do nothing */
713        }
714
715        @Override
716        public void onActivityDestroyed(Activity activity) {
717            // We really care only if the activity is being destroyed to
718            // notify the the print spooler so it can close the print dialog.
719            // Note the the spooler has a death recipient that observes if
720            // this process gets killed so we cover the case of onDestroy not
721            // being called due to this process being killed to reclaim memory.
722            final IPrintDocumentAdapterObserver observer;
723            synchronized (mLock) {
724                if (activity == mActivity) {
725                    mDestroyed = true;
726                    observer = mObserver;
727                    clearLocked();
728                } else {
729                    observer = null;
730                    activity = null;
731                }
732            }
733            if (observer != null) {
734                activity.getApplication().unregisterActivityLifecycleCallbacks(
735                        PrintDocumentAdapterDelegate.this);
736                try {
737                    observer.onDestroy();
738                } catch (RemoteException re) {
739                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
740                }
741            }
742        }
743
744        private boolean isFinished() {
745            return mDocumentAdapter == null;
746        }
747
748        private void clearLocked() {
749            mActivity = null;
750            mDocumentAdapter = null;
751            mHandler = null;
752            mLayoutOrWriteCancellation = null;
753            mLastLayoutSpec = null;
754            if (mLastWriteSpec != null) {
755                IoUtils.closeQuietly(mLastWriteSpec.fd);
756                mLastWriteSpec = null;
757            }
758        }
759
760        private boolean cancelPreviousCancellableOperationLocked() {
761            if (mLayoutOrWriteCancellation != null) {
762                mLayoutOrWriteCancellation.cancel();
763                if (DEBUG) {
764                    Log.i(LOG_TAG, "Cancelling previous operation");
765                }
766                return true;
767            }
768            return false;
769        }
770
771        private void doPendingWorkLocked() {
772            if (mStartReqeusted && !mStarted) {
773                mStarted = true;
774                mHandler.sendEmptyMessage(MyHandler.MSG_START);
775            } else if (mLastLayoutSpec != null) {
776                mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
777            } else if (mLastWriteSpec != null) {
778                mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
779            } else if (mFinishRequested && !mFinished) {
780                mFinished = true;
781                mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
782            }
783        }
784
785        private class LayoutSpec {
786            ILayoutResultCallback callback;
787            PrintAttributes oldAttributes;
788            PrintAttributes newAttributes;
789            Bundle metadata;
790            int sequence;
791        }
792
793        private class WriteSpec {
794            IWriteResultCallback callback;
795            PageRange[] pages;
796            ParcelFileDescriptor fd;
797            int sequence;
798        }
799
800        private final class MyHandler extends Handler {
801            public static final int MSG_START = 1;
802            public static final int MSG_LAYOUT = 2;
803            public static final int MSG_WRITE = 3;
804            public static final int MSG_FINISH = 4;
805
806            public MyHandler(Looper looper) {
807                super(looper, null, true);
808            }
809
810            @Override
811            public void handleMessage(Message message) {
812                if (isFinished()) {
813                    return;
814                }
815                switch (message.what) {
816                    case MSG_START: {
817                        final PrintDocumentAdapter adapter;
818                        synchronized (mLock) {
819                            adapter = mDocumentAdapter;
820                        }
821                        if (adapter != null) {
822                            adapter.onStart();
823                        }
824                    } break;
825
826                    case MSG_LAYOUT: {
827                        final PrintDocumentAdapter adapter;
828                        final CancellationSignal cancellation;
829                        final LayoutSpec layoutSpec;
830
831                        synchronized (mLock) {
832                            adapter = mDocumentAdapter;
833                            layoutSpec = mLastLayoutSpec;
834                            mLastLayoutSpec = null;
835                            cancellation = new CancellationSignal();
836                            mLayoutOrWriteCancellation = cancellation;
837                        }
838
839                        if (layoutSpec != null && adapter != null) {
840                            if (DEBUG) {
841                                Log.i(LOG_TAG, "Performing layout");
842                            }
843                            adapter.onLayout(layoutSpec.oldAttributes,
844                                    layoutSpec.newAttributes, cancellation,
845                                    new MyLayoutResultCallback(layoutSpec.callback,
846                                            layoutSpec.sequence), layoutSpec.metadata);
847                        }
848                    } break;
849
850                    case MSG_WRITE: {
851                        final PrintDocumentAdapter adapter;
852                        final CancellationSignal cancellation;
853                        final WriteSpec writeSpec;
854
855                        synchronized (mLock) {
856                            adapter = mDocumentAdapter;
857                            writeSpec = mLastWriteSpec;
858                            mLastWriteSpec = null;
859                            cancellation = new CancellationSignal();
860                            mLayoutOrWriteCancellation = cancellation;
861                        }
862
863                        if (writeSpec != null && adapter != null) {
864                            if (DEBUG) {
865                                Log.i(LOG_TAG, "Performing write");
866                            }
867                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
868                                    cancellation, new MyWriteResultCallback(writeSpec.callback,
869                                            writeSpec.fd, writeSpec.sequence));
870                        }
871                    } break;
872
873                    case MSG_FINISH: {
874                        if (DEBUG) {
875                            Log.i(LOG_TAG, "Performing finish");
876                        }
877                        final PrintDocumentAdapter adapter;
878                        final Activity activity;
879                        synchronized (mLock) {
880                            adapter = mDocumentAdapter;
881                            activity = mActivity;
882                            clearLocked();
883                        }
884                        if (adapter != null) {
885                            adapter.onFinish();
886                        }
887                        if (activity != null) {
888                            activity.getApplication().unregisterActivityLifecycleCallbacks(
889                                    PrintDocumentAdapterDelegate.this);
890                        }
891                    } break;
892
893                    default: {
894                        throw new IllegalArgumentException("Unknown message: "
895                                + message.what);
896                    }
897                }
898            }
899        }
900
901        private final class MyLayoutResultCallback extends LayoutResultCallback {
902            private ILayoutResultCallback mCallback;
903            private final int mSequence;
904
905            public MyLayoutResultCallback(ILayoutResultCallback callback,
906                    int sequence) {
907                mCallback = callback;
908                mSequence = sequence;
909            }
910
911            @Override
912            public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
913                if (info == null) {
914                    throw new NullPointerException("document info cannot be null");
915                }
916                final ILayoutResultCallback callback;
917                synchronized (mLock) {
918                    if (mDestroyed) {
919                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
920                                + "finish the printing activity before print completion?");
921                        return;
922                    }
923                    callback = mCallback;
924                    clearLocked();
925                }
926                if (callback != null) {
927                    try {
928                        callback.onLayoutFinished(info, changed, mSequence);
929                    } catch (RemoteException re) {
930                        Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
931                    }
932                }
933            }
934
935            @Override
936            public void onLayoutFailed(CharSequence error) {
937                final ILayoutResultCallback callback;
938                synchronized (mLock) {
939                    if (mDestroyed) {
940                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
941                                + "finish the printing activity before print completion?");
942                        return;
943                    }
944                    callback = mCallback;
945                    clearLocked();
946                }
947                if (callback != null) {
948                    try {
949                        callback.onLayoutFailed(error, mSequence);
950                    } catch (RemoteException re) {
951                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
952                    }
953                }
954            }
955
956            @Override
957            public void onLayoutCancelled() {
958                synchronized (mLock) {
959                    if (mDestroyed) {
960                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
961                                + "finish the printing activity before print completion?");
962                        return;
963                    }
964                    clearLocked();
965                }
966            }
967
968            private void clearLocked() {
969                mLayoutOrWriteCancellation = null;
970                mCallback = null;
971                doPendingWorkLocked();
972            }
973        }
974
975        private final class MyWriteResultCallback extends WriteResultCallback {
976            private ParcelFileDescriptor mFd;
977            private int mSequence;
978            private IWriteResultCallback mCallback;
979
980            public MyWriteResultCallback(IWriteResultCallback callback,
981                    ParcelFileDescriptor fd, int sequence) {
982                mFd = fd;
983                mSequence = sequence;
984                mCallback = callback;
985            }
986
987            @Override
988            public void onWriteFinished(PageRange[] pages) {
989                final IWriteResultCallback callback;
990                synchronized (mLock) {
991                    if (mDestroyed) {
992                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
993                                + "finish the printing activity before print completion?");
994                        return;
995                    }
996                    callback = mCallback;
997                    clearLocked();
998                }
999                if (pages == null) {
1000                    throw new IllegalArgumentException("pages cannot be null");
1001                }
1002                if (pages.length == 0) {
1003                    throw new IllegalArgumentException("pages cannot be empty");
1004                }
1005                if (callback != null) {
1006                    try {
1007                        callback.onWriteFinished(pages, mSequence);
1008                    } catch (RemoteException re) {
1009                        Log.e(LOG_TAG, "Error calling onWriteFinished", re);
1010                    }
1011                }
1012            }
1013
1014            @Override
1015            public void onWriteFailed(CharSequence error) {
1016                final IWriteResultCallback callback;
1017                synchronized (mLock) {
1018                    if (mDestroyed) {
1019                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1020                                + "finish the printing activity before print completion?");
1021                        return;
1022                    }
1023                    callback = mCallback;
1024                    clearLocked();
1025                }
1026                if (callback != null) {
1027                    try {
1028                        callback.onWriteFailed(error, mSequence);
1029                    } catch (RemoteException re) {
1030                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
1031                    }
1032                }
1033            }
1034
1035            @Override
1036            public void onWriteCancelled() {
1037                synchronized (mLock) {
1038                    if (mDestroyed) {
1039                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1040                                + "finish the printing activity before print completion?");
1041                        return;
1042                    }
1043                    clearLocked();
1044                }
1045            }
1046
1047            private void clearLocked() {
1048                mLayoutOrWriteCancellation = null;
1049                IoUtils.closeQuietly(mFd);
1050                mCallback = null;
1051                mFd = null;
1052                doPendingWorkLocked();
1053            }
1054        }
1055    }
1056
1057    private static final class PrintJobStateChangeListenerWrapper extends
1058            IPrintJobStateChangeListener.Stub {
1059        private final WeakReference<PrintJobStateChangeListener> mWeakListener;
1060        private final WeakReference<Handler> mWeakHandler;
1061
1062        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1063                Handler handler) {
1064            mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
1065            mWeakHandler = new WeakReference<Handler>(handler);
1066        }
1067
1068        @Override
1069        public void onPrintJobStateChanged(PrintJobId printJobId) {
1070            Handler handler = mWeakHandler.get();
1071            PrintJobStateChangeListener listener = mWeakListener.get();
1072            if (handler != null && listener != null) {
1073                SomeArgs args = SomeArgs.obtain();
1074                args.arg1 = this;
1075                args.arg2 = printJobId;
1076                handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
1077                        args).sendToTarget();
1078            }
1079        }
1080
1081        public void destroy() {
1082            mWeakListener.clear();
1083        }
1084
1085        public PrintJobStateChangeListener getListener() {
1086            return mWeakListener.get();
1087        }
1088    }
1089}
1090