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