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