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