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 com.android.server.print;
     18 
     19 import static android.content.pm.PackageManager.GET_META_DATA;
     20 import static android.content.pm.PackageManager.GET_SERVICES;
     21 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
     22 
     23 import android.annotation.NonNull;
     24 import android.annotation.Nullable;
     25 import android.app.PendingIntent;
     26 import android.content.ComponentName;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentSender;
     30 import android.content.pm.ParceledListSlice;
     31 import android.content.pm.ResolveInfo;
     32 import android.graphics.drawable.Icon;
     33 import android.net.Uri;
     34 import android.os.AsyncTask;
     35 import android.os.Binder;
     36 import android.os.Bundle;
     37 import android.os.Handler;
     38 import android.os.IBinder;
     39 import android.os.IBinder.DeathRecipient;
     40 import android.os.IInterface;
     41 import android.os.Looper;
     42 import android.os.Message;
     43 import android.os.RemoteCallbackList;
     44 import android.os.RemoteException;
     45 import android.os.UserHandle;
     46 import android.print.IPrintDocumentAdapter;
     47 import android.print.IPrintJobStateChangeListener;
     48 import android.printservice.recommendation.IRecommendationsChangeListener;
     49 import android.print.IPrintServicesChangeListener;
     50 import android.print.IPrinterDiscoveryObserver;
     51 import android.print.PrintAttributes;
     52 import android.print.PrintJobId;
     53 import android.print.PrintJobInfo;
     54 import android.print.PrintManager;
     55 import android.printservice.recommendation.RecommendationInfo;
     56 import android.print.PrinterId;
     57 import android.print.PrinterInfo;
     58 import android.printservice.PrintServiceInfo;
     59 import android.provider.DocumentsContract;
     60 import android.provider.Settings;
     61 import android.text.TextUtils;
     62 import android.text.TextUtils.SimpleStringSplitter;
     63 import android.util.ArrayMap;
     64 import android.util.ArraySet;
     65 import android.util.Log;
     66 import android.util.Slog;
     67 import android.util.SparseArray;
     68 
     69 import com.android.internal.R;
     70 import com.android.internal.os.BackgroundThread;
     71 import com.android.internal.os.SomeArgs;
     72 import com.android.server.print.RemotePrintService.PrintServiceCallbacks;
     73 import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks;
     74 import com.android.server.print.RemotePrintServiceRecommendationService.RemotePrintServiceRecommendationServiceCallbacks;
     75 
     76 import java.io.FileDescriptor;
     77 import java.io.PrintWriter;
     78 import java.util.ArrayList;
     79 import java.util.Collections;
     80 import java.util.HashSet;
     81 import java.util.Iterator;
     82 import java.util.List;
     83 import java.util.Map;
     84 import java.util.Set;
     85 
     86 /**
     87  * Represents the print state for a user.
     88  */
     89 final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
     90         RemotePrintServiceRecommendationServiceCallbacks {
     91 
     92     private static final String LOG_TAG = "UserState";
     93 
     94     private static final boolean DEBUG = false;
     95 
     96     private static final char COMPONENT_NAME_SEPARATOR = ':';
     97 
     98     private final SimpleStringSplitter mStringColonSplitter =
     99             new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
    100 
    101     private final Intent mQueryIntent =
    102             new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
    103 
    104     private final ArrayMap<ComponentName, RemotePrintService> mActiveServices =
    105             new ArrayMap<ComponentName, RemotePrintService>();
    106 
    107     private final List<PrintServiceInfo> mInstalledServices =
    108             new ArrayList<PrintServiceInfo>();
    109 
    110     private final Set<ComponentName> mDisabledServices =
    111             new ArraySet<ComponentName>();
    112 
    113     private final PrintJobForAppCache mPrintJobForAppCache =
    114             new PrintJobForAppCache();
    115 
    116     private final Object mLock;
    117 
    118     private final Context mContext;
    119 
    120     private final int mUserId;
    121 
    122     private final RemotePrintSpooler mSpooler;
    123 
    124     private final Handler mHandler;
    125 
    126     private PrinterDiscoverySessionMediator mPrinterDiscoverySession;
    127 
    128     private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords;
    129 
    130     private List<ListenerRecord<IPrintServicesChangeListener>> mPrintServicesChangeListenerRecords;
    131 
    132     private List<ListenerRecord<IRecommendationsChangeListener>>
    133             mPrintServiceRecommendationsChangeListenerRecords;
    134 
    135     private boolean mDestroyed;
    136 
    137     /** Currently known list of print service recommendations */
    138     private List<RecommendationInfo> mPrintServiceRecommendations;
    139 
    140     /**
    141      * Connection to the service updating the {@link #mPrintServiceRecommendations print service
    142      * recommendations}.
    143      */
    144     private RemotePrintServiceRecommendationService mPrintServiceRecommendationsService;
    145 
    146     public UserState(Context context, int userId, Object lock, boolean lowPriority) {
    147         mContext = context;
    148         mUserId = userId;
    149         mLock = lock;
    150         mSpooler = new RemotePrintSpooler(context, userId, lowPriority, this);
    151         mHandler = new UserStateHandler(context.getMainLooper());
    152 
    153         synchronized (mLock) {
    154             readInstalledPrintServicesLocked();
    155             upgradePersistentStateIfNeeded();
    156             readDisabledPrintServicesLocked();
    157 
    158             // Some print services might have gotten installed before the User State came up
    159             prunePrintServices();
    160 
    161             onConfigurationChangedLocked();
    162         }
    163     }
    164 
    165     public void increasePriority() {
    166         mSpooler.increasePriority();
    167     }
    168 
    169     @Override
    170     public void onPrintJobQueued(PrintJobInfo printJob) {
    171         final RemotePrintService service;
    172         synchronized (mLock) {
    173             throwIfDestroyedLocked();
    174             ComponentName printServiceName = printJob.getPrinterId().getServiceName();
    175             service = mActiveServices.get(printServiceName);
    176         }
    177         if (service != null) {
    178             service.onPrintJobQueued(printJob);
    179         } else {
    180             // The service for the job is no longer enabled, so just
    181             // fail the job with the appropriate message.
    182             mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
    183                     mContext.getString(R.string.reason_service_unavailable));
    184         }
    185     }
    186 
    187     @Override
    188     public void onAllPrintJobsForServiceHandled(ComponentName printService) {
    189         final RemotePrintService service;
    190         synchronized (mLock) {
    191             throwIfDestroyedLocked();
    192             service = mActiveServices.get(printService);
    193         }
    194         if (service != null) {
    195             service.onAllPrintJobsHandled();
    196         }
    197     }
    198 
    199     public void removeObsoletePrintJobs() {
    200         mSpooler.removeObsoletePrintJobs();
    201     }
    202 
    203     @SuppressWarnings("deprecation")
    204     public Bundle print(@NonNull String printJobName, @NonNull IPrintDocumentAdapter adapter,
    205             @Nullable PrintAttributes attributes, @NonNull String packageName, int appId) {
    206         // Create print job place holder.
    207         final PrintJobInfo printJob = new PrintJobInfo();
    208         printJob.setId(new PrintJobId());
    209         printJob.setAppId(appId);
    210         printJob.setLabel(printJobName);
    211         printJob.setAttributes(attributes);
    212         printJob.setState(PrintJobInfo.STATE_CREATED);
    213         printJob.setCopies(1);
    214         printJob.setCreationTime(System.currentTimeMillis());
    215 
    216         // Track this job so we can forget it when the creator dies.
    217         if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId,
    218                 printJob)) {
    219             // Not adding a print job means the client is dead - done.
    220             return null;
    221         }
    222 
    223         // Spin the spooler to add the job and show the config UI.
    224         new AsyncTask<Void, Void, Void>() {
    225             @Override
    226             protected Void doInBackground(Void... params) {
    227                 mSpooler.createPrintJob(printJob);
    228                 return null;
    229             }
    230         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    231 
    232         final long identity = Binder.clearCallingIdentity();
    233         try {
    234             Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG);
    235             intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
    236             intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
    237             intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
    238             intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
    239 
    240             IntentSender intentSender = PendingIntent.getActivityAsUser(
    241                     mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
    242                     | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
    243                     null, new UserHandle(mUserId)) .getIntentSender();
    244 
    245             Bundle result = new Bundle();
    246             result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
    247             result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender);
    248 
    249             return result;
    250         } finally {
    251             Binder.restoreCallingIdentity(identity);
    252         }
    253     }
    254 
    255     public List<PrintJobInfo> getPrintJobInfos(int appId) {
    256         List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId);
    257         // Note that the print spooler is not storing print jobs that
    258         // are in a terminal state as it is non-trivial to properly update
    259         // the spooler state for when to forget print jobs in terminal state.
    260         // Therefore, we fuse the cached print jobs for running apps (some
    261         // jobs are in a terminal state) with the ones that the print
    262         // spooler knows about (some jobs are being processed).
    263         ArrayMap<PrintJobId, PrintJobInfo> result =
    264                 new ArrayMap<PrintJobId, PrintJobInfo>();
    265 
    266         // Add the cached print jobs for running apps.
    267         final int cachedPrintJobCount = cachedPrintJobs.size();
    268         for (int i = 0; i < cachedPrintJobCount; i++) {
    269             PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i);
    270             result.put(cachedPrintJob.getId(), cachedPrintJob);
    271             // Strip out the tag and the advanced print options.
    272             // They are visible only to print services.
    273             cachedPrintJob.setTag(null);
    274             cachedPrintJob.setAdvancedOptions(null);
    275         }
    276 
    277         // Add everything else the spooler knows about.
    278         List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null,
    279                 PrintJobInfo.STATE_ANY, appId);
    280         if (printJobs != null) {
    281             final int printJobCount = printJobs.size();
    282             for (int i = 0; i < printJobCount; i++) {
    283                 PrintJobInfo printJob = printJobs.get(i);
    284                 result.put(printJob.getId(), printJob);
    285                 // Strip out the tag and the advanced print options.
    286                 // They are visible only to print services.
    287                 printJob.setTag(null);
    288                 printJob.setAdvancedOptions(null);
    289             }
    290         }
    291 
    292         return new ArrayList<PrintJobInfo>(result.values());
    293     }
    294 
    295     public PrintJobInfo getPrintJobInfo(@NonNull PrintJobId printJobId, int appId) {
    296         PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId);
    297         if (printJob == null) {
    298             printJob = mSpooler.getPrintJobInfo(printJobId, appId);
    299         }
    300         if (printJob != null) {
    301             // Strip out the tag and the advanced print options.
    302             // They are visible only to print services.
    303             printJob.setTag(null);
    304             printJob.setAdvancedOptions(null);
    305         }
    306         return printJob;
    307     }
    308 
    309     /**
    310      * Get the custom icon for a printer. If the icon is not cached, the icon is
    311      * requested asynchronously. Once it is available the printer is updated.
    312      *
    313      * @param printerId the id of the printer the icon should be loaded for
    314      * @return the custom icon to be used for the printer or null if the icon is
    315      *         not yet available
    316      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
    317      */
    318     public @Nullable Icon getCustomPrinterIcon(@NonNull PrinterId printerId) {
    319         Icon icon = mSpooler.getCustomPrinterIcon(printerId);
    320 
    321         if (icon == null) {
    322             RemotePrintService service = mActiveServices.get(printerId.getServiceName());
    323             if (service != null) {
    324                 service.requestCustomPrinterIcon(printerId);
    325             }
    326         }
    327 
    328         return icon;
    329     }
    330 
    331     public void cancelPrintJob(@NonNull PrintJobId printJobId, int appId) {
    332         PrintJobInfo printJobInfo = mSpooler.getPrintJobInfo(printJobId, appId);
    333         if (printJobInfo == null) {
    334             return;
    335         }
    336 
    337         // Take a note that we are trying to cancel the job.
    338         mSpooler.setPrintJobCancelling(printJobId, true);
    339 
    340         if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
    341             PrinterId printerId = printJobInfo.getPrinterId();
    342 
    343             if (printerId != null) {
    344                 ComponentName printServiceName = printerId.getServiceName();
    345                 RemotePrintService printService = null;
    346                 synchronized (mLock) {
    347                     printService = mActiveServices.get(printServiceName);
    348                 }
    349                 if (printService == null) {
    350                     return;
    351                 }
    352                 printService.onRequestCancelPrintJob(printJobInfo);
    353             }
    354         } else {
    355             // If the print job is failed we do not need cooperation
    356             // from the print service.
    357             mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED, null);
    358         }
    359     }
    360 
    361     public void restartPrintJob(@NonNull PrintJobId printJobId, int appId) {
    362         PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId);
    363         if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
    364             return;
    365         }
    366         mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null);
    367     }
    368 
    369     public @Nullable List<PrintServiceInfo> getPrintServices(int selectionFlags) {
    370         synchronized (mLock) {
    371             List<PrintServiceInfo> selectedServices = null;
    372             final int installedServiceCount = mInstalledServices.size();
    373             for (int i = 0; i < installedServiceCount; i++) {
    374                 PrintServiceInfo installedService = mInstalledServices.get(i);
    375 
    376                 ComponentName componentName = new ComponentName(
    377                         installedService.getResolveInfo().serviceInfo.packageName,
    378                         installedService.getResolveInfo().serviceInfo.name);
    379 
    380                 // Update isEnabled under the same lock the final returned list is created
    381                 installedService.setIsEnabled(mActiveServices.containsKey(componentName));
    382 
    383                 if (installedService.isEnabled()) {
    384                     if ((selectionFlags & PrintManager.ENABLED_SERVICES) == 0) {
    385                         continue;
    386                     }
    387                 } else {
    388                     if ((selectionFlags & PrintManager.DISABLED_SERVICES) == 0) {
    389                         continue;
    390                     }
    391                 }
    392 
    393                 if (selectedServices == null) {
    394                     selectedServices = new ArrayList<>();
    395                 }
    396                 selectedServices.add(installedService);
    397             }
    398             return selectedServices;
    399         }
    400     }
    401 
    402     public void setPrintServiceEnabled(@NonNull ComponentName serviceName, boolean isEnabled) {
    403         synchronized (mLock) {
    404             boolean isChanged = false;
    405             if (isEnabled) {
    406                 isChanged = mDisabledServices.remove(serviceName);
    407             } else {
    408                 // Make sure to only disable services that are currently installed
    409                 final int numServices = mInstalledServices.size();
    410                 for (int i = 0; i < numServices; i++) {
    411                     PrintServiceInfo service = mInstalledServices.get(i);
    412 
    413                     if (service.getComponentName().equals(serviceName)) {
    414                         mDisabledServices.add(serviceName);
    415                         isChanged = true;
    416                         break;
    417                     }
    418                 }
    419             }
    420 
    421             if (isChanged) {
    422                 writeDisabledPrintServicesLocked(mDisabledServices);
    423 
    424                 onConfigurationChangedLocked();
    425             }
    426         }
    427     }
    428 
    429     /**
    430      * @return The currently known print service recommendations
    431      */
    432     public @Nullable List<RecommendationInfo> getPrintServiceRecommendations() {
    433         return mPrintServiceRecommendations;
    434     }
    435 
    436     public void createPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) {
    437         synchronized (mLock) {
    438             throwIfDestroyedLocked();
    439 
    440             if (mPrinterDiscoverySession == null) {
    441                 mSpooler.clearCustomPrinterIconCache();
    442 
    443                 // If we do not have a session, tell all service to create one.
    444                 mPrinterDiscoverySession = new PrinterDiscoverySessionMediator(mContext) {
    445                     @Override
    446                     public void onDestroyed() {
    447                         mPrinterDiscoverySession = null;
    448                     }
    449                 };
    450                 // Add the observer to the brand new session.
    451                 mPrinterDiscoverySession.addObserverLocked(observer);
    452             } else {
    453                 // If services have created session, just add the observer.
    454                 mPrinterDiscoverySession.addObserverLocked(observer);
    455             }
    456         }
    457     }
    458 
    459     public void destroyPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) {
    460         synchronized (mLock) {
    461             // Already destroyed - nothing to do.
    462             if (mPrinterDiscoverySession == null) {
    463                 return;
    464             }
    465             // Remove this observer.
    466             mPrinterDiscoverySession.removeObserverLocked(observer);
    467         }
    468     }
    469 
    470     public void startPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer,
    471             @Nullable List<PrinterId> printerIds) {
    472         synchronized (mLock) {
    473             throwIfDestroyedLocked();
    474 
    475             // No session - nothing to do.
    476             if (mPrinterDiscoverySession == null) {
    477                 return;
    478             }
    479             // Kick of discovery.
    480             mPrinterDiscoverySession.startPrinterDiscoveryLocked(observer,
    481                     printerIds);
    482         }
    483     }
    484 
    485     public void stopPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer) {
    486         synchronized (mLock) {
    487             throwIfDestroyedLocked();
    488 
    489             // No session - nothing to do.
    490             if (mPrinterDiscoverySession == null) {
    491                 return;
    492             }
    493             // Kick of discovery.
    494             mPrinterDiscoverySession.stopPrinterDiscoveryLocked(observer);
    495         }
    496     }
    497 
    498     public void validatePrinters(@NonNull List<PrinterId> printerIds) {
    499         synchronized (mLock) {
    500             throwIfDestroyedLocked();
    501             // No services - nothing to do.
    502             if (mActiveServices.isEmpty()) {
    503                 return;
    504             }
    505             // No session - nothing to do.
    506             if (mPrinterDiscoverySession == null) {
    507                 return;
    508             }
    509             // Request an updated.
    510             mPrinterDiscoverySession.validatePrintersLocked(printerIds);
    511         }
    512     }
    513 
    514     public void startPrinterStateTracking(@NonNull PrinterId printerId) {
    515         synchronized (mLock) {
    516             throwIfDestroyedLocked();
    517             // No services - nothing to do.
    518             if (mActiveServices.isEmpty()) {
    519                 return;
    520             }
    521             // No session - nothing to do.
    522             if (mPrinterDiscoverySession == null) {
    523                 return;
    524             }
    525             // Request start tracking the printer.
    526             mPrinterDiscoverySession.startPrinterStateTrackingLocked(printerId);
    527         }
    528     }
    529 
    530     public void stopPrinterStateTracking(PrinterId printerId) {
    531         synchronized (mLock) {
    532             throwIfDestroyedLocked();
    533             // No services - nothing to do.
    534             if (mActiveServices.isEmpty()) {
    535                 return;
    536             }
    537             // No session - nothing to do.
    538             if (mPrinterDiscoverySession == null) {
    539                 return;
    540             }
    541             // Request stop tracking the printer.
    542             mPrinterDiscoverySession.stopPrinterStateTrackingLocked(printerId);
    543         }
    544     }
    545 
    546     public void addPrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener,
    547             int appId) throws RemoteException {
    548         synchronized (mLock) {
    549             throwIfDestroyedLocked();
    550             if (mPrintJobStateChangeListenerRecords == null) {
    551                 mPrintJobStateChangeListenerRecords =
    552                         new ArrayList<PrintJobStateChangeListenerRecord>();
    553             }
    554             mPrintJobStateChangeListenerRecords.add(
    555                     new PrintJobStateChangeListenerRecord(listener, appId) {
    556                 @Override
    557                 public void onBinderDied() {
    558                     synchronized (mLock) {
    559                         if (mPrintJobStateChangeListenerRecords != null) {
    560                             mPrintJobStateChangeListenerRecords.remove(this);
    561                         }
    562                     }
    563                 }
    564             });
    565         }
    566     }
    567 
    568     public void removePrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener) {
    569         synchronized (mLock) {
    570             throwIfDestroyedLocked();
    571             if (mPrintJobStateChangeListenerRecords == null) {
    572                 return;
    573             }
    574             final int recordCount = mPrintJobStateChangeListenerRecords.size();
    575             for (int i = 0; i < recordCount; i++) {
    576                 PrintJobStateChangeListenerRecord record =
    577                         mPrintJobStateChangeListenerRecords.get(i);
    578                 if (record.listener.asBinder().equals(listener.asBinder())) {
    579                     mPrintJobStateChangeListenerRecords.remove(i);
    580                     break;
    581                 }
    582             }
    583             if (mPrintJobStateChangeListenerRecords.isEmpty()) {
    584                 mPrintJobStateChangeListenerRecords = null;
    585             }
    586         }
    587     }
    588 
    589     public void addPrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener)
    590             throws RemoteException {
    591         synchronized (mLock) {
    592             throwIfDestroyedLocked();
    593             if (mPrintServicesChangeListenerRecords == null) {
    594                 mPrintServicesChangeListenerRecords = new ArrayList<>();
    595             }
    596             mPrintServicesChangeListenerRecords.add(
    597                     new ListenerRecord<IPrintServicesChangeListener>(listener) {
    598                         @Override
    599                         public void onBinderDied() {
    600                             synchronized (mLock) {
    601                                 if (mPrintServicesChangeListenerRecords != null) {
    602                                     mPrintServicesChangeListenerRecords.remove(this);
    603                                 }
    604                             }
    605                         }
    606                     });
    607         }
    608     }
    609 
    610     public void removePrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) {
    611         synchronized (mLock) {
    612             throwIfDestroyedLocked();
    613             if (mPrintServicesChangeListenerRecords == null) {
    614                 return;
    615             }
    616             final int recordCount = mPrintServicesChangeListenerRecords.size();
    617             for (int i = 0; i < recordCount; i++) {
    618                 ListenerRecord<IPrintServicesChangeListener> record =
    619                         mPrintServicesChangeListenerRecords.get(i);
    620                 if (record.listener.asBinder().equals(listener.asBinder())) {
    621                     mPrintServicesChangeListenerRecords.remove(i);
    622                     break;
    623                 }
    624             }
    625             if (mPrintServicesChangeListenerRecords.isEmpty()) {
    626                 mPrintServicesChangeListenerRecords = null;
    627             }
    628         }
    629     }
    630 
    631     public void addPrintServiceRecommendationsChangeListener(
    632             @NonNull IRecommendationsChangeListener listener) throws RemoteException {
    633         synchronized (mLock) {
    634             throwIfDestroyedLocked();
    635             if (mPrintServiceRecommendationsChangeListenerRecords == null) {
    636                 mPrintServiceRecommendationsChangeListenerRecords = new ArrayList<>();
    637 
    638                 mPrintServiceRecommendationsService =
    639                         new RemotePrintServiceRecommendationService(mContext,
    640                                 UserHandle.getUserHandleForUid(mUserId), this);
    641             }
    642             mPrintServiceRecommendationsChangeListenerRecords.add(
    643                     new ListenerRecord<IRecommendationsChangeListener>(listener) {
    644                         @Override
    645                         public void onBinderDied() {
    646                             synchronized (mLock) {
    647                                 if (mPrintServiceRecommendationsChangeListenerRecords != null) {
    648                                     mPrintServiceRecommendationsChangeListenerRecords.remove(this);
    649                                 }
    650                             }
    651                         }
    652                     });
    653         }
    654     }
    655 
    656     public void removePrintServiceRecommendationsChangeListener(
    657             @NonNull IRecommendationsChangeListener listener) {
    658         synchronized (mLock) {
    659             throwIfDestroyedLocked();
    660             if (mPrintServiceRecommendationsChangeListenerRecords == null) {
    661                 return;
    662             }
    663             final int recordCount = mPrintServiceRecommendationsChangeListenerRecords.size();
    664             for (int i = 0; i < recordCount; i++) {
    665                 ListenerRecord<IRecommendationsChangeListener> record =
    666                         mPrintServiceRecommendationsChangeListenerRecords.get(i);
    667                 if (record.listener.asBinder().equals(listener.asBinder())) {
    668                     mPrintServiceRecommendationsChangeListenerRecords.remove(i);
    669                     break;
    670                 }
    671             }
    672             if (mPrintServiceRecommendationsChangeListenerRecords.isEmpty()) {
    673                 mPrintServiceRecommendationsChangeListenerRecords = null;
    674 
    675                 mPrintServiceRecommendations = null;
    676 
    677                 mPrintServiceRecommendationsService.close();
    678                 mPrintServiceRecommendationsService = null;
    679             }
    680         }
    681     }
    682 
    683     @Override
    684     public void onPrintJobStateChanged(PrintJobInfo printJob) {
    685         mPrintJobForAppCache.onPrintJobStateChanged(printJob);
    686         mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED,
    687                 printJob.getAppId(), 0, printJob.getId()).sendToTarget();
    688     }
    689 
    690     public void onPrintServicesChanged() {
    691         mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_SERVICES_CHANGED).sendToTarget();
    692     }
    693 
    694     @Override
    695     public void onPrintServiceRecommendationsUpdated(List<RecommendationInfo> recommendations) {
    696         mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_SERVICES_RECOMMENDATIONS_UPDATED,
    697                 0, 0, recommendations).sendToTarget();
    698     }
    699 
    700     @Override
    701     public void onPrintersAdded(List<PrinterInfo> printers) {
    702         synchronized (mLock) {
    703             throwIfDestroyedLocked();
    704             // No services - nothing to do.
    705             if (mActiveServices.isEmpty()) {
    706                 return;
    707             }
    708             // No session - nothing to do.
    709             if (mPrinterDiscoverySession == null) {
    710                 return;
    711             }
    712             mPrinterDiscoverySession.onPrintersAddedLocked(printers);
    713         }
    714     }
    715 
    716     @Override
    717     public void onPrintersRemoved(List<PrinterId> printerIds) {
    718         synchronized (mLock) {
    719             throwIfDestroyedLocked();
    720             // No services - nothing to do.
    721             if (mActiveServices.isEmpty()) {
    722                 return;
    723             }
    724             // No session - nothing to do.
    725             if (mPrinterDiscoverySession == null) {
    726                 return;
    727             }
    728             mPrinterDiscoverySession.onPrintersRemovedLocked(printerIds);
    729         }
    730     }
    731 
    732     @Override
    733     public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
    734         synchronized (mLock) {
    735             throwIfDestroyedLocked();
    736 
    737             // No session - nothing to do.
    738             if (mPrinterDiscoverySession == null) {
    739                 return;
    740             }
    741             mSpooler.onCustomPrinterIconLoaded(printerId, icon);
    742             mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId);
    743         }
    744     }
    745 
    746     @Override
    747     public void onServiceDied(RemotePrintService service) {
    748         synchronized (mLock) {
    749             throwIfDestroyedLocked();
    750             // No services - nothing to do.
    751             if (mActiveServices.isEmpty()) {
    752                 return;
    753             }
    754             // Fail all print jobs.
    755             failActivePrintJobsForService(service.getComponentName());
    756             service.onAllPrintJobsHandled();
    757             // No session - nothing to do.
    758             if (mPrinterDiscoverySession == null) {
    759                 return;
    760             }
    761             mPrinterDiscoverySession.onServiceDiedLocked(service);
    762         }
    763     }
    764 
    765     public void updateIfNeededLocked() {
    766         throwIfDestroyedLocked();
    767         readConfigurationLocked();
    768         onConfigurationChangedLocked();
    769     }
    770 
    771     public void destroyLocked() {
    772         throwIfDestroyedLocked();
    773         mSpooler.destroy();
    774         for (RemotePrintService service : mActiveServices.values()) {
    775             service.destroy();
    776         }
    777         mActiveServices.clear();
    778         mInstalledServices.clear();
    779         mDisabledServices.clear();
    780         if (mPrinterDiscoverySession != null) {
    781             mPrinterDiscoverySession.destroyLocked();
    782             mPrinterDiscoverySession = null;
    783         }
    784         mDestroyed = true;
    785     }
    786 
    787     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String prefix) {
    788         pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":");
    789         pw.println();
    790 
    791         String tab = "  ";
    792 
    793         pw.append(prefix).append(tab).append("installed services:").println();
    794         final int installedServiceCount = mInstalledServices.size();
    795         for (int i = 0; i < installedServiceCount; i++) {
    796             PrintServiceInfo installedService = mInstalledServices.get(i);
    797             String installedServicePrefix = prefix + tab + tab;
    798             pw.append(installedServicePrefix).append("service:").println();
    799             ResolveInfo resolveInfo = installedService.getResolveInfo();
    800             ComponentName componentName = new ComponentName(
    801                     resolveInfo.serviceInfo.packageName,
    802                     resolveInfo.serviceInfo.name);
    803             pw.append(installedServicePrefix).append(tab).append("componentName=")
    804                     .append(componentName.flattenToString()).println();
    805             pw.append(installedServicePrefix).append(tab).append("settingsActivity=")
    806                     .append(installedService.getSettingsActivityName()).println();
    807             pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=")
    808                     .append(installedService.getAddPrintersActivityName()).println();
    809             pw.append(installedServicePrefix).append(tab).append("avancedOptionsActivity=")
    810                    .append(installedService.getAdvancedOptionsActivityName()).println();
    811         }
    812 
    813         pw.append(prefix).append(tab).append("disabled services:").println();
    814         for (ComponentName disabledService : mDisabledServices) {
    815             String disabledServicePrefix = prefix + tab + tab;
    816             pw.append(disabledServicePrefix).append("service:").println();
    817             pw.append(disabledServicePrefix).append(tab).append("componentName=")
    818                     .append(disabledService.flattenToString());
    819             pw.println();
    820         }
    821 
    822         pw.append(prefix).append(tab).append("active services:").println();
    823         final int activeServiceCount = mActiveServices.size();
    824         for (int i = 0; i < activeServiceCount; i++) {
    825             RemotePrintService activeService = mActiveServices.valueAt(i);
    826             activeService.dump(pw, prefix + tab + tab);
    827             pw.println();
    828         }
    829 
    830         pw.append(prefix).append(tab).append("cached print jobs:").println();
    831         mPrintJobForAppCache.dump(pw, prefix + tab + tab);
    832 
    833         pw.append(prefix).append(tab).append("discovery mediator:").println();
    834         if (mPrinterDiscoverySession != null) {
    835             mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
    836         }
    837 
    838         pw.append(prefix).append(tab).append("print spooler:").println();
    839         mSpooler.dump(fd, pw, prefix + tab + tab);
    840         pw.println();
    841     }
    842 
    843     private void readConfigurationLocked() {
    844         readInstalledPrintServicesLocked();
    845         readDisabledPrintServicesLocked();
    846     }
    847 
    848     private void readInstalledPrintServicesLocked() {
    849         Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
    850 
    851         List<ResolveInfo> installedServices = mContext.getPackageManager()
    852                 .queryIntentServicesAsUser(mQueryIntent,
    853                         GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING, mUserId);
    854 
    855         final int installedCount = installedServices.size();
    856         for (int i = 0, count = installedCount; i < count; i++) {
    857             ResolveInfo installedService = installedServices.get(i);
    858             if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals(
    859                     installedService.serviceInfo.permission)) {
    860                 ComponentName serviceName = new ComponentName(
    861                         installedService.serviceInfo.packageName,
    862                         installedService.serviceInfo.name);
    863                 Slog.w(LOG_TAG, "Skipping print service "
    864                         + serviceName.flattenToShortString()
    865                         + " since it does not require permission "
    866                         + android.Manifest.permission.BIND_PRINT_SERVICE);
    867                 continue;
    868             }
    869             tempPrintServices.add(PrintServiceInfo.create(installedService, mContext));
    870         }
    871 
    872         mInstalledServices.clear();
    873         mInstalledServices.addAll(tempPrintServices);
    874     }
    875 
    876     /**
    877      * Update persistent state from a previous version of Android.
    878      */
    879     private void upgradePersistentStateIfNeeded() {
    880         String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    881                 Settings.Secure.ENABLED_PRINT_SERVICES, mUserId);
    882 
    883         // Pre N we store the enabled services, in N and later we store the disabled services.
    884         // Hence if enabledSettingValue is still set, we need to upgrade.
    885         if (enabledSettingValue != null) {
    886             Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>();
    887             readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
    888                     enabledServiceNameSet);
    889 
    890             ArraySet<ComponentName> disabledServices = new ArraySet<>();
    891             final int numInstalledServices = mInstalledServices.size();
    892             for (int i = 0; i < numInstalledServices; i++) {
    893                 ComponentName serviceName = mInstalledServices.get(i).getComponentName();
    894                 if (!enabledServiceNameSet.contains(serviceName)) {
    895                     disabledServices.add(serviceName);
    896                 }
    897             }
    898 
    899             writeDisabledPrintServicesLocked(disabledServices);
    900 
    901             // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run
    902             // again.
    903             Settings.Secure.putStringForUser(mContext.getContentResolver(),
    904                     Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId);
    905         }
    906     }
    907 
    908     /**
    909      * Read the set of disabled print services from the secure settings.
    910      *
    911      * @return true if the state changed.
    912      */
    913     private void readDisabledPrintServicesLocked() {
    914         Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>();
    915         readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES,
    916                 tempDisabledServiceNameSet);
    917         if (!tempDisabledServiceNameSet.equals(mDisabledServices)) {
    918             mDisabledServices.clear();
    919             mDisabledServices.addAll(tempDisabledServiceNameSet);
    920         }
    921     }
    922 
    923     private void readPrintServicesFromSettingLocked(String setting,
    924             Set<ComponentName> outServiceNames) {
    925         String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    926                 setting, mUserId);
    927         if (!TextUtils.isEmpty(settingValue)) {
    928             TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
    929             splitter.setString(settingValue);
    930             while (splitter.hasNext()) {
    931                 String string = splitter.next();
    932                 if (TextUtils.isEmpty(string)) {
    933                     continue;
    934                 }
    935                 ComponentName componentName = ComponentName.unflattenFromString(string);
    936                 if (componentName != null) {
    937                     outServiceNames.add(componentName);
    938                 }
    939             }
    940         }
    941     }
    942 
    943     /**
    944      * Persist the disabled print services to the secure settings.
    945      */
    946     private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) {
    947         StringBuilder builder = new StringBuilder();
    948         for (ComponentName componentName : disabledServices) {
    949             if (builder.length() > 0) {
    950                 builder.append(COMPONENT_NAME_SEPARATOR);
    951             }
    952             builder.append(componentName.flattenToShortString());
    953         }
    954         Settings.Secure.putStringForUser(mContext.getContentResolver(),
    955                 Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId);
    956     }
    957 
    958     /**
    959      * Get the {@link ComponentName names} of the installed print services
    960      *
    961      * @return The names of the installed print services
    962      */
    963     private ArrayList<ComponentName> getInstalledComponents() {
    964         ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>();
    965 
    966         final int installedCount = mInstalledServices.size();
    967         for (int i = 0; i < installedCount; i++) {
    968             ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo();
    969             ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName,
    970                     resolveInfo.serviceInfo.name);
    971 
    972             installedComponents.add(serviceName);
    973         }
    974 
    975         return installedComponents;
    976     }
    977 
    978     /**
    979      * Prune persistent state if a print service was uninstalled
    980      */
    981     public void prunePrintServices() {
    982         synchronized (mLock) {
    983             ArrayList<ComponentName> installedComponents = getInstalledComponents();
    984 
    985             // Remove unnecessary entries from persistent state "disabled services"
    986             boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents);
    987             if (disabledServicesUninstalled) {
    988                 writeDisabledPrintServicesLocked(mDisabledServices);
    989             }
    990 
    991             // Remove unnecessary entries from persistent state "approved services"
    992             mSpooler.pruneApprovedPrintServices(installedComponents);
    993         }
    994     }
    995 
    996     private void onConfigurationChangedLocked() {
    997         ArrayList<ComponentName> installedComponents = getInstalledComponents();
    998 
    999         final int installedCount = installedComponents.size();
   1000         for (int i = 0; i < installedCount; i++) {
   1001             ComponentName serviceName = installedComponents.get(i);
   1002 
   1003             if (!mDisabledServices.contains(serviceName)) {
   1004                 if (!mActiveServices.containsKey(serviceName)) {
   1005                     RemotePrintService service = new RemotePrintService(
   1006                             mContext, serviceName, mUserId, mSpooler, this);
   1007                     addServiceLocked(service);
   1008                 }
   1009             } else {
   1010                 RemotePrintService service = mActiveServices.remove(serviceName);
   1011                 if (service != null) {
   1012                     removeServiceLocked(service);
   1013                 }
   1014             }
   1015         }
   1016 
   1017         Iterator<Map.Entry<ComponentName, RemotePrintService>> iterator =
   1018                 mActiveServices.entrySet().iterator();
   1019         while (iterator.hasNext()) {
   1020             Map.Entry<ComponentName, RemotePrintService> entry = iterator.next();
   1021             ComponentName serviceName = entry.getKey();
   1022             RemotePrintService service = entry.getValue();
   1023             if (!installedComponents.contains(serviceName)) {
   1024                 removeServiceLocked(service);
   1025                 iterator.remove();
   1026             }
   1027         }
   1028 
   1029         onPrintServicesChanged();
   1030     }
   1031 
   1032     private void addServiceLocked(RemotePrintService service) {
   1033         mActiveServices.put(service.getComponentName(), service);
   1034         if (mPrinterDiscoverySession != null) {
   1035             mPrinterDiscoverySession.onServiceAddedLocked(service);
   1036         }
   1037     }
   1038 
   1039     private void removeServiceLocked(RemotePrintService service) {
   1040         // Fail all print jobs.
   1041         failActivePrintJobsForService(service.getComponentName());
   1042         // If discovery is in progress, tear down the service.
   1043         if (mPrinterDiscoverySession != null) {
   1044             mPrinterDiscoverySession.onServiceRemovedLocked(service);
   1045         } else {
   1046             // Otherwise, just destroy it.
   1047             service.destroy();
   1048         }
   1049     }
   1050 
   1051     private void failActivePrintJobsForService(final ComponentName serviceName) {
   1052         // Makes sure all active print jobs are failed since the service
   1053         // just died. Do this off the main thread since we do to allow
   1054         // calls into the spooler on the main thread.
   1055         if (Looper.getMainLooper().isCurrentThread()) {
   1056             BackgroundThread.getHandler().post(new Runnable() {
   1057                 @Override
   1058                 public void run() {
   1059                     failScheduledPrintJobsForServiceInternal(serviceName);
   1060                 }
   1061             });
   1062         } else {
   1063             failScheduledPrintJobsForServiceInternal(serviceName);
   1064         }
   1065     }
   1066 
   1067     private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) {
   1068         List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName,
   1069                 PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
   1070         if (printJobs == null) {
   1071             return;
   1072         }
   1073         final long identity = Binder.clearCallingIdentity();
   1074         try {
   1075             final int printJobCount = printJobs.size();
   1076             for (int i = 0; i < printJobCount; i++) {
   1077                 PrintJobInfo printJob = printJobs.get(i);
   1078                 mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
   1079                         mContext.getString(R.string.reason_service_unavailable));
   1080             }
   1081         } finally {
   1082             Binder.restoreCallingIdentity(identity);
   1083         }
   1084     }
   1085 
   1086     private void throwIfDestroyedLocked() {
   1087         if (mDestroyed) {
   1088             throw new IllegalStateException("Cannot interact with a destroyed instance.");
   1089         }
   1090     }
   1091 
   1092     private void handleDispatchPrintJobStateChanged(PrintJobId printJobId, int appId) {
   1093         final List<PrintJobStateChangeListenerRecord> records;
   1094         synchronized (mLock) {
   1095             if (mPrintJobStateChangeListenerRecords == null) {
   1096                 return;
   1097             }
   1098             records = new ArrayList<PrintJobStateChangeListenerRecord>(
   1099                     mPrintJobStateChangeListenerRecords);
   1100         }
   1101         final int recordCount = records.size();
   1102         for (int i = 0; i < recordCount; i++) {
   1103             PrintJobStateChangeListenerRecord record = records.get(i);
   1104             if (record.appId == PrintManager.APP_ID_ANY
   1105                     || record.appId == appId)
   1106             try {
   1107                 record.listener.onPrintJobStateChanged(printJobId);
   1108             } catch (RemoteException re) {
   1109                 Log.e(LOG_TAG, "Error notifying for print job state change", re);
   1110             }
   1111         }
   1112     }
   1113 
   1114     private void handleDispatchPrintServicesChanged() {
   1115         final List<ListenerRecord<IPrintServicesChangeListener>> records;
   1116         synchronized (mLock) {
   1117             if (mPrintServicesChangeListenerRecords == null) {
   1118                 return;
   1119             }
   1120             records = new ArrayList<>(mPrintServicesChangeListenerRecords);
   1121         }
   1122         final int recordCount = records.size();
   1123         for (int i = 0; i < recordCount; i++) {
   1124             ListenerRecord<IPrintServicesChangeListener> record = records.get(i);
   1125 
   1126             try {
   1127                 record.listener.onPrintServicesChanged();;
   1128             } catch (RemoteException re) {
   1129                 Log.e(LOG_TAG, "Error notifying for print services change", re);
   1130             }
   1131         }
   1132     }
   1133 
   1134     private void handleDispatchPrintServiceRecommendationsUpdated(
   1135             @Nullable List<RecommendationInfo> recommendations) {
   1136         final List<ListenerRecord<IRecommendationsChangeListener>> records;
   1137         synchronized (mLock) {
   1138             if (mPrintServiceRecommendationsChangeListenerRecords == null) {
   1139                 return;
   1140             }
   1141             records = new ArrayList<>(mPrintServiceRecommendationsChangeListenerRecords);
   1142 
   1143             mPrintServiceRecommendations = recommendations;
   1144         }
   1145         final int recordCount = records.size();
   1146         for (int i = 0; i < recordCount; i++) {
   1147             ListenerRecord<IRecommendationsChangeListener> record = records.get(i);
   1148 
   1149             try {
   1150                 record.listener.onRecommendationsChanged();
   1151             } catch (RemoteException re) {
   1152                 Log.e(LOG_TAG, "Error notifying for print service recommendations change", re);
   1153             }
   1154         }
   1155     }
   1156 
   1157     private final class UserStateHandler extends Handler {
   1158         public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1;
   1159         public static final int MSG_DISPATCH_PRINT_SERVICES_CHANGED = 2;
   1160         public static final int MSG_DISPATCH_PRINT_SERVICES_RECOMMENDATIONS_UPDATED = 3;
   1161 
   1162         public UserStateHandler(Looper looper) {
   1163             super(looper, null, false);
   1164         }
   1165 
   1166         @Override
   1167         public void handleMessage(Message message) {
   1168             switch (message.what) {
   1169                 case MSG_DISPATCH_PRINT_JOB_STATE_CHANGED:
   1170                     PrintJobId printJobId = (PrintJobId) message.obj;
   1171                     final int appId = message.arg1;
   1172                     handleDispatchPrintJobStateChanged(printJobId, appId);
   1173                     break;
   1174                 case MSG_DISPATCH_PRINT_SERVICES_CHANGED:
   1175                     handleDispatchPrintServicesChanged();
   1176                     break;
   1177                 case MSG_DISPATCH_PRINT_SERVICES_RECOMMENDATIONS_UPDATED:
   1178                     handleDispatchPrintServiceRecommendationsUpdated(
   1179                             (List<RecommendationInfo>) message.obj);
   1180                     break;
   1181                 default:
   1182                     // not reached
   1183             }
   1184         }
   1185     }
   1186 
   1187     private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient {
   1188         @NonNull final IPrintJobStateChangeListener listener;
   1189         final int appId;
   1190 
   1191         public PrintJobStateChangeListenerRecord(@NonNull IPrintJobStateChangeListener listener,
   1192                 int appId) throws RemoteException {
   1193             this.listener = listener;
   1194             this.appId = appId;
   1195             listener.asBinder().linkToDeath(this, 0);
   1196         }
   1197 
   1198         @Override
   1199         public void binderDied() {
   1200             listener.asBinder().unlinkToDeath(this, 0);
   1201             onBinderDied();
   1202         }
   1203 
   1204         public abstract void onBinderDied();
   1205     }
   1206 
   1207     private abstract class ListenerRecord<T extends IInterface> implements DeathRecipient {
   1208         @NonNull final T listener;
   1209 
   1210         public ListenerRecord(@NonNull T listener) throws RemoteException {
   1211             this.listener = listener;
   1212             listener.asBinder().linkToDeath(this, 0);
   1213         }
   1214 
   1215         @Override
   1216         public void binderDied() {
   1217             listener.asBinder().unlinkToDeath(this, 0);
   1218             onBinderDied();
   1219         }
   1220 
   1221         public abstract void onBinderDied();
   1222     }
   1223 
   1224     private class PrinterDiscoverySessionMediator {
   1225         private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
   1226                 new ArrayMap<PrinterId, PrinterInfo>();
   1227 
   1228         private final RemoteCallbackList<IPrinterDiscoveryObserver> mDiscoveryObservers =
   1229                 new RemoteCallbackList<IPrinterDiscoveryObserver>() {
   1230             @Override
   1231             public void onCallbackDied(IPrinterDiscoveryObserver observer) {
   1232                 synchronized (mLock) {
   1233                     stopPrinterDiscoveryLocked(observer);
   1234                     removeObserverLocked(observer);
   1235                 }
   1236             }
   1237         };
   1238 
   1239         private final List<IBinder> mStartedPrinterDiscoveryTokens = new ArrayList<IBinder>();
   1240 
   1241         private final List<PrinterId> mStateTrackedPrinters = new ArrayList<PrinterId>();
   1242 
   1243         private final Handler mSessionHandler;
   1244 
   1245         private boolean mIsDestroyed;
   1246 
   1247         public PrinterDiscoverySessionMediator(Context context) {
   1248             mSessionHandler = new SessionHandler(context.getMainLooper());
   1249             // Kick off the session creation.
   1250             List<RemotePrintService> services = new ArrayList<RemotePrintService>(
   1251                     mActiveServices.values());
   1252             mSessionHandler.obtainMessage(SessionHandler
   1253                     .MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION, services)
   1254                     .sendToTarget();
   1255         }
   1256 
   1257         public void addObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
   1258             // Add the observer.
   1259             mDiscoveryObservers.register(observer);
   1260 
   1261             // Bring the added observer up to speed with the printers.
   1262             if (!mPrinters.isEmpty()) {
   1263                 List<PrinterInfo> printers = new ArrayList<PrinterInfo>(mPrinters.values());
   1264                 SomeArgs args = SomeArgs.obtain();
   1265                 args.arg1 = observer;
   1266                 args.arg2 = printers;
   1267                 mSessionHandler.obtainMessage(SessionHandler.MSG_PRINTERS_ADDED,
   1268                         args).sendToTarget();
   1269             }
   1270         }
   1271 
   1272         public void removeObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
   1273             // Remove the observer.
   1274             mDiscoveryObservers.unregister(observer);
   1275             // No one else observing - then kill it.
   1276             if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) {
   1277                 destroyLocked();
   1278             }
   1279         }
   1280 
   1281         public final void startPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer,
   1282                 @Nullable List<PrinterId> priorityList) {
   1283             if (mIsDestroyed) {
   1284                 Log.w(LOG_TAG, "Not starting dicovery - session destroyed");
   1285                 return;
   1286             }
   1287 
   1288             final boolean discoveryStarted = !mStartedPrinterDiscoveryTokens.isEmpty();
   1289 
   1290             // Remember we got a start request to match with an end.
   1291             mStartedPrinterDiscoveryTokens.add(observer.asBinder());
   1292 
   1293             // If printer discovery is ongoing and the start request has a list
   1294             // of printer to be checked, then we just request validating them.
   1295             if (discoveryStarted && priorityList != null && !priorityList.isEmpty()) {
   1296                 validatePrinters(priorityList);
   1297                 return;
   1298             }
   1299 
   1300             // The service are already performing discovery - nothing to do.
   1301             if (mStartedPrinterDiscoveryTokens.size() > 1) {
   1302                 return;
   1303             }
   1304 
   1305             List<RemotePrintService> services = new ArrayList<RemotePrintService>(
   1306                     mActiveServices.values());
   1307             SomeArgs args = SomeArgs.obtain();
   1308             args.arg1 = services;
   1309             args.arg2 = priorityList;
   1310             mSessionHandler.obtainMessage(SessionHandler
   1311                     .MSG_DISPATCH_START_PRINTER_DISCOVERY, args)
   1312                     .sendToTarget();
   1313         }
   1314 
   1315         public final void stopPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer) {
   1316             if (mIsDestroyed) {
   1317                 Log.w(LOG_TAG, "Not stopping dicovery - session destroyed");
   1318                 return;
   1319             }
   1320             // This one did not make an active discovery request - nothing to do.
   1321             if (!mStartedPrinterDiscoveryTokens.remove(observer.asBinder())) {
   1322                 return;
   1323             }
   1324             // There are other interested observers - do not stop discovery.
   1325             if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
   1326                 return;
   1327             }
   1328             List<RemotePrintService> services = new ArrayList<RemotePrintService>(
   1329                     mActiveServices.values());
   1330             mSessionHandler.obtainMessage(SessionHandler
   1331                     .MSG_DISPATCH_STOP_PRINTER_DISCOVERY, services)
   1332                     .sendToTarget();
   1333         }
   1334 
   1335         public void validatePrintersLocked(@NonNull List<PrinterId> printerIds) {
   1336             if (mIsDestroyed) {
   1337                 Log.w(LOG_TAG, "Not validating pritners - session destroyed");
   1338                 return;
   1339             }
   1340 
   1341             List<PrinterId> remainingList = new ArrayList<PrinterId>(printerIds);
   1342             while (!remainingList.isEmpty()) {
   1343                 Iterator<PrinterId> iterator = remainingList.iterator();
   1344                 // Gather the printers per service and request a validation.
   1345                 List<PrinterId> updateList = new ArrayList<PrinterId>();
   1346                 ComponentName serviceName = null;
   1347                 while (iterator.hasNext()) {
   1348                     PrinterId printerId = iterator.next();
   1349                     if (printerId != null) {
   1350                         if (updateList.isEmpty()) {
   1351                             updateList.add(printerId);
   1352                             serviceName = printerId.getServiceName();
   1353                             iterator.remove();
   1354                         } else if (printerId.getServiceName().equals(serviceName)) {
   1355                             updateList.add(printerId);
   1356                             iterator.remove();
   1357                         }
   1358                     }
   1359                 }
   1360                 // Schedule a notification of the service.
   1361                 RemotePrintService service = mActiveServices.get(serviceName);
   1362                 if (service != null) {
   1363                     SomeArgs args = SomeArgs.obtain();
   1364                     args.arg1 = service;
   1365                     args.arg2 = updateList;
   1366                     mSessionHandler.obtainMessage(SessionHandler
   1367                             .MSG_VALIDATE_PRINTERS, args)
   1368                             .sendToTarget();
   1369                 }
   1370             }
   1371         }
   1372 
   1373         public final void startPrinterStateTrackingLocked(@NonNull PrinterId printerId) {
   1374             if (mIsDestroyed) {
   1375                 Log.w(LOG_TAG, "Not starting printer state tracking - session destroyed");
   1376                 return;
   1377             }
   1378             // If printer discovery is not started - nothing to do.
   1379             if (mStartedPrinterDiscoveryTokens.isEmpty()) {
   1380                 return;
   1381             }
   1382             final boolean containedPrinterId = mStateTrackedPrinters.contains(printerId);
   1383             // Keep track of the number of requests to track this one.
   1384             mStateTrackedPrinters.add(printerId);
   1385             // If we were tracking this printer - nothing to do.
   1386             if (containedPrinterId) {
   1387                 return;
   1388             }
   1389             // No service - nothing to do.
   1390             RemotePrintService service = mActiveServices.get(printerId.getServiceName());
   1391             if (service == null) {
   1392                 return;
   1393             }
   1394             // Ask the service to start tracking.
   1395             SomeArgs args = SomeArgs.obtain();
   1396             args.arg1 = service;
   1397             args.arg2 = printerId;
   1398             mSessionHandler.obtainMessage(SessionHandler
   1399                     .MSG_START_PRINTER_STATE_TRACKING, args)
   1400                     .sendToTarget();
   1401         }
   1402 
   1403         public final void stopPrinterStateTrackingLocked(PrinterId printerId) {
   1404             if (mIsDestroyed) {
   1405                 Log.w(LOG_TAG, "Not stopping printer state tracking - session destroyed");
   1406                 return;
   1407             }
   1408             // If printer discovery is not started - nothing to do.
   1409             if (mStartedPrinterDiscoveryTokens.isEmpty()) {
   1410                 return;
   1411             }
   1412             // If we did not track this printer - nothing to do.
   1413             if (!mStateTrackedPrinters.remove(printerId)) {
   1414                 return;
   1415             }
   1416             // No service - nothing to do.
   1417             RemotePrintService service = mActiveServices.get(printerId.getServiceName());
   1418             if (service == null) {
   1419                 return;
   1420             }
   1421             // Ask the service to start tracking.
   1422             SomeArgs args = SomeArgs.obtain();
   1423             args.arg1 = service;
   1424             args.arg2 = printerId;
   1425             mSessionHandler.obtainMessage(SessionHandler
   1426                     .MSG_STOP_PRINTER_STATE_TRACKING, args)
   1427                     .sendToTarget();
   1428         }
   1429 
   1430         public void onDestroyed() {
   1431             /* do nothing */
   1432         }
   1433 
   1434         public void destroyLocked() {
   1435             if (mIsDestroyed) {
   1436                 Log.w(LOG_TAG, "Not destroying - session destroyed");
   1437                 return;
   1438             }
   1439             mIsDestroyed = true;
   1440             // Make sure printer tracking is stopped.
   1441             final int printerCount = mStateTrackedPrinters.size();
   1442             for (int i = 0; i < printerCount; i++) {
   1443                 PrinterId printerId = mStateTrackedPrinters.get(i);
   1444                 stopPrinterStateTracking(printerId);
   1445             }
   1446             // Make sure discovery is stopped.
   1447             final int observerCount = mStartedPrinterDiscoveryTokens.size();
   1448             for (int i = 0; i < observerCount; i++) {
   1449                 IBinder token = mStartedPrinterDiscoveryTokens.get(i);
   1450                 stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver.Stub.asInterface(token));
   1451             }
   1452             // Tell the services we are done.
   1453             List<RemotePrintService> services = new ArrayList<RemotePrintService>(
   1454                     mActiveServices.values());
   1455             mSessionHandler.obtainMessage(SessionHandler
   1456                     .MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION, services)
   1457                     .sendToTarget();
   1458         }
   1459 
   1460         public void onPrintersAddedLocked(List<PrinterInfo> printers) {
   1461             if (DEBUG) {
   1462                 Log.i(LOG_TAG, "onPrintersAddedLocked()");
   1463             }
   1464             if (mIsDestroyed) {
   1465                 Log.w(LOG_TAG, "Not adding printers - session destroyed");
   1466                 return;
   1467             }
   1468             List<PrinterInfo> addedPrinters = null;
   1469             final int addedPrinterCount = printers.size();
   1470             for (int i = 0; i < addedPrinterCount; i++) {
   1471                 PrinterInfo printer = printers.get(i);
   1472                 PrinterInfo oldPrinter = mPrinters.put(printer.getId(), printer);
   1473                 if (oldPrinter == null || !oldPrinter.equals(printer)) {
   1474                     if (addedPrinters == null) {
   1475                         addedPrinters = new ArrayList<PrinterInfo>();
   1476                     }
   1477                     addedPrinters.add(printer);
   1478                 }
   1479             }
   1480             if (addedPrinters != null) {
   1481                 mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
   1482                         addedPrinters).sendToTarget();
   1483             }
   1484         }
   1485 
   1486         public void onPrintersRemovedLocked(List<PrinterId> printerIds) {
   1487             if (DEBUG) {
   1488                 Log.i(LOG_TAG, "onPrintersRemovedLocked()");
   1489             }
   1490             if (mIsDestroyed) {
   1491                 Log.w(LOG_TAG, "Not removing printers - session destroyed");
   1492                 return;
   1493             }
   1494             List<PrinterId> removedPrinterIds = null;
   1495             final int removedPrinterCount = printerIds.size();
   1496             for (int i = 0; i < removedPrinterCount; i++) {
   1497                 PrinterId removedPrinterId = printerIds.get(i);
   1498                 if (mPrinters.remove(removedPrinterId) != null) {
   1499                     if (removedPrinterIds == null) {
   1500                         removedPrinterIds = new ArrayList<PrinterId>();
   1501                     }
   1502                     removedPrinterIds.add(removedPrinterId);
   1503                 }
   1504             }
   1505             if (removedPrinterIds != null) {
   1506                 mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED,
   1507                         removedPrinterIds).sendToTarget();
   1508             }
   1509         }
   1510 
   1511         public void onServiceRemovedLocked(RemotePrintService service) {
   1512             if (mIsDestroyed) {
   1513                 Log.w(LOG_TAG, "Not updating removed service - session destroyed");
   1514                 return;
   1515             }
   1516             // Remove the reported and tracked printers for that service.
   1517             ComponentName serviceName = service.getComponentName();
   1518             removePrintersForServiceLocked(serviceName);
   1519             service.destroy();
   1520         }
   1521 
   1522         /**
   1523          * Handle that a custom icon for a printer was loaded.
   1524          *
   1525          * This increments the icon generation and adds the printer again which triggers an update
   1526          * in all users of the currently known printers.
   1527          *
   1528          * @param printerId the id of the printer the icon belongs to
   1529          * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
   1530          */
   1531         public void onCustomPrinterIconLoadedLocked(PrinterId printerId) {
   1532             if (DEBUG) {
   1533                 Log.i(LOG_TAG, "onCustomPrinterIconLoadedLocked()");
   1534             }
   1535             if (mIsDestroyed) {
   1536                 Log.w(LOG_TAG, "Not updating printer - session destroyed");
   1537                 return;
   1538             }
   1539 
   1540             PrinterInfo printer = mPrinters.get(printerId);
   1541             if (printer != null) {
   1542                 PrinterInfo newPrinter = (new PrinterInfo.Builder(printer))
   1543                         .incCustomPrinterIconGen().build();
   1544                 mPrinters.put(printerId, newPrinter);
   1545 
   1546                 ArrayList<PrinterInfo> addedPrinters = new ArrayList<>(1);
   1547                 addedPrinters.add(newPrinter);
   1548                 mSessionHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
   1549                         addedPrinters).sendToTarget();
   1550             }
   1551         }
   1552 
   1553         public void onServiceDiedLocked(RemotePrintService service) {
   1554             // Remove the reported by that service.
   1555             removePrintersForServiceLocked(service.getComponentName());
   1556         }
   1557 
   1558         public void onServiceAddedLocked(RemotePrintService service) {
   1559             if (mIsDestroyed) {
   1560                 Log.w(LOG_TAG, "Not updating added service - session destroyed");
   1561                 return;
   1562             }
   1563             // Tell the service to create a session.
   1564             mSessionHandler.obtainMessage(
   1565                     SessionHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
   1566                     service).sendToTarget();
   1567             // Start printer discovery if necessary.
   1568             if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
   1569                 mSessionHandler.obtainMessage(
   1570                         SessionHandler.MSG_START_PRINTER_DISCOVERY,
   1571                         service).sendToTarget();
   1572             }
   1573             // Start tracking printers if necessary
   1574             final int trackedPrinterCount = mStateTrackedPrinters.size();
   1575             for (int i = 0; i < trackedPrinterCount; i++) {
   1576                 PrinterId printerId = mStateTrackedPrinters.get(i);
   1577                 if (printerId.getServiceName().equals(service.getComponentName())) {
   1578                     SomeArgs args = SomeArgs.obtain();
   1579                     args.arg1 = service;
   1580                     args.arg2 = printerId;
   1581                     mSessionHandler.obtainMessage(SessionHandler
   1582                             .MSG_START_PRINTER_STATE_TRACKING, args)
   1583                             .sendToTarget();
   1584                 }
   1585             }
   1586         }
   1587 
   1588         public void dump(PrintWriter pw, String prefix) {
   1589             pw.append(prefix).append("destroyed=")
   1590                     .append(String.valueOf(mDestroyed)).println();
   1591 
   1592             pw.append(prefix).append("printDiscoveryInProgress=")
   1593                     .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println();
   1594 
   1595             String tab = "  ";
   1596 
   1597             pw.append(prefix).append(tab).append("printer discovery observers:").println();
   1598             final int observerCount = mDiscoveryObservers.beginBroadcast();
   1599             for (int i = 0; i < observerCount; i++) {
   1600                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
   1601                 pw.append(prefix).append(prefix).append(observer.toString());
   1602                 pw.println();
   1603             }
   1604             mDiscoveryObservers.finishBroadcast();
   1605 
   1606             pw.append(prefix).append(tab).append("start discovery requests:").println();
   1607             final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
   1608             for (int i = 0; i < tokenCount; i++) {
   1609                 IBinder token = mStartedPrinterDiscoveryTokens.get(i);
   1610                 pw.append(prefix).append(tab).append(tab).append(token.toString()).println();
   1611             }
   1612 
   1613             pw.append(prefix).append(tab).append("tracked printer requests:").println();
   1614             final int trackedPrinters = mStateTrackedPrinters.size();
   1615             for (int i = 0; i < trackedPrinters; i++) {
   1616                 PrinterId printer = mStateTrackedPrinters.get(i);
   1617                 pw.append(prefix).append(tab).append(tab).append(printer.toString()).println();
   1618             }
   1619 
   1620             pw.append(prefix).append(tab).append("printers:").println();
   1621             final int pritnerCount = mPrinters.size();
   1622             for (int i = 0; i < pritnerCount; i++) {
   1623                 PrinterInfo printer = mPrinters.valueAt(i);
   1624                 pw.append(prefix).append(tab).append(tab).append(
   1625                         printer.toString()).println();
   1626             }
   1627         }
   1628 
   1629         private void removePrintersForServiceLocked(ComponentName serviceName) {
   1630             // No printers - nothing to do.
   1631             if (mPrinters.isEmpty()) {
   1632                 return;
   1633             }
   1634             // Remove the printers for that service.
   1635             List<PrinterId> removedPrinterIds = null;
   1636             final int printerCount = mPrinters.size();
   1637             for (int i = 0; i < printerCount; i++) {
   1638                 PrinterId printerId = mPrinters.keyAt(i);
   1639                 if (printerId.getServiceName().equals(serviceName)) {
   1640                     if (removedPrinterIds == null) {
   1641                         removedPrinterIds = new ArrayList<PrinterId>();
   1642                     }
   1643                     removedPrinterIds.add(printerId);
   1644                 }
   1645             }
   1646             if (removedPrinterIds != null) {
   1647                 final int removedPrinterCount = removedPrinterIds.size();
   1648                 for (int i = 0; i < removedPrinterCount; i++) {
   1649                     mPrinters.remove(removedPrinterIds.get(i));
   1650                 }
   1651                 mSessionHandler.obtainMessage(
   1652                         SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED,
   1653                         removedPrinterIds).sendToTarget();
   1654             }
   1655         }
   1656 
   1657         private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) {
   1658             final int observerCount = mDiscoveryObservers.beginBroadcast();
   1659             for (int i = 0; i < observerCount; i++) {
   1660                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
   1661                 handlePrintersAdded(observer, addedPrinters);
   1662             }
   1663             mDiscoveryObservers.finishBroadcast();
   1664         }
   1665 
   1666         private void handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds) {
   1667             final int observerCount = mDiscoveryObservers.beginBroadcast();
   1668             for (int i = 0; i < observerCount; i++) {
   1669                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
   1670                 handlePrintersRemoved(observer, removedPrinterIds);
   1671             }
   1672             mDiscoveryObservers.finishBroadcast();
   1673         }
   1674 
   1675         private void handleDispatchCreatePrinterDiscoverySession(
   1676                 List<RemotePrintService> services) {
   1677             final int serviceCount = services.size();
   1678             for (int i = 0; i < serviceCount; i++) {
   1679                 RemotePrintService service = services.get(i);
   1680                 service.createPrinterDiscoverySession();
   1681             }
   1682         }
   1683 
   1684         private void handleDispatchDestroyPrinterDiscoverySession(
   1685                 List<RemotePrintService> services) {
   1686             final int serviceCount = services.size();
   1687             for (int i = 0; i < serviceCount; i++) {
   1688                 RemotePrintService service = services.get(i);
   1689                 service.destroyPrinterDiscoverySession();
   1690             }
   1691             onDestroyed();
   1692         }
   1693 
   1694         private void handleDispatchStartPrinterDiscovery(
   1695                 List<RemotePrintService> services, List<PrinterId> printerIds) {
   1696             final int serviceCount = services.size();
   1697             for (int i = 0; i < serviceCount; i++) {
   1698                 RemotePrintService service = services.get(i);
   1699                 service.startPrinterDiscovery(printerIds);
   1700             }
   1701         }
   1702 
   1703         private void handleDispatchStopPrinterDiscovery(List<RemotePrintService> services) {
   1704             final int serviceCount = services.size();
   1705             for (int i = 0; i < serviceCount; i++) {
   1706                 RemotePrintService service = services.get(i);
   1707                 service.stopPrinterDiscovery();
   1708             }
   1709         }
   1710 
   1711         private void handleValidatePrinters(RemotePrintService service,
   1712                 List<PrinterId> printerIds) {
   1713             service.validatePrinters(printerIds);
   1714         }
   1715 
   1716         private void handleStartPrinterStateTracking(@NonNull RemotePrintService service,
   1717                 @NonNull PrinterId printerId) {
   1718             service.startPrinterStateTracking(printerId);
   1719         }
   1720 
   1721         private void handleStopPrinterStateTracking(RemotePrintService service,
   1722                 PrinterId printerId) {
   1723             service.stopPrinterStateTracking(printerId);
   1724         }
   1725 
   1726         private void handlePrintersAdded(IPrinterDiscoveryObserver observer,
   1727             List<PrinterInfo> printers) {
   1728             try {
   1729                 observer.onPrintersAdded(new ParceledListSlice<PrinterInfo>(printers));
   1730             } catch (RemoteException re) {
   1731                 Log.e(LOG_TAG, "Error sending added printers", re);
   1732             }
   1733         }
   1734 
   1735         private void handlePrintersRemoved(IPrinterDiscoveryObserver observer,
   1736             List<PrinterId> printerIds) {
   1737             try {
   1738                 observer.onPrintersRemoved(new ParceledListSlice<PrinterId>(printerIds));
   1739             } catch (RemoteException re) {
   1740                 Log.e(LOG_TAG, "Error sending removed printers", re);
   1741             }
   1742         }
   1743 
   1744         private final class SessionHandler extends Handler {
   1745             public static final int MSG_PRINTERS_ADDED = 1;
   1746             public static final int MSG_PRINTERS_REMOVED = 2;
   1747             public static final int MSG_DISPATCH_PRINTERS_ADDED = 3;
   1748             public static final int MSG_DISPATCH_PRINTERS_REMOVED = 4;
   1749 
   1750             public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 5;
   1751             public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 6;
   1752             public static final int MSG_START_PRINTER_DISCOVERY = 7;
   1753             public static final int MSG_STOP_PRINTER_DISCOVERY = 8;
   1754             public static final int MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION = 9;
   1755             public static final int MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION = 10;
   1756             public static final int MSG_DISPATCH_START_PRINTER_DISCOVERY = 11;
   1757             public static final int MSG_DISPATCH_STOP_PRINTER_DISCOVERY = 12;
   1758             public static final int MSG_VALIDATE_PRINTERS = 13;
   1759             public static final int MSG_START_PRINTER_STATE_TRACKING = 14;
   1760             public static final int MSG_STOP_PRINTER_STATE_TRACKING = 15;
   1761             public static final int MSG_DESTROY_SERVICE = 16;
   1762 
   1763             SessionHandler(Looper looper) {
   1764                 super(looper, null, false);
   1765             }
   1766 
   1767             @Override
   1768             @SuppressWarnings("unchecked")
   1769             public void handleMessage(Message message) {
   1770                 switch (message.what) {
   1771                     case MSG_PRINTERS_ADDED: {
   1772                         SomeArgs args = (SomeArgs) message.obj;
   1773                         IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1;
   1774                         List<PrinterInfo> addedPrinters = (List<PrinterInfo>) args.arg2;
   1775                         args.recycle();
   1776                         handlePrintersAdded(observer, addedPrinters);
   1777                     } break;
   1778 
   1779                     case MSG_PRINTERS_REMOVED: {
   1780                         SomeArgs args = (SomeArgs) message.obj;
   1781                         IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1;
   1782                         List<PrinterId> removedPrinterIds = (List<PrinterId>) args.arg2;
   1783                         args.recycle();
   1784                         handlePrintersRemoved(observer, removedPrinterIds);
   1785                     }
   1786 
   1787                     case MSG_DISPATCH_PRINTERS_ADDED: {
   1788                         List<PrinterInfo> addedPrinters = (List<PrinterInfo>) message.obj;
   1789                         handleDispatchPrintersAdded(addedPrinters);
   1790                     } break;
   1791 
   1792                     case MSG_DISPATCH_PRINTERS_REMOVED: {
   1793                         List<PrinterId> removedPrinterIds = (List<PrinterId>) message.obj;
   1794                         handleDispatchPrintersRemoved(removedPrinterIds);
   1795                     } break;
   1796 
   1797                     case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
   1798                         RemotePrintService service = (RemotePrintService) message.obj;
   1799                         service.createPrinterDiscoverySession();
   1800                     } break;
   1801 
   1802                     case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
   1803                         RemotePrintService service = (RemotePrintService) message.obj;
   1804                         service.destroyPrinterDiscoverySession();
   1805                     } break;
   1806 
   1807                     case MSG_START_PRINTER_DISCOVERY: {
   1808                         RemotePrintService service = (RemotePrintService) message.obj;
   1809                         service.startPrinterDiscovery(null);
   1810                     } break;
   1811 
   1812                     case MSG_STOP_PRINTER_DISCOVERY: {
   1813                         RemotePrintService service = (RemotePrintService) message.obj;
   1814                         service.stopPrinterDiscovery();
   1815                     } break;
   1816 
   1817                     case MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION: {
   1818                         List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
   1819                         handleDispatchCreatePrinterDiscoverySession(services);
   1820                     } break;
   1821 
   1822                     case MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION: {
   1823                         List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
   1824                         handleDispatchDestroyPrinterDiscoverySession(services);
   1825                     } break;
   1826 
   1827                     case MSG_DISPATCH_START_PRINTER_DISCOVERY: {
   1828                         SomeArgs args = (SomeArgs) message.obj;
   1829                         List<RemotePrintService> services = (List<RemotePrintService>) args.arg1;
   1830                         List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
   1831                         args.recycle();
   1832                         handleDispatchStartPrinterDiscovery(services, printerIds);
   1833                     } break;
   1834 
   1835                     case MSG_DISPATCH_STOP_PRINTER_DISCOVERY: {
   1836                         List<RemotePrintService> services = (List<RemotePrintService>) message.obj;
   1837                         handleDispatchStopPrinterDiscovery(services);
   1838                     } break;
   1839 
   1840                     case MSG_VALIDATE_PRINTERS: {
   1841                         SomeArgs args = (SomeArgs) message.obj;
   1842                         RemotePrintService service = (RemotePrintService) args.arg1;
   1843                         List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
   1844                         args.recycle();
   1845                         handleValidatePrinters(service, printerIds);
   1846                     } break;
   1847 
   1848                     case MSG_START_PRINTER_STATE_TRACKING: {
   1849                         SomeArgs args = (SomeArgs) message.obj;
   1850                         RemotePrintService service = (RemotePrintService) args.arg1;
   1851                         PrinterId printerId = (PrinterId) args.arg2;
   1852                         args.recycle();
   1853                         handleStartPrinterStateTracking(service, printerId);
   1854                     } break;
   1855 
   1856                     case MSG_STOP_PRINTER_STATE_TRACKING: {
   1857                         SomeArgs args = (SomeArgs) message.obj;
   1858                         RemotePrintService service = (RemotePrintService) args.arg1;
   1859                         PrinterId printerId = (PrinterId) args.arg2;
   1860                         args.recycle();
   1861                         handleStopPrinterStateTracking(service, printerId);
   1862                     } break;
   1863 
   1864                     case MSG_DESTROY_SERVICE: {
   1865                         RemotePrintService service = (RemotePrintService) message.obj;
   1866                         service.destroy();
   1867                     } break;
   1868                 }
   1869             }
   1870         }
   1871     }
   1872 
   1873     private final class PrintJobForAppCache {
   1874         private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
   1875                 new SparseArray<List<PrintJobInfo>>();
   1876 
   1877         public boolean onPrintJobCreated(final IBinder creator, final int appId,
   1878                 PrintJobInfo printJob) {
   1879             try {
   1880                 creator.linkToDeath(new DeathRecipient() {
   1881                     @Override
   1882                     public void binderDied() {
   1883                         creator.unlinkToDeath(this, 0);
   1884                         synchronized (mLock) {
   1885                             mPrintJobsForRunningApp.remove(appId);
   1886                         }
   1887                     }
   1888                 }, 0);
   1889             } catch (RemoteException re) {
   1890                 /* The process is already dead - we just failed. */
   1891                 return false;
   1892             }
   1893             synchronized (mLock) {
   1894                 List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
   1895                 if (printJobsForApp == null) {
   1896                     printJobsForApp = new ArrayList<PrintJobInfo>();
   1897                     mPrintJobsForRunningApp.put(appId, printJobsForApp);
   1898                 }
   1899                 printJobsForApp.add(printJob);
   1900             }
   1901             return true;
   1902         }
   1903 
   1904         public void onPrintJobStateChanged(PrintJobInfo printJob) {
   1905             synchronized (mLock) {
   1906                 List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
   1907                         printJob.getAppId());
   1908                 if (printJobsForApp == null) {
   1909                     return;
   1910                 }
   1911                 final int printJobCount = printJobsForApp.size();
   1912                 for (int i = 0; i < printJobCount; i++) {
   1913                     PrintJobInfo oldPrintJob = printJobsForApp.get(i);
   1914                     if (oldPrintJob.getId().equals(printJob.getId())) {
   1915                         printJobsForApp.set(i, printJob);
   1916                     }
   1917                 }
   1918             }
   1919         }
   1920 
   1921         public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
   1922             synchronized (mLock) {
   1923                 List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
   1924                 if (printJobsForApp == null) {
   1925                     return null;
   1926                 }
   1927                 final int printJobCount = printJobsForApp.size();
   1928                 for (int i = 0; i < printJobCount; i++) {
   1929                     PrintJobInfo printJob = printJobsForApp.get(i);
   1930                     if (printJob.getId().equals(printJobId)) {
   1931                         return printJob;
   1932                     }
   1933                 }
   1934             }
   1935             return null;
   1936         }
   1937 
   1938         public List<PrintJobInfo> getPrintJobs(int appId) {
   1939             synchronized (mLock) {
   1940                 List<PrintJobInfo> printJobs = null;
   1941                 if (appId == PrintManager.APP_ID_ANY) {
   1942                     final int bucketCount = mPrintJobsForRunningApp.size();
   1943                     for (int i = 0; i < bucketCount; i++) {
   1944                         List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
   1945                         if (printJobs == null) {
   1946                             printJobs = new ArrayList<PrintJobInfo>();
   1947                         }
   1948                         printJobs.addAll(bucket);
   1949                     }
   1950                 } else {
   1951                     List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
   1952                     if (bucket != null) {
   1953                         if (printJobs == null) {
   1954                             printJobs = new ArrayList<PrintJobInfo>();
   1955                         }
   1956                         printJobs.addAll(bucket);
   1957                     }
   1958                 }
   1959                 if (printJobs != null) {
   1960                     return printJobs;
   1961                 }
   1962                 return Collections.emptyList();
   1963             }
   1964         }
   1965 
   1966         public void dump(PrintWriter pw, String prefix) {
   1967             synchronized (mLock) {
   1968                 String tab = "  ";
   1969                 final int bucketCount = mPrintJobsForRunningApp.size();
   1970                 for (int i = 0; i < bucketCount; i++) {
   1971                     final int appId = mPrintJobsForRunningApp.keyAt(i);
   1972                     pw.append(prefix).append("appId=" + appId).append(':').println();
   1973                     List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
   1974                     final int printJobCount = bucket.size();
   1975                     for (int j = 0; j < printJobCount; j++) {
   1976                         PrintJobInfo printJob = bucket.get(j);
   1977                         pw.append(prefix).append(tab).append(printJob.toString()).println();
   1978                     }
   1979                 }
   1980             }
   1981         }
   1982     }
   1983 }
   1984