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