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