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