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