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