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