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