/* * 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 android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.print.IPrintDocumentAdapter; import android.print.IPrintJobStateChangeListener; 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.provider.DocumentsContract; import android.provider.Settings; 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.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.server.print.RemotePrintService.PrintServiceCallbacks; import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks; import java.io.FileDescriptor; import java.io.PrintWriter; 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; /** * Represents the print state for a user. */ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { private static final String LOG_TAG = "UserState"; private static final boolean DEBUG = false; private static final char COMPONENT_NAME_SEPARATOR = ':'; 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> mEnabledServices = 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 final Handler mHandler; private PrinterDiscoverySessionMediator mPrinterDiscoverySession; private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords; private boolean mDestroyed; public UserState(Context context, int userId, Object lock) { mContext = context; mUserId = userId; mLock = lock; mSpooler = new RemotePrintSpooler(context, userId, this); mHandler = new UserStateHandler(context.getMainLooper()); synchronized (mLock) { enableSystemPrintServicesLocked(); onConfigurationChangedLocked(); } } @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(String printJobName, IPrintDocumentAdapter adapter, PrintAttributes attributes, 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; } // Spin the spooler to add the job and show the config UI. new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { mSpooler.createPrintJob(printJob); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) 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(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; } public void cancelPrintJob(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) { ComponentName printServiceName = printJobInfo.getPrinterId().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(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 List<PrintServiceInfo> getEnabledPrintServices() { synchronized (mLock) { List<PrintServiceInfo> enabledServices = 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); if (mActiveServices.containsKey(componentName)) { if (enabledServices == null) { enabledServices = new ArrayList<PrintServiceInfo>(); } enabledServices.add(installedService); } } return enabledServices; } } public List<PrintServiceInfo> getInstalledPrintServices() { synchronized (mLock) { return mInstalledServices; } } public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer) { synchronized (mLock) { throwIfDestroyedLocked(); if (mActiveServices.isEmpty()) { return; } if (mPrinterDiscoverySession == null) { // If we do not have a session, tell all service to create one. mPrinterDiscoverySession = new PrinterDiscoverySessionMediator(mContext) { @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(IPrinterDiscoveryObserver observer) { synchronized (mLock) { // Already destroyed - nothing to do. if (mPrinterDiscoverySession == null) { return; } // Remove this observer. mPrinterDiscoverySession.removeObserverLocked(observer); } } public void startPrinterDiscovery(IPrinterDiscoveryObserver observer, List<PrinterId> printerIds) { synchronized (mLock) { throwIfDestroyedLocked(); // No services - nothing to do. if (mActiveServices.isEmpty()) { return; } // No session - nothing to do. if (mPrinterDiscoverySession == null) { return; } // Kick of discovery. mPrinterDiscoverySession.startPrinterDiscoveryLocked(observer, printerIds); } } public void stopPrinterDiscovery(IPrinterDiscoveryObserver observer) { synchronized (mLock) { throwIfDestroyedLocked(); // No services - nothing to do. if (mActiveServices.isEmpty()) { return; } // No session - nothing to do. if (mPrinterDiscoverySession == null) { return; } // Kick of discovery. mPrinterDiscoverySession.stopPrinterDiscoveryLocked(observer); } } public void validatePrinters(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(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(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() { mPrintJobStateChangeListenerRecords.remove(this); } }); } } public void removePrintJobStateChangeListener(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())) { mPrintJobStateChangeListenerRecords.remove(i); break; } } if (mPrintJobStateChangeListenerRecords.isEmpty()) { mPrintJobStateChangeListenerRecords = null; } } } @Override public void onPrintJobStateChanged(PrintJobInfo printJob) { mPrintJobForAppCache.onPrintJobStateChanged(printJob); mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED, printJob.getAppId(), 0, printJob.getId()).sendToTarget(); } @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 onServiceDied(RemotePrintService service) { synchronized (mLock) { throwIfDestroyedLocked(); // No services - nothing to do. if (mActiveServices.isEmpty()) { return; } // Fail all print jobs. failActivePrintJobsForService(service.getComponentName()); service.onAllPrintJobsHandled(); // No session - nothing to do. if (mPrinterDiscoverySession == null) { return; } mPrinterDiscoverySession.onServiceDiedLocked(service); } } public void updateIfNeededLocked() { throwIfDestroyedLocked(); if (readConfigurationLocked()) { onConfigurationChangedLocked(); } } public Set<ComponentName> getEnabledServices() { synchronized(mLock) { throwIfDestroyedLocked(); return mEnabledServices; } } public void destroyLocked() { throwIfDestroyedLocked(); mSpooler.destroy(); for (RemotePrintService service : mActiveServices.values()) { service.destroy(); } mActiveServices.clear(); mInstalledServices.clear(); mEnabledServices.clear(); if (mPrinterDiscoverySession != null) { mPrinterDiscoverySession.destroyLocked(); mPrinterDiscoverySession = null; } mDestroyed = true; } public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":"); pw.println(); String tab = " "; pw.append(prefix).append(tab).append("installed services:").println(); final int installedServiceCount = mInstalledServices.size(); for (int i = 0; i < installedServiceCount; i++) { PrintServiceInfo installedService = mInstalledServices.get(i); String installedServicePrefix = prefix + tab + tab; pw.append(installedServicePrefix).append("service:").println(); ResolveInfo resolveInfo = installedService.getResolveInfo(); ComponentName componentName = new ComponentName( resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); pw.append(installedServicePrefix).append(tab).append("componentName=") .append(componentName.flattenToString()).println(); pw.append(installedServicePrefix).append(tab).append("settingsActivity=") .append(installedService.getSettingsActivityName()).println(); pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=") .append(installedService.getAddPrintersActivityName()).println(); pw.append(installedServicePrefix).append(tab).append("avancedOptionsActivity=") .append(installedService.getAdvancedOptionsActivityName()).println(); } pw.append(prefix).append(tab).append("enabled services:").println(); for (ComponentName enabledService : mEnabledServices) { String enabledServicePrefix = prefix + tab + tab; pw.append(enabledServicePrefix).append("service:").println(); pw.append(enabledServicePrefix).append(tab).append("componentName=") .append(enabledService.flattenToString()); pw.println(); } pw.append(prefix).append(tab).append("active services:").println(); final int activeServiceCount = mActiveServices.size(); for (int i = 0; i < activeServiceCount; i++) { RemotePrintService activeService = mActiveServices.valueAt(i); activeService.dump(pw, prefix + tab + tab); pw.println(); } pw.append(prefix).append(tab).append("cached print jobs:").println(); mPrintJobForAppCache.dump(pw, prefix + tab + tab); pw.append(prefix).append(tab).append("discovery mediator:").println(); if (mPrinterDiscoverySession != null) { mPrinterDiscoverySession.dump(pw, prefix + tab + tab); } pw.append(prefix).append(tab).append("print spooler:").println(); mSpooler.dump(fd, pw, prefix + tab + tab); pw.println(); } private boolean readConfigurationLocked() { boolean somethingChanged = false; somethingChanged |= readInstalledPrintServicesLocked(); somethingChanged |= readEnabledPrintServicesLocked(); return somethingChanged; } private boolean readInstalledPrintServicesLocked() { Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>(); List<ResolveInfo> installedServices = mContext.getPackageManager() .queryIntentServicesAsUser(mQueryIntent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, 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(installedService, mContext)); } boolean someServiceChanged = false; if (tempPrintServices.size() != mInstalledServices.size()) { someServiceChanged = true; } else { for (PrintServiceInfo newService: tempPrintServices) { final int oldServiceIndex = mInstalledServices.indexOf(newService); if (oldServiceIndex < 0) { someServiceChanged = true; break; } // PrintServiceInfo#equals compares only the id not all members, // so we are also comparing the members coming from meta-data. PrintServiceInfo oldService = mInstalledServices.get(oldServiceIndex); if (!TextUtils.equals(oldService.getAddPrintersActivityName(), newService.getAddPrintersActivityName()) || !TextUtils.equals(oldService.getAdvancedOptionsActivityName(), newService.getAdvancedOptionsActivityName()) || !TextUtils.equals(oldService.getSettingsActivityName(), newService.getSettingsActivityName())) { someServiceChanged = true; break; } } } if (someServiceChanged) { mInstalledServices.clear(); mInstalledServices.addAll(tempPrintServices); return true; } return false; } private boolean readEnabledPrintServicesLocked() { Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>(); readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES, tempEnabledServiceNameSet); if (!tempEnabledServiceNameSet.equals(mEnabledServices)) { mEnabledServices.clear(); mEnabledServices.addAll(tempEnabledServiceNameSet); return true; } return false; } 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); } } } } private void enableSystemPrintServicesLocked() { // Load enabled and installed services. readEnabledPrintServicesLocked(); readInstalledPrintServicesLocked(); // Load the system services once enabled on first boot. Set<ComponentName> enabledOnFirstBoot = new HashSet<ComponentName>(); readPrintServicesFromSettingLocked( Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, enabledOnFirstBoot); StringBuilder builder = new StringBuilder(); final int serviceCount = mInstalledServices.size(); for (int i = 0; i < serviceCount; i++) { ServiceInfo serviceInfo = mInstalledServices.get(i).getResolveInfo().serviceInfo; // Enable system print services if we never did that and are not enabled. if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { ComponentName serviceName = new ComponentName( serviceInfo.packageName, serviceInfo.name); if (!mEnabledServices.contains(serviceName) && !enabledOnFirstBoot.contains(serviceName)) { if (builder.length() > 0) { builder.append(":"); } builder.append(serviceName.flattenToString()); } } } // Nothing to be enabled - done. if (builder.length() <= 0) { return; } String servicesToEnable = builder.toString(); // Update the enabled services setting. String enabledServices = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.ENABLED_PRINT_SERVICES, mUserId); if (TextUtils.isEmpty(enabledServices)) { enabledServices = servicesToEnable; } else { enabledServices = enabledServices + ":" + servicesToEnable; } Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices, mUserId); // Update the enabled on first boot services setting. String enabledOnFirstBootServices = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, mUserId); if (TextUtils.isEmpty(enabledOnFirstBootServices)) { enabledOnFirstBootServices = servicesToEnable; } else { enabledOnFirstBootServices = enabledOnFirstBootServices + ":" + enabledServices; } Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, enabledOnFirstBootServices, mUserId); } private void onConfigurationChangedLocked() { Set<ComponentName> installedComponents = new ArraySet<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); if (mEnabledServices.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(); } } } 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().post(new Runnable() { @Override public void run() { failScheduledPrintJobsForServiceInternal(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, int appId) { final List<PrintJobStateChangeListenerRecord> records; synchronized (mLock) { if (mPrintJobStateChangeListenerRecords == null) { return; } records = new ArrayList<PrintJobStateChangeListenerRecord>( 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 final class UserStateHandler extends Handler { public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1; public UserStateHandler(Looper looper) { super(looper, null, false); } @Override public void handleMessage(Message message) { if (message.what == MSG_DISPATCH_PRINT_JOB_STATE_CHANGED) { PrintJobId printJobId = (PrintJobId) message.obj; final int appId = message.arg1; handleDispatchPrintJobStateChanged(printJobId, appId); } } } private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient { final IPrintJobStateChangeListener listener; final int appId; public PrintJobStateChangeListenerRecord(IPrintJobStateChangeListener listener, int appId) throws RemoteException { this.listener = listener; this.appId = appId; listener.asBinder().linkToDeath(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 final Handler mHandler; private boolean mIsDestroyed; public PrinterDiscoverySessionMediator(Context context) { mHandler = new SessionHandler(context.getMainLooper()); // Kick off the session creation. List<RemotePrintService> services = new ArrayList<RemotePrintService>( mActiveServices.values()); mHandler.obtainMessage(SessionHandler .MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION, services) .sendToTarget(); } public void addObserverLocked(IPrinterDiscoveryObserver observer) { // Add the observer. mDiscoveryObservers.register(observer); // Bring the added observer up to speed with the printers. if (!mPrinters.isEmpty()) { List<PrinterInfo> printers = new ArrayList<PrinterInfo>(mPrinters.values()); SomeArgs args = SomeArgs.obtain(); args.arg1 = observer; args.arg2 = printers; mHandler.obtainMessage(SessionHandler.MSG_PRINTERS_ADDED, args).sendToTarget(); } } public void removeObserverLocked(IPrinterDiscoveryObserver observer) { // Remove the observer. mDiscoveryObservers.unregister(observer); // No one else observing - then kill it. if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) { destroyLocked(); } } public final void startPrinterDiscoveryLocked(IPrinterDiscoveryObserver observer, 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; } List<RemotePrintService> services = new ArrayList<RemotePrintService>( mActiveServices.values()); SomeArgs args = SomeArgs.obtain(); args.arg1 = services; args.arg2 = priorityList; mHandler.obtainMessage(SessionHandler .MSG_DISPATCH_START_PRINTER_DISCOVERY, args) .sendToTarget(); } public final void stopPrinterDiscoveryLocked(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; } List<RemotePrintService> services = new ArrayList<RemotePrintService>( mActiveServices.values()); mHandler.obtainMessage(SessionHandler .MSG_DISPATCH_STOP_PRINTER_DISCOVERY, services) .sendToTarget(); } public void validatePrintersLocked(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 (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) { SomeArgs args = SomeArgs.obtain(); args.arg1 = service; args.arg2 = updateList; mHandler.obtainMessage(SessionHandler .MSG_VALIDATE_PRINTERS, args) .sendToTarget(); } } } public final void startPrinterStateTrackingLocked(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. SomeArgs args = SomeArgs.obtain(); args.arg1 = service; args.arg2 = printerId; mHandler.obtainMessage(SessionHandler .MSG_START_PRINTER_STATE_TRACKING, args) .sendToTarget(); } 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. SomeArgs args = SomeArgs.obtain(); args.arg1 = service; args.arg2 = printerId; mHandler.obtainMessage(SessionHandler .MSG_STOP_PRINTER_STATE_TRACKING, args) .sendToTarget(); } 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. List<RemotePrintService> services = new ArrayList<RemotePrintService>( mActiveServices.values()); mHandler.obtainMessage(SessionHandler .MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION, services) .sendToTarget(); } 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) { mHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED, addedPrinters).sendToTarget(); } } 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) { mHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED, removedPrinterIds).sendToTarget(); } } 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(); } public void onServiceDiedLocked(RemotePrintService service) { // Remove the reported by that service. removePrintersForServiceLocked(service.getComponentName()); } 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. mHandler.obtainMessage( SessionHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION, service).sendToTarget(); // Start printer discovery if necessary. if (!mStartedPrinterDiscoveryTokens.isEmpty()) { mHandler.obtainMessage( SessionHandler.MSG_START_PRINTER_DISCOVERY, service).sendToTarget(); } // 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())) { SomeArgs args = SomeArgs.obtain(); args.arg1 = service; args.arg2 = printerId; mHandler.obtainMessage(SessionHandler .MSG_START_PRINTER_STATE_TRACKING, args) .sendToTarget(); } } } public void dump(PrintWriter pw, String prefix) { pw.append(prefix).append("destroyed=") .append(String.valueOf(mDestroyed)).println(); pw.append(prefix).append("printDiscoveryInProgress=") .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println(); String tab = " "; pw.append(prefix).append(tab).append("printer discovery observers:").println(); final int observerCount = mDiscoveryObservers.beginBroadcast(); for (int i = 0; i < observerCount; i++) { IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); pw.append(prefix).append(prefix).append(observer.toString()); pw.println(); } mDiscoveryObservers.finishBroadcast(); pw.append(prefix).append(tab).append("start discovery requests:").println(); final int tokenCount = this.mStartedPrinterDiscoveryTokens.size(); for (int i = 0; i < tokenCount; i++) { IBinder token = mStartedPrinterDiscoveryTokens.get(i); pw.append(prefix).append(tab).append(tab).append(token.toString()).println(); } pw.append(prefix).append(tab).append("tracked printer requests:").println(); final int trackedPrinters = mStateTrackedPrinters.size(); for (int i = 0; i < trackedPrinters; i++) { PrinterId printer = mStateTrackedPrinters.get(i); pw.append(prefix).append(tab).append(tab).append(printer.toString()).println(); } pw.append(prefix).append(tab).append("printers:").println(); final int pritnerCount = mPrinters.size(); for (int i = 0; i < pritnerCount; i++) { PrinterInfo printer = mPrinters.valueAt(i); pw.append(prefix).append(tab).append(tab).append( printer.toString()).println(); } } 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)); } mHandler.obtainMessage( SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED, removedPrinterIds).sendToTarget(); } } 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(RemotePrintService service, 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 SessionHandler extends Handler { public static final int MSG_PRINTERS_ADDED = 1; public static final int MSG_PRINTERS_REMOVED = 2; public static final int MSG_DISPATCH_PRINTERS_ADDED = 3; public static final int MSG_DISPATCH_PRINTERS_REMOVED = 4; public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 5; public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 6; public static final int MSG_START_PRINTER_DISCOVERY = 7; public static final int MSG_STOP_PRINTER_DISCOVERY = 8; public static final int MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION = 9; public static final int MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION = 10; public static final int MSG_DISPATCH_START_PRINTER_DISCOVERY = 11; public static final int MSG_DISPATCH_STOP_PRINTER_DISCOVERY = 12; public static final int MSG_VALIDATE_PRINTERS = 13; public static final int MSG_START_PRINTER_STATE_TRACKING = 14; public static final int MSG_STOP_PRINTER_STATE_TRACKING = 15; public static final int MSG_DESTROY_SERVICE = 16; SessionHandler(Looper looper) { super(looper, null, false); } @Override @SuppressWarnings("unchecked") public void handleMessage(Message message) { switch (message.what) { case MSG_PRINTERS_ADDED: { SomeArgs args = (SomeArgs) message.obj; IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1; List<PrinterInfo> addedPrinters = (List<PrinterInfo>) args.arg2; args.recycle(); handlePrintersAdded(observer, addedPrinters); } break; case MSG_PRINTERS_REMOVED: { SomeArgs args = (SomeArgs) message.obj; IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1; List<PrinterId> removedPrinterIds = (List<PrinterId>) args.arg2; args.recycle(); handlePrintersRemoved(observer, removedPrinterIds); } case MSG_DISPATCH_PRINTERS_ADDED: { List<PrinterInfo> addedPrinters = (List<PrinterInfo>) message.obj; handleDispatchPrintersAdded(addedPrinters); } break; case MSG_DISPATCH_PRINTERS_REMOVED: { List<PrinterId> removedPrinterIds = (List<PrinterId>) message.obj; handleDispatchPrintersRemoved(removedPrinterIds); } break; case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { RemotePrintService service = (RemotePrintService) message.obj; service.createPrinterDiscoverySession(); } break; case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { RemotePrintService service = (RemotePrintService) message.obj; service.destroyPrinterDiscoverySession(); } break; case MSG_START_PRINTER_DISCOVERY: { RemotePrintService service = (RemotePrintService) message.obj; service.startPrinterDiscovery(null); } break; case MSG_STOP_PRINTER_DISCOVERY: { RemotePrintService service = (RemotePrintService) message.obj; service.stopPrinterDiscovery(); } break; case MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION: { List<RemotePrintService> services = (List<RemotePrintService>) message.obj; handleDispatchCreatePrinterDiscoverySession(services); } break; case MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION: { List<RemotePrintService> services = (List<RemotePrintService>) message.obj; handleDispatchDestroyPrinterDiscoverySession(services); } break; case MSG_DISPATCH_START_PRINTER_DISCOVERY: { SomeArgs args = (SomeArgs) message.obj; List<RemotePrintService> services = (List<RemotePrintService>) args.arg1; List<PrinterId> printerIds = (List<PrinterId>) args.arg2; args.recycle(); handleDispatchStartPrinterDiscovery(services, printerIds); } break; case MSG_DISPATCH_STOP_PRINTER_DISCOVERY: { List<RemotePrintService> services = (List<RemotePrintService>) message.obj; handleDispatchStopPrinterDiscovery(services); } break; case MSG_VALIDATE_PRINTERS: { SomeArgs args = (SomeArgs) message.obj; RemotePrintService service = (RemotePrintService) args.arg1; List<PrinterId> printerIds = (List<PrinterId>) args.arg2; args.recycle(); handleValidatePrinters(service, printerIds); } break; case MSG_START_PRINTER_STATE_TRACKING: { SomeArgs args = (SomeArgs) message.obj; RemotePrintService service = (RemotePrintService) args.arg1; PrinterId printerId = (PrinterId) args.arg2; args.recycle(); handleStartPrinterStateTracking(service, printerId); } break; case MSG_STOP_PRINTER_STATE_TRACKING: { SomeArgs args = (SomeArgs) message.obj; RemotePrintService service = (RemotePrintService) args.arg1; PrinterId printerId = (PrinterId) args.arg2; args.recycle(); handleStopPrinterStateTracking(service, printerId); } break; case MSG_DESTROY_SERVICE: { RemotePrintService service = (RemotePrintService) message.obj; service.destroy(); } break; } } } } 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 dump(PrintWriter pw, String prefix) { synchronized (mLock) { String tab = " "; final int bucketCount = mPrintJobsForRunningApp.size(); for (int i = 0; i < bucketCount; i++) { final int appId = mPrintJobsForRunningApp.keyAt(i); pw.append(prefix).append("appId=" + appId).append(':').println(); List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); final int printJobCount = bucket.size(); for (int j = 0; j < printJobCount; j++) { PrintJobInfo printJob = bucket.get(j); pw.append(prefix).append(tab).append(printJob.toString()).println(); } } } } } }