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