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 
UserState(Context context, int userId, Object lock, boolean lowPriority)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 
increasePriority()182     public void increasePriority() {
183         mSpooler.increasePriority();
184     }
185 
186     @Override
onPrintJobQueued(PrintJobInfo printJob)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
onAllPrintJobsForServiceHandled(ComponentName printService)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 
removeObsoletePrintJobs()216     public void removeObsoletePrintJobs() {
217         mSpooler.removeObsoletePrintJobs();
218     }
219 
220     @SuppressWarnings("deprecation")
print(@onNull String printJobName, @NonNull IPrintDocumentAdapter adapter, @Nullable PrintAttributes attributes, @NonNull String packageName, int appId)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(Intent.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 
getPrintJobInfos(int appId)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 
getPrintJobInfo(@onNull PrintJobId printJobId, int appId)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      */
getCustomPrinterIcon(@onNull PrinterId printerId)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 
cancelPrintJob(@onNull PrintJobId printJobId, int appId)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 
restartPrintJob(@onNull PrintJobId printJobId, int appId)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 
getPrintServices(int selectionFlags)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 
setPrintServiceEnabled(@onNull ComponentName serviceName, boolean isEnabled)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      */
getPrintServiceRecommendations()443     public @Nullable List<RecommendationInfo> getPrintServiceRecommendations() {
444         return mPrintServiceRecommendations;
445     }
446 
createPrinterDiscoverySession(@onNull IPrinterDiscoveryObserver observer)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 
destroyPrinterDiscoverySession(@onNull IPrinterDiscoveryObserver observer)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 
startPrinterDiscovery(@onNull IPrinterDiscoveryObserver observer, @Nullable List<PrinterId> printerIds)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 
stopPrinterDiscovery(@onNull IPrinterDiscoveryObserver observer)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 
validatePrinters(@onNull List<PrinterId> printerIds)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 
startPrinterStateTracking(@onNull PrinterId printerId)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 
stopPrinterStateTracking(PrinterId printerId)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 
addPrintJobStateChangeListener(@onNull IPrintJobStateChangeListener listener, int appId)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 
removePrintJobStateChangeListener(@onNull IPrintJobStateChangeListener listener)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 
addPrintServicesChangeListener(@onNull IPrintServicesChangeListener listener)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 
removePrintServicesChangeListener(@onNull IPrintServicesChangeListener listener)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 
addPrintServiceRecommendationsChangeListener( @onNull IRecommendationsChangeListener listener)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 
removePrintServiceRecommendationsChangeListener( @onNull IRecommendationsChangeListener listener)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
onPrintJobStateChanged(PrintJobInfo printJob)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 
onPrintServicesChanged()706     public void onPrintServicesChanged() {
707         Handler.getMain().sendMessage(obtainMessage(
708                 UserState::handleDispatchPrintServicesChanged, this));
709     }
710 
711     @Override
onPrintServiceRecommendationsUpdated(List<RecommendationInfo> recommendations)712     public void onPrintServiceRecommendationsUpdated(List<RecommendationInfo> recommendations) {
713         Handler.getMain().sendMessage(obtainMessage(
714                 UserState::handleDispatchPrintServiceRecommendationsUpdated,
715                 this, recommendations));
716     }
717 
718     @Override
onPrintersAdded(List<PrinterInfo> printers)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
onPrintersRemoved(List<PrinterId> printerIds)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
onCustomPrinterIconLoaded(PrinterId printerId, Icon icon)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
onServiceDied(RemotePrintService service)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 
updateIfNeededLocked()792     public void updateIfNeededLocked() {
793         throwIfDestroyedLocked();
794         readConfigurationLocked();
795         onConfigurationChangedLocked();
796     }
797 
destroyLocked()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 
dump(@onNull DualDumpOutputStream dumpStream)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 
readConfigurationLocked()873     private void readConfigurationLocked() {
874         readInstalledPrintServicesLocked();
875         readDisabledPrintServicesLocked();
876     }
877 
readInstalledPrintServicesLocked()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      */
upgradePersistentStateIfNeeded()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      */
readDisabledPrintServicesLocked()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 
readPrintServicesFromSettingLocked(String setting, Set<ComponentName> outServiceNames)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      */
writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices)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      */
getInstalledComponents()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      */
prunePrintServices()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 
onConfigurationChangedLocked()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 
addServiceLocked(RemotePrintService service)1070     private void addServiceLocked(RemotePrintService service) {
1071         mActiveServices.put(service.getComponentName(), service);
1072         if (mPrinterDiscoverySession != null) {
1073             mPrinterDiscoverySession.onServiceAddedLocked(service);
1074         }
1075     }
1076 
removeServiceLocked(RemotePrintService service)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 
failActivePrintJobsForService(final ComponentName serviceName)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 
failScheduledPrintJobsForServiceInternal(ComponentName serviceName)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 
throwIfDestroyedLocked()1120     private void throwIfDestroyedLocked() {
1121         if (mDestroyed) {
1122             throw new IllegalStateException("Cannot interact with a destroyed instance.");
1123         }
1124     }
1125 
handleDispatchPrintJobStateChanged( PrintJobId printJobId, IntSupplier appIdSupplier)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 
handleDispatchPrintServicesChanged()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 
handleDispatchPrintServiceRecommendationsUpdated( @ullable List<RecommendationInfo> recommendations)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 
onConfigurationChanged()1193     private void onConfigurationChanged() {
1194         synchronized (mLock) {
1195             onConfigurationChangedLocked();
1196         }
1197     }
1198 
getBindInstantServiceAllowed()1199     public boolean getBindInstantServiceAllowed() {
1200         return mIsInstantServiceAllowed;
1201     }
1202 
setBindInstantServiceAllowed(boolean allowed)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 
PrintJobStateChangeListenerRecord(@onNull IPrintJobStateChangeListener listener, int appId)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 
destroy()1222         public void destroy() {
1223             listener.asBinder().unlinkToDeath(this, 0);
1224         }
1225 
1226         @Override
binderDied()1227         public void binderDied() {
1228             listener.asBinder().unlinkToDeath(this, 0);
1229             onBinderDied();
1230         }
1231 
onBinderDied()1232         public abstract void onBinderDied();
1233     }
1234 
1235     private abstract class ListenerRecord<T extends IInterface> implements DeathRecipient {
1236         @NonNull final T listener;
1237 
ListenerRecord(@onNull T listener)1238         public ListenerRecord(@NonNull T listener) throws RemoteException {
1239             this.listener = listener;
1240             listener.asBinder().linkToDeath(this, 0);
1241         }
1242 
destroy()1243         public void destroy() {
1244             listener.asBinder().unlinkToDeath(this, 0);
1245         }
1246 
1247         @Override
binderDied()1248         public void binderDied() {
1249             listener.asBinder().unlinkToDeath(this, 0);
1250             onBinderDied();
1251         }
1252 
onBinderDied()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 
PrinterDiscoverySessionMediator()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 
addObserverLocked(@onNull IPrinterDiscoveryObserver observer)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 
removeObserverLocked(@onNull IPrinterDiscoveryObserver observer)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 
startPrinterDiscoveryLocked(@onNull IPrinterDiscoveryObserver observer, @Nullable List<PrinterId> priorityList)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 
stopPrinterDiscoveryLocked(@onNull IPrinterDiscoveryObserver observer)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 
validatePrintersLocked(@onNull List<PrinterId> printerIds)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 
startPrinterStateTrackingLocked(@onNull PrinterId printerId)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 
stopPrinterStateTrackingLocked(PrinterId printerId)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 
onDestroyed()1436         public void onDestroyed() {
1437             /* do nothing */
1438         }
1439 
destroyLocked()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 
onPrintersAddedLocked(List<PrinterInfo> printers)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 
onPrintersRemovedLocked(List<PrinterId> printerIds)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 
onServiceRemovedLocked(RemotePrintService service)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          */
onCustomPrinterIconLoadedLocked(PrinterId printerId)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 
onServiceDiedLocked(RemotePrintService service)1560         public void onServiceDiedLocked(RemotePrintService service) {
1561             removeServiceLocked(service);
1562         }
1563 
onServiceAddedLocked(RemotePrintService service)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 
dumpLocked(@onNull DualDumpOutputStream dumpStream)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 
removePrintersForServiceLocked(ComponentName serviceName)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 
handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters)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 
handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds)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 
handleDispatchCreatePrinterDiscoverySession( List<RemotePrintService> services)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 
handleDispatchDestroyPrinterDiscoverySession( List<RemotePrintService> services)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 
handleDispatchStartPrinterDiscovery( List<RemotePrintService> services, List<PrinterId> printerIds)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 
handleDispatchStopPrinterDiscovery(List<RemotePrintService> services)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 
handleValidatePrinters(RemotePrintService service, List<PrinterId> printerIds)1707         private void handleValidatePrinters(RemotePrintService service,
1708                 List<PrinterId> printerIds) {
1709             service.validatePrinters(printerIds);
1710         }
1711 
handleStartPrinterStateTracking(@onNull RemotePrintService service, @NonNull PrinterId printerId)1712         private void handleStartPrinterStateTracking(@NonNull RemotePrintService service,
1713                 @NonNull PrinterId printerId) {
1714             service.startPrinterStateTracking(printerId);
1715         }
1716 
handleStopPrinterStateTracking(RemotePrintService service, PrinterId printerId)1717         private void handleStopPrinterStateTracking(RemotePrintService service,
1718                 PrinterId printerId) {
1719             service.stopPrinterStateTracking(printerId);
1720         }
1721 
handlePrintersAdded(IPrinterDiscoveryObserver observer, List<PrinterInfo> printers)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 
handlePrintersRemoved(IPrinterDiscoveryObserver observer, List<PrinterId> printerIds)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 
onPrintJobCreated(final IBinder creator, final int appId, PrintJobInfo printJob)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 
onPrintJobStateChanged(PrintJobInfo printJob)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 
getPrintJob(PrintJobId printJobId, int appId)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 
getPrintJobs(int appId)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 
dumpLocked(@onNull DualDumpOutputStream dumpStream)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