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