• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.UserIdInt;
34 import android.app.PendingIntent;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentSender;
39 import android.content.pm.ParceledListSlice;
40 import android.content.pm.ResolveInfo;
41 import android.graphics.drawable.Icon;
42 import android.net.Uri;
43 import android.os.Binder;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.IBinder;
47 import android.os.IBinder.DeathRecipient;
48 import android.os.IInterface;
49 import android.os.Looper;
50 import android.os.RemoteCallbackList;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.print.IPrintDocumentAdapter;
54 import android.print.IPrintJobStateChangeListener;
55 import android.print.IPrintServicesChangeListener;
56 import android.print.IPrinterDiscoveryObserver;
57 import android.print.PrintAttributes;
58 import android.print.PrintJobId;
59 import android.print.PrintJobInfo;
60 import android.print.PrintManager;
61 import android.print.PrinterId;
62 import android.print.PrinterInfo;
63 import android.printservice.PrintServiceInfo;
64 import android.printservice.recommendation.IRecommendationsChangeListener;
65 import android.printservice.recommendation.RecommendationInfo;
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 @UserIdInt 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 
isPrintServiceEnabled(@onNull ComponentName serviceName)440     public boolean isPrintServiceEnabled(@NonNull ComponentName serviceName) {
441         synchronized (mLock) {
442             if (mDisabledServices.contains(serviceName)) {
443                 return false;
444             }
445             return true;
446         }
447     }
448 
449     /**
450      * @return The currently known print service recommendations
451      */
getPrintServiceRecommendations()452     public @Nullable List<RecommendationInfo> getPrintServiceRecommendations() {
453         return mPrintServiceRecommendations;
454     }
455 
createPrinterDiscoverySession(@onNull IPrinterDiscoveryObserver observer)456     public void createPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) {
457         mSpooler.clearCustomPrinterIconCache();
458 
459         synchronized (mLock) {
460             throwIfDestroyedLocked();
461 
462             if (mPrinterDiscoverySession == null) {
463                 // If we do not have a session, tell all service to create one.
464                 mPrinterDiscoverySession = new PrinterDiscoverySessionMediator() {
465                     @Override
466                     public void onDestroyed() {
467                         mPrinterDiscoverySession = null;
468                     }
469                 };
470                 // Add the observer to the brand new session.
471                 mPrinterDiscoverySession.addObserverLocked(observer);
472             } else {
473                 // If services have created session, just add the observer.
474                 mPrinterDiscoverySession.addObserverLocked(observer);
475             }
476         }
477     }
478 
destroyPrinterDiscoverySession(@onNull IPrinterDiscoveryObserver observer)479     public void destroyPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) {
480         synchronized (mLock) {
481             // Already destroyed - nothing to do.
482             if (mPrinterDiscoverySession == null) {
483                 return;
484             }
485             // Remove this observer.
486             mPrinterDiscoverySession.removeObserverLocked(observer);
487         }
488     }
489 
startPrinterDiscovery(@onNull IPrinterDiscoveryObserver observer, @Nullable List<PrinterId> printerIds)490     public void startPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer,
491             @Nullable List<PrinterId> printerIds) {
492         synchronized (mLock) {
493             throwIfDestroyedLocked();
494 
495             // No session - nothing to do.
496             if (mPrinterDiscoverySession == null) {
497                 return;
498             }
499             // Kick of discovery.
500             mPrinterDiscoverySession.startPrinterDiscoveryLocked(observer,
501                     printerIds);
502         }
503     }
504 
stopPrinterDiscovery(@onNull IPrinterDiscoveryObserver observer)505     public void stopPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer) {
506         synchronized (mLock) {
507             throwIfDestroyedLocked();
508 
509             // No session - nothing to do.
510             if (mPrinterDiscoverySession == null) {
511                 return;
512             }
513             // Kick of discovery.
514             mPrinterDiscoverySession.stopPrinterDiscoveryLocked(observer);
515         }
516     }
517 
validatePrinters(@onNull List<PrinterId> printerIds)518     public void validatePrinters(@NonNull List<PrinterId> printerIds) {
519         synchronized (mLock) {
520             throwIfDestroyedLocked();
521             // No services - nothing to do.
522             if (mActiveServices.isEmpty()) {
523                 return;
524             }
525             // No session - nothing to do.
526             if (mPrinterDiscoverySession == null) {
527                 return;
528             }
529             // Request an updated.
530             mPrinterDiscoverySession.validatePrintersLocked(printerIds);
531         }
532     }
533 
startPrinterStateTracking(@onNull PrinterId printerId)534     public void startPrinterStateTracking(@NonNull PrinterId printerId) {
535         synchronized (mLock) {
536             throwIfDestroyedLocked();
537             // No services - nothing to do.
538             if (mActiveServices.isEmpty()) {
539                 return;
540             }
541             // No session - nothing to do.
542             if (mPrinterDiscoverySession == null) {
543                 return;
544             }
545             // Request start tracking the printer.
546             mPrinterDiscoverySession.startPrinterStateTrackingLocked(printerId);
547         }
548     }
549 
stopPrinterStateTracking(PrinterId printerId)550     public void stopPrinterStateTracking(PrinterId printerId) {
551         synchronized (mLock) {
552             throwIfDestroyedLocked();
553             // No services - nothing to do.
554             if (mActiveServices.isEmpty()) {
555                 return;
556             }
557             // No session - nothing to do.
558             if (mPrinterDiscoverySession == null) {
559                 return;
560             }
561             // Request stop tracking the printer.
562             mPrinterDiscoverySession.stopPrinterStateTrackingLocked(printerId);
563         }
564     }
565 
addPrintJobStateChangeListener(@onNull IPrintJobStateChangeListener listener, int appId)566     public void addPrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener,
567             int appId) throws RemoteException {
568         synchronized (mLock) {
569             throwIfDestroyedLocked();
570             if (mPrintJobStateChangeListenerRecords == null) {
571                 mPrintJobStateChangeListenerRecords =
572                         new ArrayList<PrintJobStateChangeListenerRecord>();
573             }
574             mPrintJobStateChangeListenerRecords.add(
575                     new PrintJobStateChangeListenerRecord(listener, appId) {
576                 @Override
577                 public void onBinderDied() {
578                     synchronized (mLock) {
579                         if (mPrintJobStateChangeListenerRecords != null) {
580                             mPrintJobStateChangeListenerRecords.remove(this);
581                         }
582                     }
583                 }
584             });
585         }
586     }
587 
removePrintJobStateChangeListener(@onNull IPrintJobStateChangeListener listener)588     public void removePrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener) {
589         synchronized (mLock) {
590             throwIfDestroyedLocked();
591             if (mPrintJobStateChangeListenerRecords == null) {
592                 return;
593             }
594             final int recordCount = mPrintJobStateChangeListenerRecords.size();
595             for (int i = 0; i < recordCount; i++) {
596                 PrintJobStateChangeListenerRecord record =
597                         mPrintJobStateChangeListenerRecords.get(i);
598                 if (record.listener.asBinder().equals(listener.asBinder())) {
599                     record.destroy();
600                     mPrintJobStateChangeListenerRecords.remove(i);
601                     break;
602                 }
603             }
604             if (mPrintJobStateChangeListenerRecords.isEmpty()) {
605                 mPrintJobStateChangeListenerRecords = null;
606             }
607         }
608     }
609 
addPrintServicesChangeListener(@onNull IPrintServicesChangeListener listener)610     public void addPrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener)
611             throws RemoteException {
612         synchronized (mLock) {
613             throwIfDestroyedLocked();
614             if (mPrintServicesChangeListenerRecords == null) {
615                 mPrintServicesChangeListenerRecords = new ArrayList<>();
616             }
617             mPrintServicesChangeListenerRecords.add(
618                     new ListenerRecord<IPrintServicesChangeListener>(listener) {
619                         @Override
620                         public void onBinderDied() {
621                             synchronized (mLock) {
622                                 if (mPrintServicesChangeListenerRecords != null) {
623                                     mPrintServicesChangeListenerRecords.remove(this);
624                                 }
625                             }
626                         }
627                     });
628         }
629     }
630 
removePrintServicesChangeListener(@onNull IPrintServicesChangeListener listener)631     public void removePrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) {
632         synchronized (mLock) {
633             throwIfDestroyedLocked();
634             if (mPrintServicesChangeListenerRecords == null) {
635                 return;
636             }
637             final int recordCount = mPrintServicesChangeListenerRecords.size();
638             for (int i = 0; i < recordCount; i++) {
639                 ListenerRecord<IPrintServicesChangeListener> record =
640                         mPrintServicesChangeListenerRecords.get(i);
641                 if (record.listener.asBinder().equals(listener.asBinder())) {
642                     record.destroy();
643                     mPrintServicesChangeListenerRecords.remove(i);
644                     break;
645                 }
646             }
647             if (mPrintServicesChangeListenerRecords.isEmpty()) {
648                 mPrintServicesChangeListenerRecords = null;
649             }
650         }
651     }
652 
addPrintServiceRecommendationsChangeListener( @onNull IRecommendationsChangeListener listener)653     public void addPrintServiceRecommendationsChangeListener(
654             @NonNull IRecommendationsChangeListener listener) throws RemoteException {
655         synchronized (mLock) {
656             throwIfDestroyedLocked();
657             if (mPrintServiceRecommendationsChangeListenerRecords == null) {
658                 mPrintServiceRecommendationsChangeListenerRecords = new ArrayList<>();
659 
660                 mPrintServiceRecommendationsService =
661                         new RemotePrintServiceRecommendationService(mContext,
662                                 UserHandle.of(mUserId), this);
663             }
664             mPrintServiceRecommendationsChangeListenerRecords.add(
665                     new ListenerRecord<IRecommendationsChangeListener>(listener) {
666                         @Override
667                         public void onBinderDied() {
668                             synchronized (mLock) {
669                                 if (mPrintServiceRecommendationsChangeListenerRecords != null) {
670                                     mPrintServiceRecommendationsChangeListenerRecords.remove(this);
671                                 }
672                             }
673                         }
674                     });
675         }
676     }
677 
removePrintServiceRecommendationsChangeListener( @onNull IRecommendationsChangeListener listener)678     public void removePrintServiceRecommendationsChangeListener(
679             @NonNull IRecommendationsChangeListener listener) {
680         synchronized (mLock) {
681             throwIfDestroyedLocked();
682             if (mPrintServiceRecommendationsChangeListenerRecords == null) {
683                 return;
684             }
685             final int recordCount = mPrintServiceRecommendationsChangeListenerRecords.size();
686             for (int i = 0; i < recordCount; i++) {
687                 ListenerRecord<IRecommendationsChangeListener> record =
688                         mPrintServiceRecommendationsChangeListenerRecords.get(i);
689                 if (record.listener.asBinder().equals(listener.asBinder())) {
690                     record.destroy();
691                     mPrintServiceRecommendationsChangeListenerRecords.remove(i);
692                     break;
693                 }
694             }
695             if (mPrintServiceRecommendationsChangeListenerRecords.isEmpty()) {
696                 mPrintServiceRecommendationsChangeListenerRecords = null;
697 
698                 mPrintServiceRecommendations = null;
699 
700                 mPrintServiceRecommendationsService.close();
701                 mPrintServiceRecommendationsService = null;
702             }
703         }
704     }
705 
706     @Override
onPrintJobStateChanged(PrintJobInfo printJob)707     public void onPrintJobStateChanged(PrintJobInfo printJob) {
708         mPrintJobForAppCache.onPrintJobStateChanged(printJob);
709         Handler.getMain().sendMessage(obtainMessage(
710                 UserState::handleDispatchPrintJobStateChanged,
711                 this, printJob.getId(),
712                 PooledLambda.obtainSupplier(printJob.getAppId()).recycleOnUse()));
713     }
714 
onPrintServicesChanged()715     public void onPrintServicesChanged() {
716         Handler.getMain().sendMessage(obtainMessage(
717                 UserState::handleDispatchPrintServicesChanged, this));
718     }
719 
720     @Override
onPrintServiceRecommendationsUpdated(List<RecommendationInfo> recommendations)721     public void onPrintServiceRecommendationsUpdated(List<RecommendationInfo> recommendations) {
722         Handler.getMain().sendMessage(obtainMessage(
723                 UserState::handleDispatchPrintServiceRecommendationsUpdated,
724                 this, recommendations));
725     }
726 
727     @Override
onPrintersAdded(List<PrinterInfo> printers)728     public void onPrintersAdded(List<PrinterInfo> printers) {
729         synchronized (mLock) {
730             throwIfDestroyedLocked();
731             // No services - nothing to do.
732             if (mActiveServices.isEmpty()) {
733                 return;
734             }
735             // No session - nothing to do.
736             if (mPrinterDiscoverySession == null) {
737                 return;
738             }
739             mPrinterDiscoverySession.onPrintersAddedLocked(printers);
740         }
741     }
742 
743     @Override
onPrintersRemoved(List<PrinterId> printerIds)744     public void onPrintersRemoved(List<PrinterId> printerIds) {
745         synchronized (mLock) {
746             throwIfDestroyedLocked();
747             // No services - nothing to do.
748             if (mActiveServices.isEmpty()) {
749                 return;
750             }
751             // No session - nothing to do.
752             if (mPrinterDiscoverySession == null) {
753                 return;
754             }
755             mPrinterDiscoverySession.onPrintersRemovedLocked(printerIds);
756         }
757     }
758 
759     @Override
onCustomPrinterIconLoaded(PrinterId printerId, Icon icon)760     public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
761         mSpooler.onCustomPrinterIconLoaded(printerId, icon);
762 
763         synchronized (mLock) {
764             throwIfDestroyedLocked();
765 
766             // No session - nothing to do.
767             if (mPrinterDiscoverySession == null) {
768                 return;
769             }
770             mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId);
771         }
772     }
773 
774     @Override
onServiceDied(RemotePrintService service)775     public void onServiceDied(RemotePrintService service) {
776         synchronized (mLock) {
777             throwIfDestroyedLocked();
778             // No services - nothing to do.
779             if (mActiveServices.isEmpty()) {
780                 return;
781             }
782             // Fail all print jobs.
783             failActivePrintJobsForService(service.getComponentName());
784             service.onAllPrintJobsHandled();
785 
786             mActiveServices.remove(service.getComponentName());
787 
788             // The service might need to be restarted if it died because of an update
789             Handler.getMain().sendMessageDelayed(obtainMessage(
790                     UserState::onConfigurationChanged, this),
791                     SERVICE_RESTART_DELAY_MILLIS);
792 
793             // No session - nothing to do.
794             if (mPrinterDiscoverySession == null) {
795                 return;
796             }
797             mPrinterDiscoverySession.onServiceDiedLocked(service);
798         }
799     }
800 
updateIfNeededLocked()801     public void updateIfNeededLocked() {
802         throwIfDestroyedLocked();
803         readConfigurationLocked();
804         onConfigurationChangedLocked();
805     }
806 
destroyLocked()807     public void destroyLocked() {
808         throwIfDestroyedLocked();
809         mSpooler.destroy();
810         for (RemotePrintService service : mActiveServices.values()) {
811             service.destroy();
812         }
813         mActiveServices.clear();
814         mInstalledServices.clear();
815         mDisabledServices.clear();
816         if (mPrinterDiscoverySession != null) {
817             mPrinterDiscoverySession.destroyLocked();
818             mPrinterDiscoverySession = null;
819         }
820         mDestroyed = true;
821     }
822 
dump(@onNull DualDumpOutputStream dumpStream)823     public void dump(@NonNull DualDumpOutputStream dumpStream) {
824         synchronized (mLock) {
825             dumpStream.write("user_id", PrintUserStateProto.USER_ID, mUserId);
826 
827             final int installedServiceCount = mInstalledServices.size();
828             for (int i = 0; i < installedServiceCount; i++) {
829                 long token = dumpStream.start("installed_services",
830                         PrintUserStateProto.INSTALLED_SERVICES);
831                 PrintServiceInfo installedService = mInstalledServices.get(i);
832 
833                 ResolveInfo resolveInfo = installedService.getResolveInfo();
834                 writeComponentName(dumpStream, "component_name",
835                         InstalledPrintServiceProto.COMPONENT_NAME,
836                         new ComponentName(resolveInfo.serviceInfo.packageName,
837                                 resolveInfo.serviceInfo.name));
838 
839                 writeStringIfNotNull(dumpStream, "settings_activity",
840                         InstalledPrintServiceProto.SETTINGS_ACTIVITY,
841                         installedService.getSettingsActivityName());
842                 writeStringIfNotNull(dumpStream, "add_printers_activity",
843                         InstalledPrintServiceProto.ADD_PRINTERS_ACTIVITY,
844                         installedService.getAddPrintersActivityName());
845                 writeStringIfNotNull(dumpStream, "advanced_options_activity",
846                         InstalledPrintServiceProto.ADVANCED_OPTIONS_ACTIVITY,
847                         installedService.getAdvancedOptionsActivityName());
848 
849                 dumpStream.end(token);
850             }
851 
852             for (ComponentName disabledService : mDisabledServices) {
853                 writeComponentName(dumpStream, "disabled_services",
854                         PrintUserStateProto.DISABLED_SERVICES, disabledService);
855             }
856 
857             final int activeServiceCount = mActiveServices.size();
858             for (int i = 0; i < activeServiceCount; i++) {
859                 long token = dumpStream.start("actives_services",
860                         PrintUserStateProto.ACTIVE_SERVICES);
861                 mActiveServices.valueAt(i).dump(dumpStream);
862                 dumpStream.end(token);
863             }
864 
865             mPrintJobForAppCache.dumpLocked(dumpStream);
866 
867             if (mPrinterDiscoverySession != null) {
868                 long token = dumpStream.start("discovery_service",
869                         PrintUserStateProto.DISCOVERY_SESSIONS);
870                 mPrinterDiscoverySession.dumpLocked(dumpStream);
871                 dumpStream.end(token);
872             }
873 
874         }
875 
876         long token = dumpStream.start("print_spooler_state",
877                 PrintUserStateProto.PRINT_SPOOLER_STATE);
878         mSpooler.dump(dumpStream);
879         dumpStream.end(token);
880     }
881 
readConfigurationLocked()882     private void readConfigurationLocked() {
883         readInstalledPrintServicesLocked();
884         readDisabledPrintServicesLocked();
885     }
886 
readInstalledPrintServicesLocked()887     private void readInstalledPrintServicesLocked() {
888         Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
889 
890         int queryIntentFlags = GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING;
891 
892         if (mIsInstantServiceAllowed) {
893             queryIntentFlags |= MATCH_INSTANT;
894         }
895 
896         List<ResolveInfo> installedServices = mContext.getPackageManager()
897                 .queryIntentServicesAsUser(mQueryIntent, queryIntentFlags, mUserId);
898 
899         final int installedCount = installedServices.size();
900         for (int i = 0, count = installedCount; i < count; i++) {
901             ResolveInfo installedService = installedServices.get(i);
902             if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals(
903                     installedService.serviceInfo.permission)) {
904                 ComponentName serviceName = new ComponentName(
905                         installedService.serviceInfo.packageName,
906                         installedService.serviceInfo.name);
907                 Slog.w(LOG_TAG, "Skipping print service "
908                         + serviceName.flattenToShortString()
909                         + " since it does not require permission "
910                         + android.Manifest.permission.BIND_PRINT_SERVICE);
911                 continue;
912             }
913             tempPrintServices.add(PrintServiceInfo.create(mContext, installedService));
914         }
915 
916         mInstalledServices.clear();
917         mInstalledServices.addAll(tempPrintServices);
918     }
919 
920     /**
921      * Update persistent state from a previous version of Android.
922      */
upgradePersistentStateIfNeeded()923     private void upgradePersistentStateIfNeeded() {
924         String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
925                 Settings.Secure.ENABLED_PRINT_SERVICES, mUserId);
926 
927         // Pre N we store the enabled services, in N and later we store the disabled services.
928         // Hence if enabledSettingValue is still set, we need to upgrade.
929         if (enabledSettingValue != null) {
930             Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>();
931             readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
932                     enabledServiceNameSet);
933 
934             ArraySet<ComponentName> disabledServices = new ArraySet<>();
935             final int numInstalledServices = mInstalledServices.size();
936             for (int i = 0; i < numInstalledServices; i++) {
937                 ComponentName serviceName = mInstalledServices.get(i).getComponentName();
938                 if (!enabledServiceNameSet.contains(serviceName)) {
939                     disabledServices.add(serviceName);
940                 }
941             }
942 
943             writeDisabledPrintServicesLocked(disabledServices);
944 
945             // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run
946             // again.
947             Settings.Secure.putStringForUser(mContext.getContentResolver(),
948                     Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId);
949         }
950     }
951 
952     /**
953      * Read the set of disabled print services from the secure settings.
954      *
955      * @return true if the state changed.
956      */
readDisabledPrintServicesLocked()957     private void readDisabledPrintServicesLocked() {
958         Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>();
959         readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES,
960                 tempDisabledServiceNameSet);
961         if (!tempDisabledServiceNameSet.equals(mDisabledServices)) {
962             mDisabledServices.clear();
963             mDisabledServices.addAll(tempDisabledServiceNameSet);
964         }
965     }
966 
readPrintServicesFromSettingLocked(String setting, Set<ComponentName> outServiceNames)967     private void readPrintServicesFromSettingLocked(String setting,
968             Set<ComponentName> outServiceNames) {
969         String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
970                 setting, mUserId);
971         if (!TextUtils.isEmpty(settingValue)) {
972             TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
973             splitter.setString(settingValue);
974             while (splitter.hasNext()) {
975                 String string = splitter.next();
976                 if (TextUtils.isEmpty(string)) {
977                     continue;
978                 }
979                 ComponentName componentName = ComponentName.unflattenFromString(string);
980                 if (componentName != null) {
981                     outServiceNames.add(componentName);
982                 }
983             }
984         }
985     }
986 
987     /**
988      * Persist the disabled print services to the secure settings.
989      */
writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices)990     private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) {
991         StringBuilder builder = new StringBuilder();
992         for (ComponentName componentName : disabledServices) {
993             if (builder.length() > 0) {
994                 builder.append(COMPONENT_NAME_SEPARATOR);
995             }
996             builder.append(componentName.flattenToShortString());
997         }
998         Settings.Secure.putStringForUser(mContext.getContentResolver(),
999                 Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId);
1000     }
1001 
1002     /**
1003      * Get the {@link ComponentName names} of the installed print services
1004      *
1005      * @return The names of the installed print services
1006      */
getInstalledComponents()1007     private ArrayList<ComponentName> getInstalledComponents() {
1008         ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>();
1009 
1010         final int installedCount = mInstalledServices.size();
1011         for (int i = 0; i < installedCount; i++) {
1012             ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo();
1013             ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName,
1014                     resolveInfo.serviceInfo.name);
1015 
1016             installedComponents.add(serviceName);
1017         }
1018 
1019         return installedComponents;
1020     }
1021 
1022     /**
1023      * Prune persistent state if a print service was uninstalled
1024      */
prunePrintServices()1025     public void prunePrintServices() {
1026         ArrayList<ComponentName> installedComponents;
1027 
1028         synchronized (mLock) {
1029             installedComponents = getInstalledComponents();
1030 
1031             // Remove unnecessary entries from persistent state "disabled services"
1032             boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents);
1033             if (disabledServicesUninstalled) {
1034                 writeDisabledPrintServicesLocked(mDisabledServices);
1035             }
1036         }
1037 
1038         // Remove unnecessary entries from persistent state "approved services"
1039         mSpooler.pruneApprovedPrintServices(installedComponents);
1040 
1041     }
1042 
onConfigurationChangedLocked()1043     private void onConfigurationChangedLocked() {
1044         ArrayList<ComponentName> installedComponents = getInstalledComponents();
1045 
1046         final int installedCount = installedComponents.size();
1047         for (int i = 0; i < installedCount; i++) {
1048             ComponentName serviceName = installedComponents.get(i);
1049 
1050             if (!mDisabledServices.contains(serviceName)) {
1051                 if (!mActiveServices.containsKey(serviceName)) {
1052                     RemotePrintService service = new RemotePrintService(
1053                             mContext, serviceName, mUserId, mSpooler, this);
1054                     addServiceLocked(service);
1055                 }
1056             } else {
1057                 RemotePrintService service = mActiveServices.remove(serviceName);
1058                 if (service != null) {
1059                     removeServiceLocked(service);
1060                 }
1061             }
1062         }
1063 
1064         Iterator<Map.Entry<ComponentName, RemotePrintService>> iterator =
1065                 mActiveServices.entrySet().iterator();
1066         while (iterator.hasNext()) {
1067             Map.Entry<ComponentName, RemotePrintService> entry = iterator.next();
1068             ComponentName serviceName = entry.getKey();
1069             RemotePrintService service = entry.getValue();
1070             if (!installedComponents.contains(serviceName)) {
1071                 removeServiceLocked(service);
1072                 iterator.remove();
1073             }
1074         }
1075 
1076         onPrintServicesChanged();
1077     }
1078 
addServiceLocked(RemotePrintService service)1079     private void addServiceLocked(RemotePrintService service) {
1080         mActiveServices.put(service.getComponentName(), service);
1081         if (mPrinterDiscoverySession != null) {
1082             mPrinterDiscoverySession.onServiceAddedLocked(service);
1083         }
1084     }
1085 
removeServiceLocked(RemotePrintService service)1086     private void removeServiceLocked(RemotePrintService service) {
1087         // Fail all print jobs.
1088         failActivePrintJobsForService(service.getComponentName());
1089         // If discovery is in progress, tear down the service.
1090         if (mPrinterDiscoverySession != null) {
1091             mPrinterDiscoverySession.onServiceRemovedLocked(service);
1092         } else {
1093             // Otherwise, just destroy it.
1094             service.destroy();
1095         }
1096     }
1097 
failActivePrintJobsForService(final ComponentName serviceName)1098     private void failActivePrintJobsForService(final ComponentName serviceName) {
1099         // Makes sure all active print jobs are failed since the service
1100         // just died. Do this off the main thread since we do to allow
1101         // calls into the spooler on the main thread.
1102         if (Looper.getMainLooper().isCurrentThread()) {
1103             BackgroundThread.getHandler().sendMessage(obtainMessage(
1104                     UserState::failScheduledPrintJobsForServiceInternal, this, serviceName));
1105         } else {
1106             failScheduledPrintJobsForServiceInternal(serviceName);
1107         }
1108     }
1109 
failScheduledPrintJobsForServiceInternal(ComponentName serviceName)1110     private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) {
1111         List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName,
1112                 PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
1113         if (printJobs == null) {
1114             return;
1115         }
1116         final long identity = Binder.clearCallingIdentity();
1117         try {
1118             final int printJobCount = printJobs.size();
1119             for (int i = 0; i < printJobCount; i++) {
1120                 PrintJobInfo printJob = printJobs.get(i);
1121                 mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
1122                         mContext.getString(R.string.reason_service_unavailable));
1123             }
1124         } finally {
1125             Binder.restoreCallingIdentity(identity);
1126         }
1127     }
1128 
throwIfDestroyedLocked()1129     private void throwIfDestroyedLocked() {
1130         if (mDestroyed) {
1131             throw new IllegalStateException("Cannot interact with a destroyed instance.");
1132         }
1133     }
1134 
handleDispatchPrintJobStateChanged( PrintJobId printJobId, IntSupplier appIdSupplier)1135     private void handleDispatchPrintJobStateChanged(
1136             PrintJobId printJobId, IntSupplier appIdSupplier) {
1137         int appId = appIdSupplier.getAsInt();
1138         final List<PrintJobStateChangeListenerRecord> records;
1139         synchronized (mLock) {
1140             if (mPrintJobStateChangeListenerRecords == null) {
1141                 return;
1142             }
1143             records = new ArrayList<>(mPrintJobStateChangeListenerRecords);
1144         }
1145         final int recordCount = records.size();
1146         for (int i = 0; i < recordCount; i++) {
1147             PrintJobStateChangeListenerRecord record = records.get(i);
1148             if (record.appId == PrintManager.APP_ID_ANY
1149                     || record.appId == appId) {
1150                 try {
1151                     record.listener.onPrintJobStateChanged(printJobId);
1152                 } catch (RemoteException re) {
1153                     Log.e(LOG_TAG, "Error notifying for print job state change", re);
1154                 }
1155             }
1156         }
1157     }
1158 
handleDispatchPrintServicesChanged()1159     private void handleDispatchPrintServicesChanged() {
1160         final List<ListenerRecord<IPrintServicesChangeListener>> records;
1161         synchronized (mLock) {
1162             if (mPrintServicesChangeListenerRecords == null) {
1163                 return;
1164             }
1165             records = new ArrayList<>(mPrintServicesChangeListenerRecords);
1166         }
1167         final int recordCount = records.size();
1168         for (int i = 0; i < recordCount; i++) {
1169             ListenerRecord<IPrintServicesChangeListener> record = records.get(i);
1170 
1171             try {
1172                 record.listener.onPrintServicesChanged();;
1173             } catch (RemoteException re) {
1174                 Log.e(LOG_TAG, "Error notifying for print services change", re);
1175             }
1176         }
1177     }
1178 
handleDispatchPrintServiceRecommendationsUpdated( @ullable List<RecommendationInfo> recommendations)1179     private void handleDispatchPrintServiceRecommendationsUpdated(
1180             @Nullable List<RecommendationInfo> recommendations) {
1181         final List<ListenerRecord<IRecommendationsChangeListener>> records;
1182         synchronized (mLock) {
1183             if (mPrintServiceRecommendationsChangeListenerRecords == null) {
1184                 return;
1185             }
1186             records = new ArrayList<>(mPrintServiceRecommendationsChangeListenerRecords);
1187 
1188             mPrintServiceRecommendations = recommendations;
1189         }
1190         final int recordCount = records.size();
1191         for (int i = 0; i < recordCount; i++) {
1192             ListenerRecord<IRecommendationsChangeListener> record = records.get(i);
1193 
1194             try {
1195                 record.listener.onRecommendationsChanged();
1196             } catch (RemoteException re) {
1197                 Log.e(LOG_TAG, "Error notifying for print service recommendations change", re);
1198             }
1199         }
1200     }
1201 
onConfigurationChanged()1202     private void onConfigurationChanged() {
1203         synchronized (mLock) {
1204             onConfigurationChangedLocked();
1205         }
1206     }
1207 
getBindInstantServiceAllowed()1208     public boolean getBindInstantServiceAllowed() {
1209         return mIsInstantServiceAllowed;
1210     }
1211 
setBindInstantServiceAllowed(boolean allowed)1212     public void setBindInstantServiceAllowed(boolean allowed) {
1213         synchronized (mLock) {
1214             mIsInstantServiceAllowed = allowed;
1215 
1216             updateIfNeededLocked();
1217         }
1218     }
1219 
1220     private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient {
1221         @NonNull final IPrintJobStateChangeListener listener;
1222         final int appId;
1223 
PrintJobStateChangeListenerRecord(@onNull IPrintJobStateChangeListener listener, int appId)1224         public PrintJobStateChangeListenerRecord(@NonNull IPrintJobStateChangeListener listener,
1225                 int appId) throws RemoteException {
1226             this.listener = listener;
1227             this.appId = appId;
1228             listener.asBinder().linkToDeath(this, 0);
1229         }
1230 
destroy()1231         public void destroy() {
1232             listener.asBinder().unlinkToDeath(this, 0);
1233         }
1234 
1235         @Override
binderDied()1236         public void binderDied() {
1237             listener.asBinder().unlinkToDeath(this, 0);
1238             onBinderDied();
1239         }
1240 
onBinderDied()1241         public abstract void onBinderDied();
1242     }
1243 
1244     private abstract class ListenerRecord<T extends IInterface> implements DeathRecipient {
1245         @NonNull final T listener;
1246 
ListenerRecord(@onNull T listener)1247         public ListenerRecord(@NonNull T listener) throws RemoteException {
1248             this.listener = listener;
1249             listener.asBinder().linkToDeath(this, 0);
1250         }
1251 
destroy()1252         public void destroy() {
1253             listener.asBinder().unlinkToDeath(this, 0);
1254         }
1255 
1256         @Override
binderDied()1257         public void binderDied() {
1258             listener.asBinder().unlinkToDeath(this, 0);
1259             onBinderDied();
1260         }
1261 
onBinderDied()1262         public abstract void onBinderDied();
1263     }
1264 
1265     private class PrinterDiscoverySessionMediator {
1266         private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
1267                 new ArrayMap<PrinterId, PrinterInfo>();
1268 
1269         private final RemoteCallbackList<IPrinterDiscoveryObserver> mDiscoveryObservers =
1270                 new RemoteCallbackList<IPrinterDiscoveryObserver>() {
1271             @Override
1272             public void onCallbackDied(IPrinterDiscoveryObserver observer) {
1273                 synchronized (mLock) {
1274                     stopPrinterDiscoveryLocked(observer);
1275                     removeObserverLocked(observer);
1276                 }
1277             }
1278         };
1279 
1280         private final List<IBinder> mStartedPrinterDiscoveryTokens = new ArrayList<IBinder>();
1281 
1282         private final List<PrinterId> mStateTrackedPrinters = new ArrayList<PrinterId>();
1283 
1284         private boolean mIsDestroyed;
1285 
PrinterDiscoverySessionMediator()1286         PrinterDiscoverySessionMediator() {
1287             // Kick off the session creation.
1288             Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator::
1289                     handleDispatchCreatePrinterDiscoverySession,
1290                     this, new ArrayList<>(mActiveServices.values())));
1291         }
1292 
addObserverLocked(@onNull IPrinterDiscoveryObserver observer)1293         public void addObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
1294             // Add the observer.
1295             mDiscoveryObservers.register(observer);
1296 
1297             // Bring the added observer up to speed with the printers.
1298             if (!mPrinters.isEmpty()) {
1299                 Handler.getMain().sendMessage(obtainMessage(
1300                         UserState.PrinterDiscoverySessionMediator::handlePrintersAdded,
1301                         this, observer, new ArrayList<>(mPrinters.values())));
1302             }
1303         }
1304 
removeObserverLocked(@onNull IPrinterDiscoveryObserver observer)1305         public void removeObserverLocked(@NonNull IPrinterDiscoveryObserver observer) {
1306             // Remove the observer.
1307             mDiscoveryObservers.unregister(observer);
1308             // No one else observing - then kill it.
1309             if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) {
1310                 destroyLocked();
1311             }
1312         }
1313 
startPrinterDiscoveryLocked(@onNull IPrinterDiscoveryObserver observer, @Nullable List<PrinterId> priorityList)1314         public final void startPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer,
1315                 @Nullable List<PrinterId> priorityList) {
1316             if (mIsDestroyed) {
1317                 Log.w(LOG_TAG, "Not starting dicovery - session destroyed");
1318                 return;
1319             }
1320 
1321             final boolean discoveryStarted = !mStartedPrinterDiscoveryTokens.isEmpty();
1322 
1323             // Remember we got a start request to match with an end.
1324             mStartedPrinterDiscoveryTokens.add(observer.asBinder());
1325 
1326             // If printer discovery is ongoing and the start request has a list
1327             // of printer to be checked, then we just request validating them.
1328             if (discoveryStarted && priorityList != null && !priorityList.isEmpty()) {
1329                 validatePrinters(priorityList);
1330                 return;
1331             }
1332 
1333             // The service are already performing discovery - nothing to do.
1334             if (mStartedPrinterDiscoveryTokens.size() > 1) {
1335                 return;
1336             }
1337 
1338             Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator::
1339                     handleDispatchStartPrinterDiscovery, this,
1340                     new ArrayList<>(mActiveServices.values()), priorityList));
1341         }
1342 
stopPrinterDiscoveryLocked(@onNull IPrinterDiscoveryObserver observer)1343         public final void stopPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer) {
1344             if (mIsDestroyed) {
1345                 Log.w(LOG_TAG, "Not stopping dicovery - session destroyed");
1346                 return;
1347             }
1348             // This one did not make an active discovery request - nothing to do.
1349             if (!mStartedPrinterDiscoveryTokens.remove(observer.asBinder())) {
1350                 return;
1351             }
1352             // There are other interested observers - do not stop discovery.
1353             if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
1354                 return;
1355             }
1356             Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator::
1357                     handleDispatchStopPrinterDiscovery,
1358                     this, new ArrayList<>(mActiveServices.values())));
1359         }
1360 
validatePrintersLocked(@onNull List<PrinterId> printerIds)1361         public void validatePrintersLocked(@NonNull List<PrinterId> printerIds) {
1362             if (mIsDestroyed) {
1363                 Log.w(LOG_TAG, "Not validating pritners - session destroyed");
1364                 return;
1365             }
1366 
1367             List<PrinterId> remainingList = new ArrayList<PrinterId>(printerIds);
1368             while (!remainingList.isEmpty()) {
1369                 Iterator<PrinterId> iterator = remainingList.iterator();
1370                 // Gather the printers per service and request a validation.
1371                 List<PrinterId> updateList = new ArrayList<PrinterId>();
1372                 ComponentName serviceName = null;
1373                 while (iterator.hasNext()) {
1374                     PrinterId printerId = iterator.next();
1375                     if (printerId != null) {
1376                         if (updateList.isEmpty()) {
1377                             updateList.add(printerId);
1378                             serviceName = printerId.getServiceName();
1379                             iterator.remove();
1380                         } else if (printerId.getServiceName().equals(serviceName)) {
1381                             updateList.add(printerId);
1382                             iterator.remove();
1383                         }
1384                     }
1385                 }
1386                 // Schedule a notification of the service.
1387                 RemotePrintService service = mActiveServices.get(serviceName);
1388                 if (service != null) {
1389                     Handler.getMain().sendMessage(obtainMessage(
1390                             UserState.PrinterDiscoverySessionMediator::handleValidatePrinters,
1391                             this, service, updateList));
1392                 }
1393             }
1394         }
1395 
startPrinterStateTrackingLocked(@onNull PrinterId printerId)1396         public final void startPrinterStateTrackingLocked(@NonNull PrinterId printerId) {
1397             if (mIsDestroyed) {
1398                 Log.w(LOG_TAG, "Not starting printer state tracking - session destroyed");
1399                 return;
1400             }
1401             // If printer discovery is not started - nothing to do.
1402             if (mStartedPrinterDiscoveryTokens.isEmpty()) {
1403                 return;
1404             }
1405             final boolean containedPrinterId = mStateTrackedPrinters.contains(printerId);
1406             // Keep track of the number of requests to track this one.
1407             mStateTrackedPrinters.add(printerId);
1408             // If we were tracking this printer - nothing to do.
1409             if (containedPrinterId) {
1410                 return;
1411             }
1412             // No service - nothing to do.
1413             RemotePrintService service = mActiveServices.get(printerId.getServiceName());
1414             if (service == null) {
1415                 return;
1416             }
1417             // Ask the service to start tracking.
1418             Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator::
1419                     handleStartPrinterStateTracking, this, service, printerId));
1420         }
1421 
stopPrinterStateTrackingLocked(PrinterId printerId)1422         public final void stopPrinterStateTrackingLocked(PrinterId printerId) {
1423             if (mIsDestroyed) {
1424                 Log.w(LOG_TAG, "Not stopping printer state tracking - session destroyed");
1425                 return;
1426             }
1427             // If printer discovery is not started - nothing to do.
1428             if (mStartedPrinterDiscoveryTokens.isEmpty()) {
1429                 return;
1430             }
1431             // If we did not track this printer - nothing to do.
1432             if (!mStateTrackedPrinters.remove(printerId)) {
1433                 return;
1434             }
1435             // No service - nothing to do.
1436             RemotePrintService service = mActiveServices.get(printerId.getServiceName());
1437             if (service == null) {
1438                 return;
1439             }
1440             // Ask the service to start tracking.
1441             Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator::
1442                     handleStopPrinterStateTracking, this, service, printerId));
1443         }
1444 
onDestroyed()1445         public void onDestroyed() {
1446             /* do nothing */
1447         }
1448 
destroyLocked()1449         public void destroyLocked() {
1450             if (mIsDestroyed) {
1451                 Log.w(LOG_TAG, "Not destroying - session destroyed");
1452                 return;
1453             }
1454             mIsDestroyed = true;
1455             // Make sure printer tracking is stopped.
1456             final int printerCount = mStateTrackedPrinters.size();
1457             for (int i = 0; i < printerCount; i++) {
1458                 PrinterId printerId = mStateTrackedPrinters.get(i);
1459                 stopPrinterStateTracking(printerId);
1460             }
1461             // Make sure discovery is stopped.
1462             final int observerCount = mStartedPrinterDiscoveryTokens.size();
1463             for (int i = 0; i < observerCount; i++) {
1464                 IBinder token = mStartedPrinterDiscoveryTokens.get(i);
1465                 stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver.Stub.asInterface(token));
1466             }
1467             // Tell the services we are done.
1468             Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator::
1469                     handleDispatchDestroyPrinterDiscoverySession,
1470                     this, new ArrayList<>(mActiveServices.values())));
1471         }
1472 
onPrintersAddedLocked(List<PrinterInfo> printers)1473         public void onPrintersAddedLocked(List<PrinterInfo> printers) {
1474             if (DEBUG) {
1475                 Log.i(LOG_TAG, "onPrintersAddedLocked()");
1476             }
1477             if (mIsDestroyed) {
1478                 Log.w(LOG_TAG, "Not adding printers - session destroyed");
1479                 return;
1480             }
1481             List<PrinterInfo> addedPrinters = null;
1482             final int addedPrinterCount = printers.size();
1483             for (int i = 0; i < addedPrinterCount; i++) {
1484                 PrinterInfo printer = printers.get(i);
1485                 PrinterInfo oldPrinter = mPrinters.put(printer.getId(), printer);
1486                 if (oldPrinter == null || !oldPrinter.equals(printer)) {
1487                     if (addedPrinters == null) {
1488                         addedPrinters = new ArrayList<PrinterInfo>();
1489                     }
1490                     addedPrinters.add(printer);
1491                 }
1492             }
1493             if (addedPrinters != null) {
1494                 Handler.getMain().sendMessage(obtainMessage(
1495                         UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersAdded,
1496                         this, addedPrinters));
1497             }
1498         }
1499 
onPrintersRemovedLocked(List<PrinterId> printerIds)1500         public void onPrintersRemovedLocked(List<PrinterId> printerIds) {
1501             if (DEBUG) {
1502                 Log.i(LOG_TAG, "onPrintersRemovedLocked()");
1503             }
1504             if (mIsDestroyed) {
1505                 Log.w(LOG_TAG, "Not removing printers - session destroyed");
1506                 return;
1507             }
1508             List<PrinterId> removedPrinterIds = null;
1509             final int removedPrinterCount = printerIds.size();
1510             for (int i = 0; i < removedPrinterCount; i++) {
1511                 PrinterId removedPrinterId = printerIds.get(i);
1512                 if (mPrinters.remove(removedPrinterId) != null) {
1513                     if (removedPrinterIds == null) {
1514                         removedPrinterIds = new ArrayList<PrinterId>();
1515                     }
1516                     removedPrinterIds.add(removedPrinterId);
1517                 }
1518             }
1519             if (removedPrinterIds != null) {
1520                 Handler.getMain().sendMessage(obtainMessage(
1521                         UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersRemoved,
1522                         this, removedPrinterIds));
1523             }
1524         }
1525 
onServiceRemovedLocked(RemotePrintService service)1526         public void onServiceRemovedLocked(RemotePrintService service) {
1527             if (mIsDestroyed) {
1528                 Log.w(LOG_TAG, "Not updating removed service - session destroyed");
1529                 return;
1530             }
1531             // Remove the reported and tracked printers for that service.
1532             ComponentName serviceName = service.getComponentName();
1533             removePrintersForServiceLocked(serviceName);
1534             service.destroy();
1535         }
1536 
1537         /**
1538          * Handle that a custom icon for a printer was loaded.
1539          *
1540          * This increments the icon generation and adds the printer again which triggers an update
1541          * in all users of the currently known printers.
1542          *
1543          * @param printerId the id of the printer the icon belongs to
1544          * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
1545          */
onCustomPrinterIconLoadedLocked(PrinterId printerId)1546         public void onCustomPrinterIconLoadedLocked(PrinterId printerId) {
1547             if (DEBUG) {
1548                 Log.i(LOG_TAG, "onCustomPrinterIconLoadedLocked()");
1549             }
1550             if (mIsDestroyed) {
1551                 Log.w(LOG_TAG, "Not updating printer - session destroyed");
1552                 return;
1553             }
1554 
1555             PrinterInfo printer = mPrinters.get(printerId);
1556             if (printer != null) {
1557                 PrinterInfo newPrinter = (new PrinterInfo.Builder(printer))
1558                         .incCustomPrinterIconGen().build();
1559                 mPrinters.put(printerId, newPrinter);
1560 
1561                 ArrayList<PrinterInfo> addedPrinters = new ArrayList<>(1);
1562                 addedPrinters.add(newPrinter);
1563                 Handler.getMain().sendMessage(obtainMessage(
1564                         UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersAdded,
1565                         this, addedPrinters));
1566             }
1567         }
1568 
onServiceDiedLocked(RemotePrintService service)1569         public void onServiceDiedLocked(RemotePrintService service) {
1570             removeServiceLocked(service);
1571         }
1572 
onServiceAddedLocked(RemotePrintService service)1573         public void onServiceAddedLocked(RemotePrintService service) {
1574             if (mIsDestroyed) {
1575                 Log.w(LOG_TAG, "Not updating added service - session destroyed");
1576                 return;
1577             }
1578             // Tell the service to create a session.
1579             Handler.getMain().sendMessage(obtainMessage(
1580                     RemotePrintService::createPrinterDiscoverySession, service));
1581             // Start printer discovery if necessary.
1582             if (!mStartedPrinterDiscoveryTokens.isEmpty()) {
1583                 Handler.getMain().sendMessage(obtainMessage(
1584                         RemotePrintService::startPrinterDiscovery, service, null));
1585             }
1586             // Start tracking printers if necessary
1587             final int trackedPrinterCount = mStateTrackedPrinters.size();
1588             for (int i = 0; i < trackedPrinterCount; i++) {
1589                 PrinterId printerId = mStateTrackedPrinters.get(i);
1590                 if (printerId.getServiceName().equals(service.getComponentName())) {
1591                     Handler.getMain().sendMessage(obtainMessage(
1592                             RemotePrintService::startPrinterStateTracking, service, printerId));
1593                 }
1594             }
1595         }
1596 
dumpLocked(@onNull DualDumpOutputStream dumpStream)1597         public void dumpLocked(@NonNull DualDumpOutputStream dumpStream) {
1598             dumpStream.write("is_destroyed", PrinterDiscoverySessionProto.IS_DESTROYED, mDestroyed);
1599             dumpStream.write("is_printer_discovery_in_progress",
1600                     PrinterDiscoverySessionProto.IS_PRINTER_DISCOVERY_IN_PROGRESS,
1601                     !mStartedPrinterDiscoveryTokens.isEmpty());
1602 
1603             final int observerCount = mDiscoveryObservers.beginBroadcast();
1604             for (int i = 0; i < observerCount; i++) {
1605                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1606                 dumpStream.write("printer_discovery_observers",
1607                         PrinterDiscoverySessionProto.PRINTER_DISCOVERY_OBSERVERS,
1608                         observer.toString());
1609             }
1610             mDiscoveryObservers.finishBroadcast();
1611 
1612             final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
1613             for (int i = 0; i < tokenCount; i++) {
1614                 IBinder token = mStartedPrinterDiscoveryTokens.get(i);
1615                 dumpStream.write("discovery_requests",
1616                         PrinterDiscoverySessionProto.DISCOVERY_REQUESTS, token.toString());
1617             }
1618 
1619             final int trackedPrinters = mStateTrackedPrinters.size();
1620             for (int i = 0; i < trackedPrinters; i++) {
1621                 PrinterId printer = mStateTrackedPrinters.get(i);
1622                 writePrinterId(dumpStream, "tracked_printer_requests",
1623                         PrinterDiscoverySessionProto.TRACKED_PRINTER_REQUESTS, printer);
1624             }
1625 
1626             final int printerCount = mPrinters.size();
1627             for (int i = 0; i < printerCount; i++) {
1628                 PrinterInfo printer = mPrinters.valueAt(i);
1629                 writePrinterInfo(mContext, dumpStream, "printer",
1630                         PrinterDiscoverySessionProto.PRINTER, printer);
1631             }
1632         }
1633 
removePrintersForServiceLocked(ComponentName serviceName)1634         private void removePrintersForServiceLocked(ComponentName serviceName) {
1635             // No printers - nothing to do.
1636             if (mPrinters.isEmpty()) {
1637                 return;
1638             }
1639             // Remove the printers for that service.
1640             List<PrinterId> removedPrinterIds = null;
1641             final int printerCount = mPrinters.size();
1642             for (int i = 0; i < printerCount; i++) {
1643                 PrinterId printerId = mPrinters.keyAt(i);
1644                 if (printerId.getServiceName().equals(serviceName)) {
1645                     if (removedPrinterIds == null) {
1646                         removedPrinterIds = new ArrayList<PrinterId>();
1647                     }
1648                     removedPrinterIds.add(printerId);
1649                 }
1650             }
1651             if (removedPrinterIds != null) {
1652                 final int removedPrinterCount = removedPrinterIds.size();
1653                 for (int i = 0; i < removedPrinterCount; i++) {
1654                     mPrinters.remove(removedPrinterIds.get(i));
1655                 }
1656                 Handler.getMain().sendMessage(obtainMessage(
1657                         UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersRemoved,
1658                         this, removedPrinterIds));
1659             }
1660         }
1661 
handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters)1662         private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) {
1663             final int observerCount = mDiscoveryObservers.beginBroadcast();
1664             for (int i = 0; i < observerCount; i++) {
1665                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1666                 handlePrintersAdded(observer, addedPrinters);
1667             }
1668             mDiscoveryObservers.finishBroadcast();
1669         }
1670 
handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds)1671         private void handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds) {
1672             final int observerCount = mDiscoveryObservers.beginBroadcast();
1673             for (int i = 0; i < observerCount; i++) {
1674                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
1675                 handlePrintersRemoved(observer, removedPrinterIds);
1676             }
1677             mDiscoveryObservers.finishBroadcast();
1678         }
1679 
handleDispatchCreatePrinterDiscoverySession( List<RemotePrintService> services)1680         private void handleDispatchCreatePrinterDiscoverySession(
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.createPrinterDiscoverySession();
1686             }
1687         }
1688 
handleDispatchDestroyPrinterDiscoverySession( List<RemotePrintService> services)1689         private void handleDispatchDestroyPrinterDiscoverySession(
1690                 List<RemotePrintService> services) {
1691             final int serviceCount = services.size();
1692             for (int i = 0; i < serviceCount; i++) {
1693                 RemotePrintService service = services.get(i);
1694                 service.destroyPrinterDiscoverySession();
1695             }
1696             onDestroyed();
1697         }
1698 
handleDispatchStartPrinterDiscovery( List<RemotePrintService> services, List<PrinterId> printerIds)1699         private void handleDispatchStartPrinterDiscovery(
1700                 List<RemotePrintService> services, List<PrinterId> printerIds) {
1701             final int serviceCount = services.size();
1702             for (int i = 0; i < serviceCount; i++) {
1703                 RemotePrintService service = services.get(i);
1704                 service.startPrinterDiscovery(printerIds);
1705             }
1706         }
1707 
handleDispatchStopPrinterDiscovery(List<RemotePrintService> services)1708         private void handleDispatchStopPrinterDiscovery(List<RemotePrintService> services) {
1709             final int serviceCount = services.size();
1710             for (int i = 0; i < serviceCount; i++) {
1711                 RemotePrintService service = services.get(i);
1712                 service.stopPrinterDiscovery();
1713             }
1714         }
1715 
handleValidatePrinters(RemotePrintService service, List<PrinterId> printerIds)1716         private void handleValidatePrinters(RemotePrintService service,
1717                 List<PrinterId> printerIds) {
1718             service.validatePrinters(printerIds);
1719         }
1720 
handleStartPrinterStateTracking(@onNull RemotePrintService service, @NonNull PrinterId printerId)1721         private void handleStartPrinterStateTracking(@NonNull RemotePrintService service,
1722                 @NonNull PrinterId printerId) {
1723             service.startPrinterStateTracking(printerId);
1724         }
1725 
handleStopPrinterStateTracking(RemotePrintService service, PrinterId printerId)1726         private void handleStopPrinterStateTracking(RemotePrintService service,
1727                 PrinterId printerId) {
1728             service.stopPrinterStateTracking(printerId);
1729         }
1730 
handlePrintersAdded(IPrinterDiscoveryObserver observer, List<PrinterInfo> printers)1731         private void handlePrintersAdded(IPrinterDiscoveryObserver observer,
1732             List<PrinterInfo> printers) {
1733             try {
1734                 observer.onPrintersAdded(new ParceledListSlice<PrinterInfo>(printers));
1735             } catch (RemoteException re) {
1736                 Log.e(LOG_TAG, "Error sending added printers", re);
1737             }
1738         }
1739 
handlePrintersRemoved(IPrinterDiscoveryObserver observer, List<PrinterId> printerIds)1740         private void handlePrintersRemoved(IPrinterDiscoveryObserver observer,
1741             List<PrinterId> printerIds) {
1742             try {
1743                 observer.onPrintersRemoved(new ParceledListSlice<PrinterId>(printerIds));
1744             } catch (RemoteException re) {
1745                 Log.e(LOG_TAG, "Error sending removed printers", re);
1746             }
1747         }
1748     }
1749 
1750     private final class PrintJobForAppCache {
1751         private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
1752                 new SparseArray<List<PrintJobInfo>>();
1753 
onPrintJobCreated(final IBinder creator, final int appId, PrintJobInfo printJob)1754         public boolean onPrintJobCreated(final IBinder creator, final int appId,
1755                 PrintJobInfo printJob) {
1756             try {
1757                 creator.linkToDeath(new DeathRecipient() {
1758                     @Override
1759                     public void binderDied() {
1760                         creator.unlinkToDeath(this, 0);
1761                         synchronized (mLock) {
1762                             mPrintJobsForRunningApp.remove(appId);
1763                         }
1764                     }
1765                 }, 0);
1766             } catch (RemoteException re) {
1767                 /* The process is already dead - we just failed. */
1768                 return false;
1769             }
1770             synchronized (mLock) {
1771                 List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
1772                 if (printJobsForApp == null) {
1773                     printJobsForApp = new ArrayList<PrintJobInfo>();
1774                     mPrintJobsForRunningApp.put(appId, printJobsForApp);
1775                 }
1776                 printJobsForApp.add(printJob);
1777             }
1778             return true;
1779         }
1780 
onPrintJobStateChanged(PrintJobInfo printJob)1781         public void onPrintJobStateChanged(PrintJobInfo printJob) {
1782             synchronized (mLock) {
1783                 List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
1784                         printJob.getAppId());
1785                 if (printJobsForApp == null) {
1786                     return;
1787                 }
1788                 final int printJobCount = printJobsForApp.size();
1789                 for (int i = 0; i < printJobCount; i++) {
1790                     PrintJobInfo oldPrintJob = printJobsForApp.get(i);
1791                     if (oldPrintJob.getId().equals(printJob.getId())) {
1792                         printJobsForApp.set(i, printJob);
1793                     }
1794                 }
1795             }
1796         }
1797 
getPrintJob(PrintJobId printJobId, int appId)1798         public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
1799             synchronized (mLock) {
1800                 List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
1801                 if (printJobsForApp == null) {
1802                     return null;
1803                 }
1804                 final int printJobCount = printJobsForApp.size();
1805                 for (int i = 0; i < printJobCount; i++) {
1806                     PrintJobInfo printJob = printJobsForApp.get(i);
1807                     if (printJob.getId().equals(printJobId)) {
1808                         return printJob;
1809                     }
1810                 }
1811             }
1812             return null;
1813         }
1814 
getPrintJobs(int appId)1815         public List<PrintJobInfo> getPrintJobs(int appId) {
1816             synchronized (mLock) {
1817                 List<PrintJobInfo> printJobs = null;
1818                 if (appId == PrintManager.APP_ID_ANY) {
1819                     final int bucketCount = mPrintJobsForRunningApp.size();
1820                     for (int i = 0; i < bucketCount; i++) {
1821                         List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
1822                         if (printJobs == null) {
1823                             printJobs = new ArrayList<PrintJobInfo>();
1824                         }
1825                         printJobs.addAll(bucket);
1826                     }
1827                 } else {
1828                     List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
1829                     if (bucket != null) {
1830                         if (printJobs == null) {
1831                             printJobs = new ArrayList<PrintJobInfo>();
1832                         }
1833                         printJobs.addAll(bucket);
1834                     }
1835                 }
1836                 if (printJobs != null) {
1837                     return printJobs;
1838                 }
1839                 return Collections.emptyList();
1840             }
1841         }
1842 
dumpLocked(@onNull DualDumpOutputStream dumpStream)1843         public void dumpLocked(@NonNull DualDumpOutputStream dumpStream) {
1844             final int bucketCount = mPrintJobsForRunningApp.size();
1845             for (int i = 0; i < bucketCount; i++) {
1846                 final int appId = mPrintJobsForRunningApp.keyAt(i);
1847                 List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
1848                 final int printJobCount = bucket.size();
1849                 for (int j = 0; j < printJobCount; j++) {
1850                     long token = dumpStream.start("cached_print_jobs",
1851                             PrintUserStateProto.CACHED_PRINT_JOBS);
1852 
1853                     dumpStream.write("app_id", CachedPrintJobProto.APP_ID, appId);
1854 
1855                     writePrintJobInfo(mContext, dumpStream, "print_job",
1856                             CachedPrintJobProto.PRINT_JOB, bucket.get(j));
1857 
1858                     dumpStream.end(token);
1859                 }
1860             }
1861         }
1862     }
1863 }
1864