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