• 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
kill(String reason)949         public void kill(String reason) {
950             synchronized (mLock) {
951                 // If destroyed the handler is null.
952                 if (!isDestroyedLocked()) {
953                     mHandler.obtainMessage(MyHandler.MSG_ON_KILL,
954                             reason).sendToTarget();
955                 }
956             }
957         }
958 
959         @Override
onActivityPaused(Activity activity)960         public void onActivityPaused(Activity activity) {
961             /* do nothing */
962         }
963 
964         @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)965         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
966             /* do nothing */
967         }
968 
969         @Override
onActivityStarted(Activity activity)970         public void onActivityStarted(Activity activity) {
971             /* do nothing */
972         }
973 
974         @Override
onActivityResumed(Activity activity)975         public void onActivityResumed(Activity activity) {
976             /* do nothing */
977         }
978 
979         @Override
onActivityStopped(Activity activity)980         public void onActivityStopped(Activity activity) {
981             /* do nothing */
982         }
983 
984         @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)985         public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
986             /* do nothing */
987         }
988 
989         @Override
onActivityDestroyed(Activity activity)990         public void onActivityDestroyed(Activity activity) {
991             // We really care only if the activity is being destroyed to
992             // notify the the print spooler so it can close the print dialog.
993             // Note the the spooler has a death recipient that observes if
994             // this process gets killed so we cover the case of onDestroy not
995             // being called due to this process being killed to reclaim memory.
996             IPrintDocumentAdapterObserver observer = null;
997             synchronized (mLock) {
998                 if (activity == mActivity) {
999                     observer = mObserver;
1000                     destroyLocked();
1001                 }
1002             }
1003             if (observer != null) {
1004                 try {
1005                     observer.onDestroy();
1006                 } catch (RemoteException re) {
1007                     Log.e(LOG_TAG, "Error announcing destroyed state", re);
1008                 }
1009             }
1010         }
1011 
isDestroyedLocked()1012         private boolean isDestroyedLocked() {
1013             return (mActivity == null);
1014         }
1015 
destroyLocked()1016         private void destroyLocked() {
1017             mActivity.getApplication().unregisterActivityLifecycleCallbacks(
1018                     PrintDocumentAdapterDelegate.this);
1019             mActivity = null;
1020 
1021             mDocumentAdapter = null;
1022 
1023             // This method is only called from the main thread, so
1024             // clearing the messages guarantees that any time a
1025             // message is handled we are not in a destroyed state.
1026             mHandler.removeMessages(MyHandler.MSG_ON_START);
1027             mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
1028             mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
1029             mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
1030             mHandler = null;
1031 
1032             mObserver = null;
1033 
1034             if (mPendingCallback != null) {
1035                 mPendingCallback.destroy();
1036                 mPendingCallback = null;
1037             }
1038         }
1039 
1040         private final class MyHandler extends Handler {
1041             public static final int MSG_ON_START = 1;
1042             public static final int MSG_ON_LAYOUT = 2;
1043             public static final int MSG_ON_WRITE = 3;
1044             public static final int MSG_ON_FINISH = 4;
1045             public static final int MSG_ON_KILL = 5;
1046 
MyHandler(Looper looper)1047             public MyHandler(Looper looper) {
1048                 super(looper, null, true);
1049             }
1050 
1051             @Override
handleMessage(Message message)1052             public void handleMessage(Message message) {
1053                 switch (message.what) {
1054                     case MSG_ON_START: {
1055                         if (DEBUG) {
1056                             Log.i(LOG_TAG, "onStart()");
1057                         }
1058 
1059                         ((PrintDocumentAdapter) message.obj).onStart();
1060                     } break;
1061 
1062                     case MSG_ON_LAYOUT: {
1063                         SomeArgs args = (SomeArgs) message.obj;
1064                         PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
1065                         PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
1066                         PrintAttributes newAttributes = (PrintAttributes) args.arg3;
1067                         CancellationSignal cancellation = (CancellationSignal) args.arg4;
1068                         LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
1069                         Bundle metadata = (Bundle) args.arg6;
1070                         args.recycle();
1071 
1072                         if (DEBUG) {
1073                             StringBuilder builder = new StringBuilder();
1074                             builder.append("PrintDocumentAdapter#onLayout() {\n");
1075                             builder.append("\n  oldAttributes:").append(oldAttributes);
1076                             builder.append("\n  newAttributes:").append(newAttributes);
1077                             builder.append("\n  preview:").append(metadata.getBoolean(
1078                                     PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
1079                             builder.append("\n}");
1080                             Log.i(LOG_TAG, builder.toString());
1081                         }
1082 
1083                         adapter.onLayout(oldAttributes, newAttributes, cancellation,
1084                                 callback, metadata);
1085                     } break;
1086 
1087                     case MSG_ON_WRITE: {
1088                         SomeArgs args = (SomeArgs) message.obj;
1089                         PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
1090                         PageRange[] pages = (PageRange[]) args.arg2;
1091                         ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
1092                         CancellationSignal cancellation = (CancellationSignal) args.arg4;
1093                         WriteResultCallback callback = (WriteResultCallback) args.arg5;
1094                         args.recycle();
1095 
1096                         if (DEBUG) {
1097                             StringBuilder builder = new StringBuilder();
1098                             builder.append("PrintDocumentAdapter#onWrite() {\n");
1099                             builder.append("\n  pages:").append(Arrays.toString(pages));
1100                             builder.append("\n}");
1101                             Log.i(LOG_TAG, builder.toString());
1102                         }
1103 
1104                         adapter.onWrite(pages, fd, cancellation, callback);
1105                     } break;
1106 
1107                     case MSG_ON_FINISH: {
1108                         if (DEBUG) {
1109                             Log.i(LOG_TAG, "onFinish()");
1110                         }
1111 
1112                         ((PrintDocumentAdapter) message.obj).onFinish();
1113 
1114                         // Done printing, so destroy this instance as it
1115                         // should not be used anymore.
1116                         synchronized (mLock) {
1117                             destroyLocked();
1118                         }
1119                     } break;
1120 
1121                     case MSG_ON_KILL: {
1122                         if (DEBUG) {
1123                             Log.i(LOG_TAG, "onKill()");
1124                         }
1125 
1126                         String reason = (String) message.obj;
1127                         throw new RuntimeException(reason);
1128                     }
1129 
1130                     default: {
1131                         throw new IllegalArgumentException("Unknown message: "
1132                                 + message.what);
1133                     }
1134                 }
1135             }
1136         }
1137 
1138         private interface DestroyableCallback {
destroy()1139             public void destroy();
1140         }
1141 
1142         private final class MyLayoutResultCallback extends LayoutResultCallback
1143                 implements DestroyableCallback {
1144             private ILayoutResultCallback mCallback;
1145             private final int mSequence;
1146 
MyLayoutResultCallback(ILayoutResultCallback callback, int sequence)1147             public MyLayoutResultCallback(ILayoutResultCallback callback,
1148                     int sequence) {
1149                 mCallback = callback;
1150                 mSequence = sequence;
1151             }
1152 
1153             @Override
onLayoutFinished(PrintDocumentInfo info, boolean changed)1154             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
1155                 final ILayoutResultCallback callback;
1156                 synchronized (mLock) {
1157                     callback = mCallback;
1158                 }
1159 
1160                 // If the callback is null we are destroyed.
1161                 if (callback == null) {
1162                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1163                             + "finish the printing activity before print completion "
1164                             + "or did you invoke a callback after finish?");
1165                     return;
1166                 }
1167 
1168                 try {
1169                     if (info == null) {
1170                         throw new NullPointerException("document info cannot be null");
1171                     }
1172 
1173                     try {
1174                         callback.onLayoutFinished(info, changed, mSequence);
1175                     } catch (RemoteException re) {
1176                         Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
1177                     }
1178                 } finally {
1179                     destroy();
1180                 }
1181             }
1182 
1183             @Override
onLayoutFailed(CharSequence error)1184             public void onLayoutFailed(CharSequence error) {
1185                 final ILayoutResultCallback callback;
1186                 synchronized (mLock) {
1187                     callback = mCallback;
1188                 }
1189 
1190                 // If the callback is null we are destroyed.
1191                 if (callback == null) {
1192                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1193                             + "finish the printing activity before print completion "
1194                             + "or did you invoke a callback after finish?");
1195                     return;
1196                 }
1197 
1198                 try {
1199                     callback.onLayoutFailed(error, mSequence);
1200                 } catch (RemoteException re) {
1201                     Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
1202                 } finally {
1203                     destroy();
1204                 }
1205             }
1206 
1207             @Override
onLayoutCancelled()1208             public void onLayoutCancelled() {
1209                 final ILayoutResultCallback callback;
1210                 synchronized (mLock) {
1211                     callback = mCallback;
1212                 }
1213 
1214                 // If the callback is null we are destroyed.
1215                 if (callback == null) {
1216                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1217                             + "finish the printing activity before print completion "
1218                             + "or did you invoke a callback after finish?");
1219                     return;
1220                 }
1221 
1222                 try {
1223                     callback.onLayoutCanceled(mSequence);
1224                 } catch (RemoteException re) {
1225                     Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
1226                 } finally {
1227                     destroy();
1228                 }
1229             }
1230 
1231             @Override
destroy()1232             public void destroy() {
1233                 synchronized (mLock) {
1234                     mCallback = null;
1235                     mPendingCallback = null;
1236                 }
1237             }
1238         }
1239 
1240         private final class MyWriteResultCallback extends WriteResultCallback
1241                 implements DestroyableCallback {
1242             private ParcelFileDescriptor mFd;
1243             private IWriteResultCallback mCallback;
1244             private final int mSequence;
1245 
MyWriteResultCallback(IWriteResultCallback callback, ParcelFileDescriptor fd, int sequence)1246             public MyWriteResultCallback(IWriteResultCallback callback,
1247                     ParcelFileDescriptor fd, int sequence) {
1248                 mFd = fd;
1249                 mSequence = sequence;
1250                 mCallback = callback;
1251             }
1252 
1253             @Override
onWriteFinished(PageRange[] pages)1254             public void onWriteFinished(PageRange[] pages) {
1255                 final IWriteResultCallback callback;
1256                 synchronized (mLock) {
1257                     callback = mCallback;
1258                 }
1259 
1260                 // If the callback is null we are destroyed.
1261                 if (callback == null) {
1262                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1263                             + "finish the printing activity before print completion "
1264                             + "or did you invoke a callback after finish?");
1265                     return;
1266                 }
1267 
1268                 try {
1269                     if (pages == null) {
1270                         throw new IllegalArgumentException("pages cannot be null");
1271                     }
1272                     if (pages.length == 0) {
1273                         throw new IllegalArgumentException("pages cannot be empty");
1274                     }
1275 
1276                     try {
1277                         callback.onWriteFinished(pages, mSequence);
1278                     } catch (RemoteException re) {
1279                         Log.e(LOG_TAG, "Error calling onWriteFinished", re);
1280                     }
1281                 } finally {
1282                     destroy();
1283                 }
1284             }
1285 
1286             @Override
onWriteFailed(CharSequence error)1287             public void onWriteFailed(CharSequence error) {
1288                 final IWriteResultCallback callback;
1289                 synchronized (mLock) {
1290                     callback = mCallback;
1291                 }
1292 
1293                 // If the callback is null we are destroyed.
1294                 if (callback == null) {
1295                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1296                             + "finish the printing activity before print completion "
1297                             + "or did you invoke a callback after finish?");
1298                     return;
1299                 }
1300 
1301                 try {
1302                     callback.onWriteFailed(error, mSequence);
1303                 } catch (RemoteException re) {
1304                     Log.e(LOG_TAG, "Error calling onWriteFailed", re);
1305                 } finally {
1306                     destroy();
1307                 }
1308             }
1309 
1310             @Override
onWriteCancelled()1311             public void onWriteCancelled() {
1312                 final IWriteResultCallback callback;
1313                 synchronized (mLock) {
1314                     callback = mCallback;
1315                 }
1316 
1317                 // If the callback is null we are destroyed.
1318                 if (callback == null) {
1319                     Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
1320                             + "finish the printing activity before print completion "
1321                             + "or did you invoke a callback after finish?");
1322                     return;
1323                 }
1324 
1325                 try {
1326                     callback.onWriteCanceled(mSequence);
1327                 } catch (RemoteException re) {
1328                     Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
1329                 } finally {
1330                     destroy();
1331                 }
1332             }
1333 
1334             @Override
destroy()1335             public void destroy() {
1336                 synchronized (mLock) {
1337                     IoUtils.closeQuietly(mFd);
1338                     mCallback = null;
1339                     mFd = null;
1340                     mPendingCallback = null;
1341                 }
1342             }
1343         }
1344     }
1345 
1346     /**
1347      * @hide
1348      */
1349     public static final class PrintJobStateChangeListenerWrapper extends
1350             IPrintJobStateChangeListener.Stub {
1351         private final WeakReference<PrintJobStateChangeListener> mWeakListener;
1352         private final WeakReference<Handler> mWeakHandler;
1353 
PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener, Handler handler)1354         public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
1355                 Handler handler) {
1356             mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
1357             mWeakHandler = new WeakReference<Handler>(handler);
1358         }
1359 
1360         @Override
onPrintJobStateChanged(PrintJobId printJobId)1361         public void onPrintJobStateChanged(PrintJobId printJobId) {
1362             Handler handler = mWeakHandler.get();
1363             PrintJobStateChangeListener listener = mWeakListener.get();
1364             if (handler != null && listener != null) {
1365                 SomeArgs args = SomeArgs.obtain();
1366                 args.arg1 = this;
1367                 args.arg2 = printJobId;
1368                 handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
1369                         args).sendToTarget();
1370             }
1371         }
1372 
destroy()1373         public void destroy() {
1374             mWeakListener.clear();
1375         }
1376 
getListener()1377         public PrintJobStateChangeListener getListener() {
1378             return mWeakListener.get();
1379         }
1380     }
1381 
1382     /**
1383      * @hide
1384      */
1385     public static final class PrintServicesChangeListenerWrapper extends
1386             IPrintServicesChangeListener.Stub {
1387         private final WeakReference<PrintServicesChangeListener> mWeakListener;
1388         private final WeakReference<Handler> mWeakHandler;
1389 
PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener, Handler handler)1390         public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener,
1391                 Handler handler) {
1392             mWeakListener = new WeakReference<>(listener);
1393             mWeakHandler = new WeakReference<>(handler);
1394         }
1395 
1396         @Override
onPrintServicesChanged()1397         public void onPrintServicesChanged() {
1398             Handler handler = mWeakHandler.get();
1399             PrintServicesChangeListener listener = mWeakListener.get();
1400             if (handler != null && listener != null) {
1401                 handler.post(listener::onPrintServicesChanged);
1402             }
1403         }
1404 
destroy()1405         public void destroy() {
1406             mWeakListener.clear();
1407         }
1408     }
1409 
1410     /**
1411      * @hide
1412      */
1413     public static final class PrintServiceRecommendationsChangeListenerWrapper extends
1414             IRecommendationsChangeListener.Stub {
1415         private final WeakReference<PrintServiceRecommendationsChangeListener> mWeakListener;
1416         private final WeakReference<Handler> mWeakHandler;
1417 
PrintServiceRecommendationsChangeListenerWrapper( PrintServiceRecommendationsChangeListener listener, Handler handler)1418         public PrintServiceRecommendationsChangeListenerWrapper(
1419                 PrintServiceRecommendationsChangeListener listener, Handler handler) {
1420             mWeakListener = new WeakReference<>(listener);
1421             mWeakHandler = new WeakReference<>(handler);
1422         }
1423 
1424         @Override
onRecommendationsChanged()1425         public void onRecommendationsChanged() {
1426             Handler handler = mWeakHandler.get();
1427             PrintServiceRecommendationsChangeListener listener = mWeakListener.get();
1428             if (handler != null && listener != null) {
1429                 handler.post(listener::onPrintServiceRecommendationsChanged);
1430             }
1431         }
1432 
destroy()1433         public void destroy() {
1434             mWeakListener.clear();
1435         }
1436     }
1437 }
1438