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 
17 package android.print;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.annotation.SystemService;
23 import android.annotation.TestApi;
24 import android.app.Activity;
25 import android.app.Application.ActivityLifecycleCallbacks;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.IntentSender;
29 import android.content.IntentSender.SendIntentException;
30 import android.graphics.drawable.Icon;
31 import android.os.Bundle;
32 import android.os.CancellationSignal;
33 import android.os.Handler;
34 import android.os.ICancellationSignal;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.ParcelFileDescriptor;
38 import android.os.RemoteException;
39 import android.print.PrintDocumentAdapter.LayoutResultCallback;
40 import android.print.PrintDocumentAdapter.WriteResultCallback;
41 import android.printservice.PrintServiceInfo;
42 import android.printservice.recommendation.IRecommendationsChangeListener;
43 import android.printservice.recommendation.RecommendationInfo;
44 import android.text.TextUtils;
45 import android.util.ArrayMap;
46 import android.util.Log;
47 
48 import com.android.internal.os.SomeArgs;
49 import com.android.internal.util.Preconditions;
50 
51 import libcore.io.IoUtils;
52 
53 import java.lang.ref.WeakReference;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collections;
57 import java.util.List;
58 import 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)
107 public 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          */
onPrintJobStateChanged(PrintJobId printJobId)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          */
onPrintServicesChanged()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          */
onPrintServiceRecommendationsChanged()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      */
PrintManager(Context context, IPrintManager service, int userId, int appId)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      */
getGlobalPrintManagerForUser(int userId)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 
getPrintJobInfo(PrintJobId printJobId)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      */
addPrintJobStateChangeListener(PrintJobStateChangeListener listener)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      */
removePrintJobStateChangeListener(PrintJobStateChangeListener listener)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      */
getPrintJob(PrintJobId printJobId)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      */
getCustomPrinterIcon(PrinterId printerId)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      */
getPrintJobs()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 
cancelPrintJob(PrintJobId printJobId)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 
restartPrintJob(PrintJobId printJobId)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      */
print(@onNull String printJobName, @NonNull PrintDocumentAdapter documentAdapter, @Nullable PrintAttributes attributes)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
addPrintServicesChangeListener(@onNull PrintServicesChangeListener listener, @Nullable Handler handler)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
removePrintServicesChangeListener(@onNull PrintServicesChangeListener listener)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
getPrintServices(int selectionFlags)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
addPrintServiceRecommendationsChangeListener( @onNull PrintServiceRecommendationsChangeListener listener, @Nullable Handler handler)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
removePrintServiceRecommendationsChangeListener( @onNull PrintServiceRecommendationsChangeListener listener)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
getPrintServiceRecommendations()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      */
createPrinterDiscoverySession()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      */
setPrintServiceEnabled(@onNull ComponentName service, boolean isEnabled)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 
PrintDocumentAdapterDelegate(Activity activity, PrintDocumentAdapter documentAdapter)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
setObserver(IPrintDocumentAdapterObserver observer)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
start()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
layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, ILayoutResultCallback callback, Bundle metadata, int sequence)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
write(PageRange[] pages, ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence)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
finish()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
kill(String reason)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
onActivityPaused(Activity activity)927         public void onActivityPaused(Activity activity) {
928             /* do nothing */
929         }
930 
931         @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)932         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
933             /* do nothing */
934         }
935 
936         @Override
onActivityStarted(Activity activity)937         public void onActivityStarted(Activity activity) {
938             /* do nothing */
939         }
940 
941         @Override
onActivityResumed(Activity activity)942         public void onActivityResumed(Activity activity) {
943             /* do nothing */
944         }
945 
946         @Override
onActivityStopped(Activity activity)947         public void onActivityStopped(Activity activity) {
948             /* do nothing */
949         }
950 
951         @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)952         public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
953             /* do nothing */
954         }
955 
956         @Override
onActivityDestroyed(Activity activity)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 
isDestroyedLocked()979         private boolean isDestroyedLocked() {
980             return (mActivity == null);
981         }
982 
destroyLocked()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 
MyHandler(Looper looper)1014             public MyHandler(Looper looper) {
1015                 super(looper, null, true);
1016             }
1017 
1018             @Override
handleMessage(Message message)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 {
destroy()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 
MyLayoutResultCallback(ILayoutResultCallback callback, int sequence)1114             public MyLayoutResultCallback(ILayoutResultCallback callback,
1115                     int sequence) {
1116                 mCallback = callback;
1117                 mSequence = sequence;
1118             }
1119 
1120             @Override
onLayoutFinished(PrintDocumentInfo info, boolean changed)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
onLayoutFailed(CharSequence error)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
onLayoutCancelled()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
destroy()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 
MyWriteResultCallback(IWriteResultCallback callback, ParcelFileDescriptor fd, int sequence)1213             public MyWriteResultCallback(IWriteResultCallback callback,
1214                     ParcelFileDescriptor fd, int sequence) {
1215                 mFd = fd;
1216                 mSequence = sequence;
1217                 mCallback = callback;
1218             }
1219 
1220             @Override
onWriteFinished(PageRange[] pages)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
onWriteFailed(CharSequence error)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
onWriteCancelled()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
destroy()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 
PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, Handler handler)1321         public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1322                 Handler handler) {
1323             mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
1324             mWeakHandler = new WeakReference<Handler>(handler);
1325         }
1326 
1327         @Override
onPrintJobStateChanged(PrintJobId printJobId)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 
destroy()1340         public void destroy() {
1341             mWeakListener.clear();
1342         }
1343 
getListener()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 
PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener, Handler handler)1357         public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener,
1358                 Handler handler) {
1359             mWeakListener = new WeakReference<>(listener);
1360             mWeakHandler = new WeakReference<>(handler);
1361         }
1362 
1363         @Override
onPrintServicesChanged()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 
destroy()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 
PrintServiceRecommendationsChangeListenerWrapper( PrintServiceRecommendationsChangeListener listener, Handler handler)1385         public PrintServiceRecommendationsChangeListenerWrapper(
1386                 PrintServiceRecommendationsChangeListener listener, Handler handler) {
1387             mWeakListener = new WeakReference<>(listener);
1388             mWeakHandler = new WeakReference<>(handler);
1389         }
1390 
1391         @Override
onRecommendationsChanged()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 
destroy()1400         public void destroy() {
1401             mWeakListener.clear();
1402         }
1403     }
1404 }
1405