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