| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.print; |
| |
| import static android.content.pm.PackageManager.GET_META_DATA; |
| import static android.content.pm.PackageManager.GET_SERVICES; |
| import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; |
| |
| import static com.android.internal.print.DumpUtils.writePrintJobInfo; |
| import static com.android.internal.print.DumpUtils.writePrinterId; |
| import static com.android.internal.print.DumpUtils.writePrinterInfo; |
| import static com.android.internal.util.dump.DumpUtils.writeComponentName; |
| import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; |
| import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.PendingIntent; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentSender; |
| import android.content.pm.ParceledListSlice; |
| import android.content.pm.ResolveInfo; |
| import android.graphics.drawable.Icon; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.IBinder.DeathRecipient; |
| import android.os.IInterface; |
| import android.os.Looper; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.print.IPrintDocumentAdapter; |
| import android.print.IPrintJobStateChangeListener; |
| import android.print.IPrintServicesChangeListener; |
| import android.print.IPrinterDiscoveryObserver; |
| import android.print.PrintAttributes; |
| import android.print.PrintJobId; |
| import android.print.PrintJobInfo; |
| import android.print.PrintManager; |
| import android.print.PrinterId; |
| import android.print.PrinterInfo; |
| import android.printservice.PrintServiceInfo; |
| import android.printservice.recommendation.IRecommendationsChangeListener; |
| import android.printservice.recommendation.RecommendationInfo; |
| import android.provider.DocumentsContract; |
| import android.provider.Settings; |
| import android.service.print.CachedPrintJobProto; |
| import android.service.print.InstalledPrintServiceProto; |
| import android.service.print.PrintUserStateProto; |
| import android.service.print.PrinterDiscoverySessionProto; |
| import android.text.TextUtils; |
| import android.text.TextUtils.SimpleStringSplitter; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.R; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.internal.os.BackgroundThread; |
| import com.android.internal.util.dump.DualDumpOutputStream; |
| import com.android.internal.util.function.pooled.PooledLambda; |
| import com.android.server.print.RemotePrintService.PrintServiceCallbacks; |
| import com.android.server.print.RemotePrintServiceRecommendationService |
| .RemotePrintServiceRecommendationServiceCallbacks; |
| import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.IntSupplier; |
| |
| /** |
| * Represents the print state for a user. |
| */ |
| final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, |
| RemotePrintServiceRecommendationServiceCallbacks { |
| |
| private static final String LOG_TAG = "UserState"; |
| |
| private static final boolean DEBUG = false; |
| |
| private static final char COMPONENT_NAME_SEPARATOR = ':'; |
| |
| private static final int SERVICE_RESTART_DELAY_MILLIS = 500; |
| |
| private final SimpleStringSplitter mStringColonSplitter = |
| new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); |
| |
| private final Intent mQueryIntent = |
| new Intent(android.printservice.PrintService.SERVICE_INTERFACE); |
| |
| private final ArrayMap<ComponentName, RemotePrintService> mActiveServices = |
| new ArrayMap<ComponentName, RemotePrintService>(); |
| |
| private final List<PrintServiceInfo> mInstalledServices = |
| new ArrayList<PrintServiceInfo>(); |
| |
| private final Set<ComponentName> mDisabledServices = |
| new ArraySet<ComponentName>(); |
| |
| private final PrintJobForAppCache mPrintJobForAppCache = |
| new PrintJobForAppCache(); |
| |
| private final Object mLock; |
| |
| private final Context mContext; |
| |
| private final int mUserId; |
| |
| private final RemotePrintSpooler mSpooler; |
| |
| private PrinterDiscoverySessionMediator mPrinterDiscoverySession; |
| |
| private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords; |
| |
| private List<ListenerRecord<IPrintServicesChangeListener>> mPrintServicesChangeListenerRecords; |
| |
| private List<ListenerRecord<IRecommendationsChangeListener>> |
| mPrintServiceRecommendationsChangeListenerRecords; |
| |
| private boolean mDestroyed; |
| |
| /** Currently known list of print service recommendations */ |
| private List<RecommendationInfo> mPrintServiceRecommendations; |
| |
| /** |
| * Connection to the service updating the {@link #mPrintServiceRecommendations print service |
| * recommendations}. |
| */ |
| private RemotePrintServiceRecommendationService mPrintServiceRecommendationsService; |
| |
| public UserState(Context context, int userId, Object lock, boolean lowPriority) { |
| mContext = context; |
| mUserId = userId; |
| mLock = lock; |
| mSpooler = new RemotePrintSpooler(context, userId, lowPriority, this); |
| |
| synchronized (mLock) { |
| readInstalledPrintServicesLocked(); |
| upgradePersistentStateIfNeeded(); |
| readDisabledPrintServicesLocked(); |
| } |
| |
| // Some print services might have gotten installed before the User State came up |
| prunePrintServices(); |
| |
| onConfigurationChanged(); |
| } |
| |
| public void increasePriority() { |
| mSpooler.increasePriority(); |
| } |
| |
| @Override |
| public void onPrintJobQueued(PrintJobInfo printJob) { |
| final RemotePrintService service; |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| ComponentName printServiceName = printJob.getPrinterId().getServiceName(); |
| service = mActiveServices.get(printServiceName); |
| } |
| if (service != null) { |
| service.onPrintJobQueued(printJob); |
| } else { |
| // The service for the job is no longer enabled, so just |
| // fail the job with the appropriate message. |
| mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, |
| mContext.getString(R.string.reason_service_unavailable)); |
| } |
| } |
| |
| @Override |
| public void onAllPrintJobsForServiceHandled(ComponentName printService) { |
| final RemotePrintService service; |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| service = mActiveServices.get(printService); |
| } |
| if (service != null) { |
| service.onAllPrintJobsHandled(); |
| } |
| } |
| |
| public void removeObsoletePrintJobs() { |
| mSpooler.removeObsoletePrintJobs(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| public Bundle print(@NonNull String printJobName, @NonNull IPrintDocumentAdapter adapter, |
| @Nullable PrintAttributes attributes, @NonNull String packageName, int appId) { |
| // Create print job place holder. |
| final PrintJobInfo printJob = new PrintJobInfo(); |
| printJob.setId(new PrintJobId()); |
| printJob.setAppId(appId); |
| printJob.setLabel(printJobName); |
| printJob.setAttributes(attributes); |
| printJob.setState(PrintJobInfo.STATE_CREATED); |
| printJob.setCopies(1); |
| printJob.setCreationTime(System.currentTimeMillis()); |
| |
| // Track this job so we can forget it when the creator dies. |
| if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId, |
| printJob)) { |
| // Not adding a print job means the client is dead - done. |
| return null; |
| } |
| |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG); |
| intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null)); |
| intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder()); |
| intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob); |
| intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName); |
| |
| IntentSender intentSender = PendingIntent.getActivityAsUser( |
| mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT |
| | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, |
| null, new UserHandle(mUserId)) .getIntentSender(); |
| |
| Bundle result = new Bundle(); |
| result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob); |
| result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender); |
| |
| return result; |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| public List<PrintJobInfo> getPrintJobInfos(int appId) { |
| List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId); |
| // Note that the print spooler is not storing print jobs that |
| // are in a terminal state as it is non-trivial to properly update |
| // the spooler state for when to forget print jobs in terminal state. |
| // Therefore, we fuse the cached print jobs for running apps (some |
| // jobs are in a terminal state) with the ones that the print |
| // spooler knows about (some jobs are being processed). |
| ArrayMap<PrintJobId, PrintJobInfo> result = |
| new ArrayMap<PrintJobId, PrintJobInfo>(); |
| |
| // Add the cached print jobs for running apps. |
| final int cachedPrintJobCount = cachedPrintJobs.size(); |
| for (int i = 0; i < cachedPrintJobCount; i++) { |
| PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i); |
| result.put(cachedPrintJob.getId(), cachedPrintJob); |
| // Strip out the tag and the advanced print options. |
| // They are visible only to print services. |
| cachedPrintJob.setTag(null); |
| cachedPrintJob.setAdvancedOptions(null); |
| } |
| |
| // Add everything else the spooler knows about. |
| List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null, |
| PrintJobInfo.STATE_ANY, appId); |
| if (printJobs != null) { |
| final int printJobCount = printJobs.size(); |
| for (int i = 0; i < printJobCount; i++) { |
| PrintJobInfo printJob = printJobs.get(i); |
| result.put(printJob.getId(), printJob); |
| // Strip out the tag and the advanced print options. |
| // They are visible only to print services. |
| printJob.setTag(null); |
| printJob.setAdvancedOptions(null); |
| } |
| } |
| |
| return new ArrayList<PrintJobInfo>(result.values()); |
| } |
| |
| public PrintJobInfo getPrintJobInfo(@NonNull PrintJobId printJobId, int appId) { |
| PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId); |
| if (printJob == null) { |
| printJob = mSpooler.getPrintJobInfo(printJobId, appId); |
| } |
| if (printJob != null) { |
| // Strip out the tag and the advanced print options. |
| // They are visible only to print services. |
| printJob.setTag(null); |
| printJob.setAdvancedOptions(null); |
| } |
| return printJob; |
| } |
| |
| /** |
| * Get the custom icon for a printer. If the icon is not cached, the icon is |
| * requested asynchronously. Once it is available the printer is updated. |
| * |
| * @param printerId the id of the printer the icon should be loaded for |
| * @return the custom icon to be used for the printer or null if the icon is |
| * not yet available |
| * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon |
| */ |
| public @Nullable Icon getCustomPrinterIcon(@NonNull PrinterId printerId) { |
| Icon icon = mSpooler.getCustomPrinterIcon(printerId); |
| |
| if (icon == null) { |
| RemotePrintService service = mActiveServices.get(printerId.getServiceName()); |
| if (service != null) { |
| service.requestCustomPrinterIcon(printerId); |
| } |
| } |
| |
| return icon; |
| } |
| |
| public void cancelPrintJob(@NonNull PrintJobId printJobId, int appId) { |
| PrintJobInfo printJobInfo = mSpooler.getPrintJobInfo(printJobId, appId); |
| if (printJobInfo == null) { |
| return; |
| } |
| |
| // Take a note that we are trying to cancel the job. |
| mSpooler.setPrintJobCancelling(printJobId, true); |
| |
| if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) { |
| PrinterId printerId = printJobInfo.getPrinterId(); |
| |
| if (printerId != null) { |
| ComponentName printServiceName = printerId.getServiceName(); |
| RemotePrintService printService = null; |
| synchronized (mLock) { |
| printService = mActiveServices.get(printServiceName); |
| } |
| if (printService == null) { |
| return; |
| } |
| printService.onRequestCancelPrintJob(printJobInfo); |
| } |
| } else { |
| // If the print job is failed we do not need cooperation |
| // from the print service. |
| mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED, null); |
| } |
| } |
| |
| public void restartPrintJob(@NonNull PrintJobId printJobId, int appId) { |
| PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId); |
| if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) { |
| return; |
| } |
| mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null); |
| } |
| |
| public @Nullable List<PrintServiceInfo> getPrintServices(int selectionFlags) { |
| synchronized (mLock) { |
| List<PrintServiceInfo> selectedServices = null; |
| final int installedServiceCount = mInstalledServices.size(); |
| for (int i = 0; i < installedServiceCount; i++) { |
| PrintServiceInfo installedService = mInstalledServices.get(i); |
| |
| ComponentName componentName = new ComponentName( |
| installedService.getResolveInfo().serviceInfo.packageName, |
| installedService.getResolveInfo().serviceInfo.name); |
| |
| // Update isEnabled under the same lock the final returned list is created |
| installedService.setIsEnabled(mActiveServices.containsKey(componentName)); |
| |
| if (installedService.isEnabled()) { |
| if ((selectionFlags & PrintManager.ENABLED_SERVICES) == 0) { |
| continue; |
| } |
| } else { |
| if ((selectionFlags & PrintManager.DISABLED_SERVICES) == 0) { |
| continue; |
| } |
| } |
| |
| if (selectedServices == null) { |
| selectedServices = new ArrayList<>(); |
| } |
| selectedServices.add(installedService); |
| } |
| return selectedServices; |
| } |
| } |
| |
| public void setPrintServiceEnabled(@NonNull ComponentName serviceName, boolean isEnabled) { |
| synchronized (mLock) { |
| boolean isChanged = false; |
| if (isEnabled) { |
| isChanged = mDisabledServices.remove(serviceName); |
| } else { |
| // Make sure to only disable services that are currently installed |
| final int numServices = mInstalledServices.size(); |
| for (int i = 0; i < numServices; i++) { |
| PrintServiceInfo service = mInstalledServices.get(i); |
| |
| if (service.getComponentName().equals(serviceName)) { |
| mDisabledServices.add(serviceName); |
| isChanged = true; |
| break; |
| } |
| } |
| } |
| |
| if (isChanged) { |
| writeDisabledPrintServicesLocked(mDisabledServices); |
| |
| MetricsLogger.action(mContext, MetricsEvent.ACTION_PRINT_SERVICE_TOGGLE, |
| isEnabled ? 0 : 1); |
| |
| onConfigurationChangedLocked(); |
| } |
| } |
| } |
| |
| /** |
| * @return The currently known print service recommendations |
| */ |
| public @Nullable List<RecommendationInfo> getPrintServiceRecommendations() { |
| return mPrintServiceRecommendations; |
| } |
| |
| public void createPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) { |
| mSpooler.clearCustomPrinterIconCache(); |
| |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| |
| if (mPrinterDiscoverySession == null) { |
| // If we do not have a session, tell all service to create one. |
| mPrinterDiscoverySession = new PrinterDiscoverySessionMediator() { |
| @Override |
| public void onDestroyed() { |
| mPrinterDiscoverySession = null; |
| } |
| }; |
| // Add the observer to the brand new session. |
| mPrinterDiscoverySession.addObserverLocked(observer); |
| } else { |
| // If services have created session, just add the observer. |
| mPrinterDiscoverySession.addObserverLocked(observer); |
| } |
| } |
| } |
| |
| public void destroyPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) { |
| synchronized (mLock) { |
| // Already destroyed - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| // Remove this observer. |
| mPrinterDiscoverySession.removeObserverLocked(observer); |
| } |
| } |
| |
| public void startPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer, |
| @Nullable List<PrinterId> printerIds) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| // Kick of discovery. |
| mPrinterDiscoverySession.startPrinterDiscoveryLocked(observer, |
| printerIds); |
| } |
| } |
| |
| public void stopPrinterDiscovery(@NonNull IPrinterDiscoveryObserver observer) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| // Kick of discovery. |
| mPrinterDiscoverySession.stopPrinterDiscoveryLocked(observer); |
| } |
| } |
| |
| public void validatePrinters(@NonNull List<PrinterId> printerIds) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| // No services - nothing to do. |
| if (mActiveServices.isEmpty()) { |
| return; |
| } |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| // Request an updated. |
| mPrinterDiscoverySession.validatePrintersLocked(printerIds); |
| } |
| } |
| |
| public void startPrinterStateTracking(@NonNull PrinterId printerId) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| // No services - nothing to do. |
| if (mActiveServices.isEmpty()) { |
| return; |
| } |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| // Request start tracking the printer. |
| mPrinterDiscoverySession.startPrinterStateTrackingLocked(printerId); |
| } |
| } |
| |
| public void stopPrinterStateTracking(PrinterId printerId) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| // No services - nothing to do. |
| if (mActiveServices.isEmpty()) { |
| return; |
| } |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| // Request stop tracking the printer. |
| mPrinterDiscoverySession.stopPrinterStateTrackingLocked(printerId); |
| } |
| } |
| |
| public void addPrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener, |
| int appId) throws RemoteException { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| if (mPrintJobStateChangeListenerRecords == null) { |
| mPrintJobStateChangeListenerRecords = |
| new ArrayList<PrintJobStateChangeListenerRecord>(); |
| } |
| mPrintJobStateChangeListenerRecords.add( |
| new PrintJobStateChangeListenerRecord(listener, appId) { |
| @Override |
| public void onBinderDied() { |
| synchronized (mLock) { |
| if (mPrintJobStateChangeListenerRecords != null) { |
| mPrintJobStateChangeListenerRecords.remove(this); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| public void removePrintJobStateChangeListener(@NonNull IPrintJobStateChangeListener listener) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| if (mPrintJobStateChangeListenerRecords == null) { |
| return; |
| } |
| final int recordCount = mPrintJobStateChangeListenerRecords.size(); |
| for (int i = 0; i < recordCount; i++) { |
| PrintJobStateChangeListenerRecord record = |
| mPrintJobStateChangeListenerRecords.get(i); |
| if (record.listener.asBinder().equals(listener.asBinder())) { |
| record.destroy(); |
| mPrintJobStateChangeListenerRecords.remove(i); |
| break; |
| } |
| } |
| if (mPrintJobStateChangeListenerRecords.isEmpty()) { |
| mPrintJobStateChangeListenerRecords = null; |
| } |
| } |
| } |
| |
| public void addPrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) |
| throws RemoteException { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| if (mPrintServicesChangeListenerRecords == null) { |
| mPrintServicesChangeListenerRecords = new ArrayList<>(); |
| } |
| mPrintServicesChangeListenerRecords.add( |
| new ListenerRecord<IPrintServicesChangeListener>(listener) { |
| @Override |
| public void onBinderDied() { |
| synchronized (mLock) { |
| if (mPrintServicesChangeListenerRecords != null) { |
| mPrintServicesChangeListenerRecords.remove(this); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| public void removePrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| if (mPrintServicesChangeListenerRecords == null) { |
| return; |
| } |
| final int recordCount = mPrintServicesChangeListenerRecords.size(); |
| for (int i = 0; i < recordCount; i++) { |
| ListenerRecord<IPrintServicesChangeListener> record = |
| mPrintServicesChangeListenerRecords.get(i); |
| if (record.listener.asBinder().equals(listener.asBinder())) { |
| record.destroy(); |
| mPrintServicesChangeListenerRecords.remove(i); |
| break; |
| } |
| } |
| if (mPrintServicesChangeListenerRecords.isEmpty()) { |
| mPrintServicesChangeListenerRecords = null; |
| } |
| } |
| } |
| |
| public void addPrintServiceRecommendationsChangeListener( |
| @NonNull IRecommendationsChangeListener listener) throws RemoteException { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| if (mPrintServiceRecommendationsChangeListenerRecords == null) { |
| mPrintServiceRecommendationsChangeListenerRecords = new ArrayList<>(); |
| |
| mPrintServiceRecommendationsService = |
| new RemotePrintServiceRecommendationService(mContext, |
| UserHandle.getUserHandleForUid(mUserId), this); |
| } |
| mPrintServiceRecommendationsChangeListenerRecords.add( |
| new ListenerRecord<IRecommendationsChangeListener>(listener) { |
| @Override |
| public void onBinderDied() { |
| synchronized (mLock) { |
| if (mPrintServiceRecommendationsChangeListenerRecords != null) { |
| mPrintServiceRecommendationsChangeListenerRecords.remove(this); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| public void removePrintServiceRecommendationsChangeListener( |
| @NonNull IRecommendationsChangeListener listener) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| if (mPrintServiceRecommendationsChangeListenerRecords == null) { |
| return; |
| } |
| final int recordCount = mPrintServiceRecommendationsChangeListenerRecords.size(); |
| for (int i = 0; i < recordCount; i++) { |
| ListenerRecord<IRecommendationsChangeListener> record = |
| mPrintServiceRecommendationsChangeListenerRecords.get(i); |
| if (record.listener.asBinder().equals(listener.asBinder())) { |
| record.destroy(); |
| mPrintServiceRecommendationsChangeListenerRecords.remove(i); |
| break; |
| } |
| } |
| if (mPrintServiceRecommendationsChangeListenerRecords.isEmpty()) { |
| mPrintServiceRecommendationsChangeListenerRecords = null; |
| |
| mPrintServiceRecommendations = null; |
| |
| mPrintServiceRecommendationsService.close(); |
| mPrintServiceRecommendationsService = null; |
| } |
| } |
| } |
| |
| @Override |
| public void onPrintJobStateChanged(PrintJobInfo printJob) { |
| mPrintJobForAppCache.onPrintJobStateChanged(printJob); |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState::handleDispatchPrintJobStateChanged, |
| this, printJob.getId(), |
| PooledLambda.obtainSupplier(printJob.getAppId()).recycleOnUse())); |
| } |
| |
| public void onPrintServicesChanged() { |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState::handleDispatchPrintServicesChanged, this)); |
| } |
| |
| @Override |
| public void onPrintServiceRecommendationsUpdated(List<RecommendationInfo> recommendations) { |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState::handleDispatchPrintServiceRecommendationsUpdated, |
| this, recommendations)); |
| } |
| |
| @Override |
| public void onPrintersAdded(List<PrinterInfo> printers) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| // No services - nothing to do. |
| if (mActiveServices.isEmpty()) { |
| return; |
| } |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| mPrinterDiscoverySession.onPrintersAddedLocked(printers); |
| } |
| } |
| |
| @Override |
| public void onPrintersRemoved(List<PrinterId> printerIds) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| // No services - nothing to do. |
| if (mActiveServices.isEmpty()) { |
| return; |
| } |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| mPrinterDiscoverySession.onPrintersRemovedLocked(printerIds); |
| } |
| } |
| |
| @Override |
| public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) { |
| mSpooler.onCustomPrinterIconLoaded(printerId, icon); |
| |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId); |
| } |
| } |
| |
| @Override |
| public void onServiceDied(RemotePrintService service) { |
| synchronized (mLock) { |
| throwIfDestroyedLocked(); |
| // No services - nothing to do. |
| if (mActiveServices.isEmpty()) { |
| return; |
| } |
| // Fail all print jobs. |
| failActivePrintJobsForService(service.getComponentName()); |
| service.onAllPrintJobsHandled(); |
| |
| mActiveServices.remove(service.getComponentName()); |
| |
| // The service might need to be restarted if it died because of an update |
| Handler.getMain().sendMessageDelayed(obtainMessage( |
| UserState::onConfigurationChanged, this), |
| SERVICE_RESTART_DELAY_MILLIS); |
| |
| // No session - nothing to do. |
| if (mPrinterDiscoverySession == null) { |
| return; |
| } |
| mPrinterDiscoverySession.onServiceDiedLocked(service); |
| } |
| } |
| |
| public void updateIfNeededLocked() { |
| throwIfDestroyedLocked(); |
| readConfigurationLocked(); |
| onConfigurationChangedLocked(); |
| } |
| |
| public void destroyLocked() { |
| throwIfDestroyedLocked(); |
| mSpooler.destroy(); |
| for (RemotePrintService service : mActiveServices.values()) { |
| service.destroy(); |
| } |
| mActiveServices.clear(); |
| mInstalledServices.clear(); |
| mDisabledServices.clear(); |
| if (mPrinterDiscoverySession != null) { |
| mPrinterDiscoverySession.destroyLocked(); |
| mPrinterDiscoverySession = null; |
| } |
| mDestroyed = true; |
| } |
| |
| public void dump(@NonNull DualDumpOutputStream dumpStream) { |
| synchronized (mLock) { |
| dumpStream.write("user_id", PrintUserStateProto.USER_ID, mUserId); |
| |
| final int installedServiceCount = mInstalledServices.size(); |
| for (int i = 0; i < installedServiceCount; i++) { |
| long token = dumpStream.start("installed_services", |
| PrintUserStateProto.INSTALLED_SERVICES); |
| PrintServiceInfo installedService = mInstalledServices.get(i); |
| |
| ResolveInfo resolveInfo = installedService.getResolveInfo(); |
| writeComponentName(dumpStream, "component_name", |
| InstalledPrintServiceProto.COMPONENT_NAME, |
| new ComponentName(resolveInfo.serviceInfo.packageName, |
| resolveInfo.serviceInfo.name)); |
| |
| writeStringIfNotNull(dumpStream, "settings_activity", |
| InstalledPrintServiceProto.SETTINGS_ACTIVITY, |
| installedService.getSettingsActivityName()); |
| writeStringIfNotNull(dumpStream, "add_printers_activity", |
| InstalledPrintServiceProto.ADD_PRINTERS_ACTIVITY, |
| installedService.getAddPrintersActivityName()); |
| writeStringIfNotNull(dumpStream, "advanced_options_activity", |
| InstalledPrintServiceProto.ADVANCED_OPTIONS_ACTIVITY, |
| installedService.getAdvancedOptionsActivityName()); |
| |
| dumpStream.end(token); |
| } |
| |
| for (ComponentName disabledService : mDisabledServices) { |
| writeComponentName(dumpStream, "disabled_services", |
| PrintUserStateProto.DISABLED_SERVICES, disabledService); |
| } |
| |
| final int activeServiceCount = mActiveServices.size(); |
| for (int i = 0; i < activeServiceCount; i++) { |
| long token = dumpStream.start("actives_services", |
| PrintUserStateProto.ACTIVE_SERVICES); |
| mActiveServices.valueAt(i).dump(dumpStream); |
| dumpStream.end(token); |
| } |
| |
| mPrintJobForAppCache.dumpLocked(dumpStream); |
| |
| if (mPrinterDiscoverySession != null) { |
| long token = dumpStream.start("discovery_service", |
| PrintUserStateProto.DISCOVERY_SESSIONS); |
| mPrinterDiscoverySession.dumpLocked(dumpStream); |
| dumpStream.end(token); |
| } |
| |
| } |
| |
| long token = dumpStream.start("print_spooler_state", |
| PrintUserStateProto.PRINT_SPOOLER_STATE); |
| mSpooler.dump(dumpStream); |
| dumpStream.end(token); |
| } |
| |
| private void readConfigurationLocked() { |
| readInstalledPrintServicesLocked(); |
| readDisabledPrintServicesLocked(); |
| } |
| |
| private void readInstalledPrintServicesLocked() { |
| Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>(); |
| |
| List<ResolveInfo> installedServices = mContext.getPackageManager() |
| .queryIntentServicesAsUser(mQueryIntent, |
| GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING, mUserId); |
| |
| final int installedCount = installedServices.size(); |
| for (int i = 0, count = installedCount; i < count; i++) { |
| ResolveInfo installedService = installedServices.get(i); |
| if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals( |
| installedService.serviceInfo.permission)) { |
| ComponentName serviceName = new ComponentName( |
| installedService.serviceInfo.packageName, |
| installedService.serviceInfo.name); |
| Slog.w(LOG_TAG, "Skipping print service " |
| + serviceName.flattenToShortString() |
| + " since it does not require permission " |
| + android.Manifest.permission.BIND_PRINT_SERVICE); |
| continue; |
| } |
| tempPrintServices.add(PrintServiceInfo.create(mContext, installedService)); |
| } |
| |
| mInstalledServices.clear(); |
| mInstalledServices.addAll(tempPrintServices); |
| } |
| |
| /** |
| * Update persistent state from a previous version of Android. |
| */ |
| private void upgradePersistentStateIfNeeded() { |
| String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| Settings.Secure.ENABLED_PRINT_SERVICES, mUserId); |
| |
| // Pre N we store the enabled services, in N and later we store the disabled services. |
| // Hence if enabledSettingValue is still set, we need to upgrade. |
| if (enabledSettingValue != null) { |
| Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>(); |
| readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES, |
| enabledServiceNameSet); |
| |
| ArraySet<ComponentName> disabledServices = new ArraySet<>(); |
| final int numInstalledServices = mInstalledServices.size(); |
| for (int i = 0; i < numInstalledServices; i++) { |
| ComponentName serviceName = mInstalledServices.get(i).getComponentName(); |
| if (!enabledServiceNameSet.contains(serviceName)) { |
| disabledServices.add(serviceName); |
| } |
| } |
| |
| writeDisabledPrintServicesLocked(disabledServices); |
| |
| // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run |
| // again. |
| Settings.Secure.putStringForUser(mContext.getContentResolver(), |
| Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId); |
| } |
| } |
| |
| /** |
| * Read the set of disabled print services from the secure settings. |
| * |
| * @return true if the state changed. |
| */ |
| private void readDisabledPrintServicesLocked() { |
| Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>(); |
| readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES, |
| tempDisabledServiceNameSet); |
| if (!tempDisabledServiceNameSet.equals(mDisabledServices)) { |
| mDisabledServices.clear(); |
| mDisabledServices.addAll(tempDisabledServiceNameSet); |
| } |
| } |
| |
| private void readPrintServicesFromSettingLocked(String setting, |
| Set<ComponentName> outServiceNames) { |
| String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| setting, mUserId); |
| if (!TextUtils.isEmpty(settingValue)) { |
| TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; |
| splitter.setString(settingValue); |
| while (splitter.hasNext()) { |
| String string = splitter.next(); |
| if (TextUtils.isEmpty(string)) { |
| continue; |
| } |
| ComponentName componentName = ComponentName.unflattenFromString(string); |
| if (componentName != null) { |
| outServiceNames.add(componentName); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Persist the disabled print services to the secure settings. |
| */ |
| private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) { |
| StringBuilder builder = new StringBuilder(); |
| for (ComponentName componentName : disabledServices) { |
| if (builder.length() > 0) { |
| builder.append(COMPONENT_NAME_SEPARATOR); |
| } |
| builder.append(componentName.flattenToShortString()); |
| } |
| Settings.Secure.putStringForUser(mContext.getContentResolver(), |
| Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId); |
| } |
| |
| /** |
| * Get the {@link ComponentName names} of the installed print services |
| * |
| * @return The names of the installed print services |
| */ |
| private ArrayList<ComponentName> getInstalledComponents() { |
| ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>(); |
| |
| final int installedCount = mInstalledServices.size(); |
| for (int i = 0; i < installedCount; i++) { |
| ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo(); |
| ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName, |
| resolveInfo.serviceInfo.name); |
| |
| installedComponents.add(serviceName); |
| } |
| |
| return installedComponents; |
| } |
| |
| /** |
| * Prune persistent state if a print service was uninstalled |
| */ |
| public void prunePrintServices() { |
| ArrayList<ComponentName> installedComponents; |
| |
| synchronized (mLock) { |
| installedComponents = getInstalledComponents(); |
| |
| // Remove unnecessary entries from persistent state "disabled services" |
| boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents); |
| if (disabledServicesUninstalled) { |
| writeDisabledPrintServicesLocked(mDisabledServices); |
| } |
| } |
| |
| // Remove unnecessary entries from persistent state "approved services" |
| mSpooler.pruneApprovedPrintServices(installedComponents); |
| |
| } |
| |
| private void onConfigurationChangedLocked() { |
| ArrayList<ComponentName> installedComponents = getInstalledComponents(); |
| |
| final int installedCount = installedComponents.size(); |
| for (int i = 0; i < installedCount; i++) { |
| ComponentName serviceName = installedComponents.get(i); |
| |
| if (!mDisabledServices.contains(serviceName)) { |
| if (!mActiveServices.containsKey(serviceName)) { |
| RemotePrintService service = new RemotePrintService( |
| mContext, serviceName, mUserId, mSpooler, this); |
| addServiceLocked(service); |
| } |
| } else { |
| RemotePrintService service = mActiveServices.remove(serviceName); |
| if (service != null) { |
| removeServiceLocked(service); |
| } |
| } |
| } |
| |
| Iterator<Map.Entry<ComponentName, RemotePrintService>> iterator = |
| mActiveServices.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<ComponentName, RemotePrintService> entry = iterator.next(); |
| ComponentName serviceName = entry.getKey(); |
| RemotePrintService service = entry.getValue(); |
| if (!installedComponents.contains(serviceName)) { |
| removeServiceLocked(service); |
| iterator.remove(); |
| } |
| } |
| |
| onPrintServicesChanged(); |
| } |
| |
| private void addServiceLocked(RemotePrintService service) { |
| mActiveServices.put(service.getComponentName(), service); |
| if (mPrinterDiscoverySession != null) { |
| mPrinterDiscoverySession.onServiceAddedLocked(service); |
| } |
| } |
| |
| private void removeServiceLocked(RemotePrintService service) { |
| // Fail all print jobs. |
| failActivePrintJobsForService(service.getComponentName()); |
| // If discovery is in progress, tear down the service. |
| if (mPrinterDiscoverySession != null) { |
| mPrinterDiscoverySession.onServiceRemovedLocked(service); |
| } else { |
| // Otherwise, just destroy it. |
| service.destroy(); |
| } |
| } |
| |
| private void failActivePrintJobsForService(final ComponentName serviceName) { |
| // Makes sure all active print jobs are failed since the service |
| // just died. Do this off the main thread since we do to allow |
| // calls into the spooler on the main thread. |
| if (Looper.getMainLooper().isCurrentThread()) { |
| BackgroundThread.getHandler().sendMessage(obtainMessage( |
| UserState::failScheduledPrintJobsForServiceInternal, this, serviceName)); |
| } else { |
| failScheduledPrintJobsForServiceInternal(serviceName); |
| } |
| } |
| |
| private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) { |
| List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName, |
| PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY); |
| if (printJobs == null) { |
| return; |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| final int printJobCount = printJobs.size(); |
| for (int i = 0; i < printJobCount; i++) { |
| PrintJobInfo printJob = printJobs.get(i); |
| mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, |
| mContext.getString(R.string.reason_service_unavailable)); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| private void throwIfDestroyedLocked() { |
| if (mDestroyed) { |
| throw new IllegalStateException("Cannot interact with a destroyed instance."); |
| } |
| } |
| |
| private void handleDispatchPrintJobStateChanged( |
| PrintJobId printJobId, IntSupplier appIdSupplier) { |
| int appId = appIdSupplier.getAsInt(); |
| final List<PrintJobStateChangeListenerRecord> records; |
| synchronized (mLock) { |
| if (mPrintJobStateChangeListenerRecords == null) { |
| return; |
| } |
| records = new ArrayList<>(mPrintJobStateChangeListenerRecords); |
| } |
| final int recordCount = records.size(); |
| for (int i = 0; i < recordCount; i++) { |
| PrintJobStateChangeListenerRecord record = records.get(i); |
| if (record.appId == PrintManager.APP_ID_ANY |
| || record.appId == appId) { |
| try { |
| record.listener.onPrintJobStateChanged(printJobId); |
| } catch (RemoteException re) { |
| Log.e(LOG_TAG, "Error notifying for print job state change", re); |
| } |
| } |
| } |
| } |
| |
| private void handleDispatchPrintServicesChanged() { |
| final List<ListenerRecord<IPrintServicesChangeListener>> records; |
| synchronized (mLock) { |
| if (mPrintServicesChangeListenerRecords == null) { |
| return; |
| } |
| records = new ArrayList<>(mPrintServicesChangeListenerRecords); |
| } |
| final int recordCount = records.size(); |
| for (int i = 0; i < recordCount; i++) { |
| ListenerRecord<IPrintServicesChangeListener> record = records.get(i); |
| |
| try { |
| record.listener.onPrintServicesChanged();; |
| } catch (RemoteException re) { |
| Log.e(LOG_TAG, "Error notifying for print services change", re); |
| } |
| } |
| } |
| |
| private void handleDispatchPrintServiceRecommendationsUpdated( |
| @Nullable List<RecommendationInfo> recommendations) { |
| final List<ListenerRecord<IRecommendationsChangeListener>> records; |
| synchronized (mLock) { |
| if (mPrintServiceRecommendationsChangeListenerRecords == null) { |
| return; |
| } |
| records = new ArrayList<>(mPrintServiceRecommendationsChangeListenerRecords); |
| |
| mPrintServiceRecommendations = recommendations; |
| } |
| final int recordCount = records.size(); |
| for (int i = 0; i < recordCount; i++) { |
| ListenerRecord<IRecommendationsChangeListener> record = records.get(i); |
| |
| try { |
| record.listener.onRecommendationsChanged(); |
| } catch (RemoteException re) { |
| Log.e(LOG_TAG, "Error notifying for print service recommendations change", re); |
| } |
| } |
| } |
| |
| private void onConfigurationChanged() { |
| synchronized (mLock) { |
| onConfigurationChangedLocked(); |
| } |
| } |
| |
| private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient { |
| @NonNull final IPrintJobStateChangeListener listener; |
| final int appId; |
| |
| public PrintJobStateChangeListenerRecord(@NonNull IPrintJobStateChangeListener listener, |
| int appId) throws RemoteException { |
| this.listener = listener; |
| this.appId = appId; |
| listener.asBinder().linkToDeath(this, 0); |
| } |
| |
| public void destroy() { |
| listener.asBinder().unlinkToDeath(this, 0); |
| } |
| |
| @Override |
| public void binderDied() { |
| listener.asBinder().unlinkToDeath(this, 0); |
| onBinderDied(); |
| } |
| |
| public abstract void onBinderDied(); |
| } |
| |
| private abstract class ListenerRecord<T extends IInterface> implements DeathRecipient { |
| @NonNull final T listener; |
| |
| public ListenerRecord(@NonNull T listener) throws RemoteException { |
| this.listener = listener; |
| listener.asBinder().linkToDeath(this, 0); |
| } |
| |
| public void destroy() { |
| listener.asBinder().unlinkToDeath(this, 0); |
| } |
| |
| @Override |
| public void binderDied() { |
| listener.asBinder().unlinkToDeath(this, 0); |
| onBinderDied(); |
| } |
| |
| public abstract void onBinderDied(); |
| } |
| |
| private class PrinterDiscoverySessionMediator { |
| private final ArrayMap<PrinterId, PrinterInfo> mPrinters = |
| new ArrayMap<PrinterId, PrinterInfo>(); |
| |
| private final RemoteCallbackList<IPrinterDiscoveryObserver> mDiscoveryObservers = |
| new RemoteCallbackList<IPrinterDiscoveryObserver>() { |
| @Override |
| public void onCallbackDied(IPrinterDiscoveryObserver observer) { |
| synchronized (mLock) { |
| stopPrinterDiscoveryLocked(observer); |
| removeObserverLocked(observer); |
| } |
| } |
| }; |
| |
| private final List<IBinder> mStartedPrinterDiscoveryTokens = new ArrayList<IBinder>(); |
| |
| private final List<PrinterId> mStateTrackedPrinters = new ArrayList<PrinterId>(); |
| |
| private boolean mIsDestroyed; |
| |
| PrinterDiscoverySessionMediator() { |
| // Kick off the session creation. |
| Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator:: |
| handleDispatchCreatePrinterDiscoverySession, |
| this, new ArrayList<>(mActiveServices.values()))); |
| } |
| |
| public void addObserverLocked(@NonNull IPrinterDiscoveryObserver observer) { |
| // Add the observer. |
| mDiscoveryObservers.register(observer); |
| |
| // Bring the added observer up to speed with the printers. |
| if (!mPrinters.isEmpty()) { |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState.PrinterDiscoverySessionMediator::handlePrintersAdded, |
| this, observer, new ArrayList<>(mPrinters.values()))); |
| } |
| } |
| |
| public void removeObserverLocked(@NonNull IPrinterDiscoveryObserver observer) { |
| // Remove the observer. |
| mDiscoveryObservers.unregister(observer); |
| // No one else observing - then kill it. |
| if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) { |
| destroyLocked(); |
| } |
| } |
| |
| public final void startPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer, |
| @Nullable List<PrinterId> priorityList) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not starting dicovery - session destroyed"); |
| return; |
| } |
| |
| final boolean discoveryStarted = !mStartedPrinterDiscoveryTokens.isEmpty(); |
| |
| // Remember we got a start request to match with an end. |
| mStartedPrinterDiscoveryTokens.add(observer.asBinder()); |
| |
| // If printer discovery is ongoing and the start request has a list |
| // of printer to be checked, then we just request validating them. |
| if (discoveryStarted && priorityList != null && !priorityList.isEmpty()) { |
| validatePrinters(priorityList); |
| return; |
| } |
| |
| // The service are already performing discovery - nothing to do. |
| if (mStartedPrinterDiscoveryTokens.size() > 1) { |
| return; |
| } |
| |
| Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator:: |
| handleDispatchStartPrinterDiscovery, this, |
| new ArrayList<>(mActiveServices.values()), priorityList)); |
| } |
| |
| public final void stopPrinterDiscoveryLocked(@NonNull IPrinterDiscoveryObserver observer) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not stopping dicovery - session destroyed"); |
| return; |
| } |
| // This one did not make an active discovery request - nothing to do. |
| if (!mStartedPrinterDiscoveryTokens.remove(observer.asBinder())) { |
| return; |
| } |
| // There are other interested observers - do not stop discovery. |
| if (!mStartedPrinterDiscoveryTokens.isEmpty()) { |
| return; |
| } |
| Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator:: |
| handleDispatchStopPrinterDiscovery, |
| this, new ArrayList<>(mActiveServices.values()))); |
| } |
| |
| public void validatePrintersLocked(@NonNull List<PrinterId> printerIds) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not validating pritners - session destroyed"); |
| return; |
| } |
| |
| List<PrinterId> remainingList = new ArrayList<PrinterId>(printerIds); |
| while (!remainingList.isEmpty()) { |
| Iterator<PrinterId> iterator = remainingList.iterator(); |
| // Gather the printers per service and request a validation. |
| List<PrinterId> updateList = new ArrayList<PrinterId>(); |
| ComponentName serviceName = null; |
| while (iterator.hasNext()) { |
| PrinterId printerId = iterator.next(); |
| if (printerId != null) { |
| if (updateList.isEmpty()) { |
| updateList.add(printerId); |
| serviceName = printerId.getServiceName(); |
| iterator.remove(); |
| } else if (printerId.getServiceName().equals(serviceName)) { |
| updateList.add(printerId); |
| iterator.remove(); |
| } |
| } |
| } |
| // Schedule a notification of the service. |
| RemotePrintService service = mActiveServices.get(serviceName); |
| if (service != null) { |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState.PrinterDiscoverySessionMediator::handleValidatePrinters, |
| this, service, updateList)); |
| } |
| } |
| } |
| |
| public final void startPrinterStateTrackingLocked(@NonNull PrinterId printerId) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not starting printer state tracking - session destroyed"); |
| return; |
| } |
| // If printer discovery is not started - nothing to do. |
| if (mStartedPrinterDiscoveryTokens.isEmpty()) { |
| return; |
| } |
| final boolean containedPrinterId = mStateTrackedPrinters.contains(printerId); |
| // Keep track of the number of requests to track this one. |
| mStateTrackedPrinters.add(printerId); |
| // If we were tracking this printer - nothing to do. |
| if (containedPrinterId) { |
| return; |
| } |
| // No service - nothing to do. |
| RemotePrintService service = mActiveServices.get(printerId.getServiceName()); |
| if (service == null) { |
| return; |
| } |
| // Ask the service to start tracking. |
| Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator:: |
| handleStartPrinterStateTracking, this, service, printerId)); |
| } |
| |
| public final void stopPrinterStateTrackingLocked(PrinterId printerId) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not stopping printer state tracking - session destroyed"); |
| return; |
| } |
| // If printer discovery is not started - nothing to do. |
| if (mStartedPrinterDiscoveryTokens.isEmpty()) { |
| return; |
| } |
| // If we did not track this printer - nothing to do. |
| if (!mStateTrackedPrinters.remove(printerId)) { |
| return; |
| } |
| // No service - nothing to do. |
| RemotePrintService service = mActiveServices.get(printerId.getServiceName()); |
| if (service == null) { |
| return; |
| } |
| // Ask the service to start tracking. |
| Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator:: |
| handleStopPrinterStateTracking, this, service, printerId)); |
| } |
| |
| public void onDestroyed() { |
| /* do nothing */ |
| } |
| |
| public void destroyLocked() { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not destroying - session destroyed"); |
| return; |
| } |
| mIsDestroyed = true; |
| // Make sure printer tracking is stopped. |
| final int printerCount = mStateTrackedPrinters.size(); |
| for (int i = 0; i < printerCount; i++) { |
| PrinterId printerId = mStateTrackedPrinters.get(i); |
| stopPrinterStateTracking(printerId); |
| } |
| // Make sure discovery is stopped. |
| final int observerCount = mStartedPrinterDiscoveryTokens.size(); |
| for (int i = 0; i < observerCount; i++) { |
| IBinder token = mStartedPrinterDiscoveryTokens.get(i); |
| stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver.Stub.asInterface(token)); |
| } |
| // Tell the services we are done. |
| Handler.getMain().sendMessage(obtainMessage(UserState.PrinterDiscoverySessionMediator:: |
| handleDispatchDestroyPrinterDiscoverySession, |
| this, new ArrayList<>(mActiveServices.values()))); |
| } |
| |
| public void onPrintersAddedLocked(List<PrinterInfo> printers) { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "onPrintersAddedLocked()"); |
| } |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not adding printers - session destroyed"); |
| return; |
| } |
| List<PrinterInfo> addedPrinters = null; |
| final int addedPrinterCount = printers.size(); |
| for (int i = 0; i < addedPrinterCount; i++) { |
| PrinterInfo printer = printers.get(i); |
| PrinterInfo oldPrinter = mPrinters.put(printer.getId(), printer); |
| if (oldPrinter == null || !oldPrinter.equals(printer)) { |
| if (addedPrinters == null) { |
| addedPrinters = new ArrayList<PrinterInfo>(); |
| } |
| addedPrinters.add(printer); |
| } |
| } |
| if (addedPrinters != null) { |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersAdded, |
| this, addedPrinters)); |
| } |
| } |
| |
| public void onPrintersRemovedLocked(List<PrinterId> printerIds) { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "onPrintersRemovedLocked()"); |
| } |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not removing printers - session destroyed"); |
| return; |
| } |
| List<PrinterId> removedPrinterIds = null; |
| final int removedPrinterCount = printerIds.size(); |
| for (int i = 0; i < removedPrinterCount; i++) { |
| PrinterId removedPrinterId = printerIds.get(i); |
| if (mPrinters.remove(removedPrinterId) != null) { |
| if (removedPrinterIds == null) { |
| removedPrinterIds = new ArrayList<PrinterId>(); |
| } |
| removedPrinterIds.add(removedPrinterId); |
| } |
| } |
| if (removedPrinterIds != null) { |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersRemoved, |
| this, removedPrinterIds)); |
| } |
| } |
| |
| public void onServiceRemovedLocked(RemotePrintService service) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not updating removed service - session destroyed"); |
| return; |
| } |
| // Remove the reported and tracked printers for that service. |
| ComponentName serviceName = service.getComponentName(); |
| removePrintersForServiceLocked(serviceName); |
| service.destroy(); |
| } |
| |
| /** |
| * Handle that a custom icon for a printer was loaded. |
| * |
| * This increments the icon generation and adds the printer again which triggers an update |
| * in all users of the currently known printers. |
| * |
| * @param printerId the id of the printer the icon belongs to |
| * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon |
| */ |
| public void onCustomPrinterIconLoadedLocked(PrinterId printerId) { |
| if (DEBUG) { |
| Log.i(LOG_TAG, "onCustomPrinterIconLoadedLocked()"); |
| } |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not updating printer - session destroyed"); |
| return; |
| } |
| |
| PrinterInfo printer = mPrinters.get(printerId); |
| if (printer != null) { |
| PrinterInfo newPrinter = (new PrinterInfo.Builder(printer)) |
| .incCustomPrinterIconGen().build(); |
| mPrinters.put(printerId, newPrinter); |
| |
| ArrayList<PrinterInfo> addedPrinters = new ArrayList<>(1); |
| addedPrinters.add(newPrinter); |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersAdded, |
| this, addedPrinters)); |
| } |
| } |
| |
| public void onServiceDiedLocked(RemotePrintService service) { |
| removeServiceLocked(service); |
| } |
| |
| public void onServiceAddedLocked(RemotePrintService service) { |
| if (mIsDestroyed) { |
| Log.w(LOG_TAG, "Not updating added service - session destroyed"); |
| return; |
| } |
| // Tell the service to create a session. |
| Handler.getMain().sendMessage(obtainMessage( |
| RemotePrintService::createPrinterDiscoverySession, service)); |
| // Start printer discovery if necessary. |
| if (!mStartedPrinterDiscoveryTokens.isEmpty()) { |
| Handler.getMain().sendMessage(obtainMessage( |
| RemotePrintService::startPrinterDiscovery, service, null)); |
| } |
| // Start tracking printers if necessary |
| final int trackedPrinterCount = mStateTrackedPrinters.size(); |
| for (int i = 0; i < trackedPrinterCount; i++) { |
| PrinterId printerId = mStateTrackedPrinters.get(i); |
| if (printerId.getServiceName().equals(service.getComponentName())) { |
| Handler.getMain().sendMessage(obtainMessage( |
| RemotePrintService::startPrinterStateTracking, service, printerId)); |
| } |
| } |
| } |
| |
| public void dumpLocked(@NonNull DualDumpOutputStream dumpStream) { |
| dumpStream.write("is_destroyed", PrinterDiscoverySessionProto.IS_DESTROYED, mDestroyed); |
| dumpStream.write("is_printer_discovery_in_progress", |
| PrinterDiscoverySessionProto.IS_PRINTER_DISCOVERY_IN_PROGRESS, |
| !mStartedPrinterDiscoveryTokens.isEmpty()); |
| |
| final int observerCount = mDiscoveryObservers.beginBroadcast(); |
| for (int i = 0; i < observerCount; i++) { |
| IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); |
| dumpStream.write("printer_discovery_observers", |
| PrinterDiscoverySessionProto.PRINTER_DISCOVERY_OBSERVERS, |
| observer.toString()); |
| } |
| mDiscoveryObservers.finishBroadcast(); |
| |
| final int tokenCount = this.mStartedPrinterDiscoveryTokens.size(); |
| for (int i = 0; i < tokenCount; i++) { |
| IBinder token = mStartedPrinterDiscoveryTokens.get(i); |
| dumpStream.write("discovery_requests", |
| PrinterDiscoverySessionProto.DISCOVERY_REQUESTS, token.toString()); |
| } |
| |
| final int trackedPrinters = mStateTrackedPrinters.size(); |
| for (int i = 0; i < trackedPrinters; i++) { |
| PrinterId printer = mStateTrackedPrinters.get(i); |
| writePrinterId(dumpStream, "tracked_printer_requests", |
| PrinterDiscoverySessionProto.TRACKED_PRINTER_REQUESTS, printer); |
| } |
| |
| final int printerCount = mPrinters.size(); |
| for (int i = 0; i < printerCount; i++) { |
| PrinterInfo printer = mPrinters.valueAt(i); |
| writePrinterInfo(mContext, dumpStream, "printer", |
| PrinterDiscoverySessionProto.PRINTER, printer); |
| } |
| } |
| |
| private void removePrintersForServiceLocked(ComponentName serviceName) { |
| // No printers - nothing to do. |
| if (mPrinters.isEmpty()) { |
| return; |
| } |
| // Remove the printers for that service. |
| List<PrinterId> removedPrinterIds = null; |
| final int printerCount = mPrinters.size(); |
| for (int i = 0; i < printerCount; i++) { |
| PrinterId printerId = mPrinters.keyAt(i); |
| if (printerId.getServiceName().equals(serviceName)) { |
| if (removedPrinterIds == null) { |
| removedPrinterIds = new ArrayList<PrinterId>(); |
| } |
| removedPrinterIds.add(printerId); |
| } |
| } |
| if (removedPrinterIds != null) { |
| final int removedPrinterCount = removedPrinterIds.size(); |
| for (int i = 0; i < removedPrinterCount; i++) { |
| mPrinters.remove(removedPrinterIds.get(i)); |
| } |
| Handler.getMain().sendMessage(obtainMessage( |
| UserState.PrinterDiscoverySessionMediator::handleDispatchPrintersRemoved, |
| this, removedPrinterIds)); |
| } |
| } |
| |
| private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) { |
| final int observerCount = mDiscoveryObservers.beginBroadcast(); |
| for (int i = 0; i < observerCount; i++) { |
| IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); |
| handlePrintersAdded(observer, addedPrinters); |
| } |
| mDiscoveryObservers.finishBroadcast(); |
| } |
| |
| private void handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds) { |
| final int observerCount = mDiscoveryObservers.beginBroadcast(); |
| for (int i = 0; i < observerCount; i++) { |
| IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); |
| handlePrintersRemoved(observer, removedPrinterIds); |
| } |
| mDiscoveryObservers.finishBroadcast(); |
| } |
| |
| private void handleDispatchCreatePrinterDiscoverySession( |
| List<RemotePrintService> services) { |
| final int serviceCount = services.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| RemotePrintService service = services.get(i); |
| service.createPrinterDiscoverySession(); |
| } |
| } |
| |
| private void handleDispatchDestroyPrinterDiscoverySession( |
| List<RemotePrintService> services) { |
| final int serviceCount = services.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| RemotePrintService service = services.get(i); |
| service.destroyPrinterDiscoverySession(); |
| } |
| onDestroyed(); |
| } |
| |
| private void handleDispatchStartPrinterDiscovery( |
| List<RemotePrintService> services, List<PrinterId> printerIds) { |
| final int serviceCount = services.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| RemotePrintService service = services.get(i); |
| service.startPrinterDiscovery(printerIds); |
| } |
| } |
| |
| private void handleDispatchStopPrinterDiscovery(List<RemotePrintService> services) { |
| final int serviceCount = services.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| RemotePrintService service = services.get(i); |
| service.stopPrinterDiscovery(); |
| } |
| } |
| |
| private void handleValidatePrinters(RemotePrintService service, |
| List<PrinterId> printerIds) { |
| service.validatePrinters(printerIds); |
| } |
| |
| private void handleStartPrinterStateTracking(@NonNull RemotePrintService service, |
| @NonNull PrinterId printerId) { |
| service.startPrinterStateTracking(printerId); |
| } |
| |
| private void handleStopPrinterStateTracking(RemotePrintService service, |
| PrinterId printerId) { |
| service.stopPrinterStateTracking(printerId); |
| } |
| |
| private void handlePrintersAdded(IPrinterDiscoveryObserver observer, |
| List<PrinterInfo> printers) { |
| try { |
| observer.onPrintersAdded(new ParceledListSlice<PrinterInfo>(printers)); |
| } catch (RemoteException re) { |
| Log.e(LOG_TAG, "Error sending added printers", re); |
| } |
| } |
| |
| private void handlePrintersRemoved(IPrinterDiscoveryObserver observer, |
| List<PrinterId> printerIds) { |
| try { |
| observer.onPrintersRemoved(new ParceledListSlice<PrinterId>(printerIds)); |
| } catch (RemoteException re) { |
| Log.e(LOG_TAG, "Error sending removed printers", re); |
| } |
| } |
| } |
| |
| private final class PrintJobForAppCache { |
| private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp = |
| new SparseArray<List<PrintJobInfo>>(); |
| |
| public boolean onPrintJobCreated(final IBinder creator, final int appId, |
| PrintJobInfo printJob) { |
| try { |
| creator.linkToDeath(new DeathRecipient() { |
| @Override |
| public void binderDied() { |
| creator.unlinkToDeath(this, 0); |
| synchronized (mLock) { |
| mPrintJobsForRunningApp.remove(appId); |
| } |
| } |
| }, 0); |
| } catch (RemoteException re) { |
| /* The process is already dead - we just failed. */ |
| return false; |
| } |
| synchronized (mLock) { |
| List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId); |
| if (printJobsForApp == null) { |
| printJobsForApp = new ArrayList<PrintJobInfo>(); |
| mPrintJobsForRunningApp.put(appId, printJobsForApp); |
| } |
| printJobsForApp.add(printJob); |
| } |
| return true; |
| } |
| |
| public void onPrintJobStateChanged(PrintJobInfo printJob) { |
| synchronized (mLock) { |
| List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get( |
| printJob.getAppId()); |
| if (printJobsForApp == null) { |
| return; |
| } |
| final int printJobCount = printJobsForApp.size(); |
| for (int i = 0; i < printJobCount; i++) { |
| PrintJobInfo oldPrintJob = printJobsForApp.get(i); |
| if (oldPrintJob.getId().equals(printJob.getId())) { |
| printJobsForApp.set(i, printJob); |
| } |
| } |
| } |
| } |
| |
| public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) { |
| synchronized (mLock) { |
| List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId); |
| if (printJobsForApp == null) { |
| return null; |
| } |
| final int printJobCount = printJobsForApp.size(); |
| for (int i = 0; i < printJobCount; i++) { |
| PrintJobInfo printJob = printJobsForApp.get(i); |
| if (printJob.getId().equals(printJobId)) { |
| return printJob; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public List<PrintJobInfo> getPrintJobs(int appId) { |
| synchronized (mLock) { |
| List<PrintJobInfo> printJobs = null; |
| if (appId == PrintManager.APP_ID_ANY) { |
| final int bucketCount = mPrintJobsForRunningApp.size(); |
| for (int i = 0; i < bucketCount; i++) { |
| List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); |
| if (printJobs == null) { |
| printJobs = new ArrayList<PrintJobInfo>(); |
| } |
| printJobs.addAll(bucket); |
| } |
| } else { |
| List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId); |
| if (bucket != null) { |
| if (printJobs == null) { |
| printJobs = new ArrayList<PrintJobInfo>(); |
| } |
| printJobs.addAll(bucket); |
| } |
| } |
| if (printJobs != null) { |
| return printJobs; |
| } |
| return Collections.emptyList(); |
| } |
| } |
| |
| public void dumpLocked(@NonNull DualDumpOutputStream dumpStream) { |
| final int bucketCount = mPrintJobsForRunningApp.size(); |
| for (int i = 0; i < bucketCount; i++) { |
| final int appId = mPrintJobsForRunningApp.keyAt(i); |
| List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); |
| final int printJobCount = bucket.size(); |
| for (int j = 0; j < printJobCount; j++) { |
| long token = dumpStream.start("cached_print_jobs", |
| PrintUserStateProto.CACHED_PRINT_JOBS); |
| |
| dumpStream.write("app_id", CachedPrintJobProto.APP_ID, appId); |
| |
| writePrintJobInfo(mContext, dumpStream, "print_job", |
| CachedPrintJobProto.PRINT_JOB, bucket.get(j)); |
| |
| dumpStream.end(token); |
| } |
| } |
| } |
| } |
| } |