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