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