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