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