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