diff options
Diffstat (limited to 'services/print')
5 files changed, 3786 insertions, 0 deletions
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java new file mode 100644 index 0000000..e24fdb5 --- /dev/null +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -0,0 +1,661 @@ +/* + * 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.Manifest; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.print.IPrintDocumentAdapter; +import android.print.IPrintJobStateChangeListener; +import android.print.IPrintManager; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintAttributes; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.printservice.PrintServiceInfo; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public final class PrintManagerService extends IPrintManager.Stub { + + private static final char COMPONENT_NAME_SEPARATOR = ':'; + + private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME = + "EXTRA_PRINT_SERVICE_COMPONENT_NAME"; + + private final Object mLock = new Object(); + + private final Context mContext; + + private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); + + private int mCurrentUserId = UserHandle.USER_OWNER; + + public PrintManagerService(Context context) { + mContext = context; + registerContentObservers(); + registerBoradcastReceivers(); + } + + public void systemRuning() { + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + final UserState userState; + synchronized (mLock) { + userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + } + // This is the first time we switch to this user after boot, so + // now is the time to remove obsolete print jobs since they + // are from the last boot and no application would query them. + userState.removeObsoletePrintJobs(); + } + }); + } + + @Override + public Bundle print(String printJobName, IPrintDocumentAdapter adapter, + PrintAttributes attributes, String packageName, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + String resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.print(printJobName, adapter, attributes, + resolvedPackageName, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public List<PrintJobInfo> getPrintJobInfos(int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getPrintJobInfos(resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getPrintJobInfo(printJobId, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.cancelPrintJob(printJobId, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void restartPrintJob(PrintJobId printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.restartPrintJob(printJobId, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public List<PrintServiceInfo> getEnabledPrintServices(int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getEnabledPrintServices(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public List<PrintServiceInfo> getInstalledPrintServices(int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getInstalledPrintServices(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer, + int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.createPrinterDiscoverySession(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void destroyPrinterDiscoverySession(IPrinterDiscoveryObserver observer, + int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.destroyPrinterDiscoverySession(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startPrinterDiscovery(IPrinterDiscoveryObserver observer, + List<PrinterId> priorityList, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.startPrinterDiscovery(observer, priorityList); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void stopPrinterDiscovery(IPrinterDiscoveryObserver observer, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.stopPrinterDiscovery(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void validatePrinters(List<PrinterId> printerIds, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.validatePrinters(printerIds); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startPrinterStateTracking(PrinterId printerId, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.startPrinterStateTracking(printerId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void stopPrinterStateTracking(PrinterId printerId, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.stopPrinterStateTracking(printerId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void addPrintJobStateChangeListener(IPrintJobStateChangeListener listener, + int appId, int userId) throws RemoteException { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.addPrintJobStateChangeListener(listener, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void removePrintJobStateChangeListener(IPrintJobStateChangeListener listener, + int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.removePrintJobStateChangeListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump PrintManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mLock) { + final long identity = Binder.clearCallingIdentity(); + try { + pw.println("PRINT MANAGER STATE (dumpsys print)"); + final int userStateCount = mUserStates.size(); + for (int i = 0; i < userStateCount; i++) { + UserState userState = mUserStates.valueAt(i); + userState.dump(fd, pw, ""); + pw.println(); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void registerContentObservers() { + final Uri enabledPrintServicesUri = Settings.Secure.getUriFor( + Settings.Secure.ENABLED_PRINT_SERVICES); + + ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (enabledPrintServicesUri.equals(uri)) { + synchronized (mLock) { + UserState userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + } + } + } + }; + + mContext.getContentResolver().registerContentObserver(enabledPrintServicesUri, + false, observer, UserHandle.USER_ALL); + } + + private void registerBoradcastReceivers() { + PackageMonitor monitor = new PackageMonitor() { + @Override + public void onPackageModified(String packageName) { + synchronized (mLock) { + boolean servicesChanged = false; + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + if (packageName.equals(componentName.getPackageName())) { + servicesChanged = true; + } + } + if (servicesChanged) { + userState.updateIfNeededLocked(); + } + } + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + synchronized (mLock) { + boolean servicesRemoved = false; + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + if (packageName.equals(componentName.getPackageName())) { + iterator.remove(); + servicesRemoved = true; + } + } + if (servicesRemoved) { + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_PRINT_SERVICES, + userState.getEnabledServices(), getChangingUserId()); + userState.updateIfNeededLocked(); + } + } + } + + @Override + public boolean onHandleForceStop(Intent intent, String[] stoppedPackages, + int uid, boolean doit) { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + boolean stoppedSomePackages = false; + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + String componentPackage = componentName.getPackageName(); + for (String stoppedPackage : stoppedPackages) { + if (componentPackage.equals(stoppedPackage)) { + if (!doit) { + return true; + } + stoppedSomePackages = true; + break; + } + } + } + if (stoppedSomePackages) { + userState.updateIfNeededLocked(); + } + return false; + } + } + + @Override + public void onPackageAdded(String packageName, int uid) { + Intent intent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE); + intent.setPackage(packageName); + + List<ResolveInfo> installedServices = mContext.getPackageManager() + .queryIntentServicesAsUser(intent, PackageManager.GET_SERVICES, + getChangingUserId()); + + if (installedServices == null) { + return; + } + + final int installedServiceCount = installedServices.size(); + for (int i = 0; i < installedServiceCount; i++) { + ServiceInfo serviceInfo = installedServices.get(i).serviceInfo; + ComponentName component = new ComponentName(serviceInfo.packageName, + serviceInfo.name); + String label = serviceInfo.loadLabel(mContext.getPackageManager()).toString(); + showEnableInstalledPrintServiceNotification(component, label, + getChangingUserId()); + } + } + + private void persistComponentNamesToSettingLocked(String settingName, + Set<ComponentName> componentNames, int userId) { + StringBuilder builder = new StringBuilder(); + for (ComponentName componentName : componentNames) { + if (builder.length() > 0) { + builder.append(COMPONENT_NAME_SEPARATOR); + } + builder.append(componentName.flattenToShortString()); + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + settingName, builder.toString(), userId); + } + }; + + // package changes + monitor.register(mContext, BackgroundThread.getHandler().getLooper(), + UserHandle.ALL, true); + + // user changes + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_SWITCHED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } + } + }, UserHandle.ALL, intentFilter, null, BackgroundThread.getHandler()); + } + + private UserState getCurrentUserStateLocked() { + return getOrCreateUserStateLocked(mCurrentUserId); + } + + private UserState getOrCreateUserStateLocked(int userId) { + UserState userState = mUserStates.get(userId); + if (userState == null) { + userState = new UserState(mContext, userId, mLock); + mUserStates.put(userId, userState); + } + return userState; + } + + private void switchUser(int newUserId) { + UserState userState; + synchronized (mLock) { + if (newUserId == mCurrentUserId) { + return; + } + mCurrentUserId = newUserId; + userState = mUserStates.get(mCurrentUserId); + if (userState == null) { + userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + } else { + userState.updateIfNeededLocked(); + } + } + // This is the first time we switch to this user after boot, so + // now is the time to remove obsolete print jobs since they + // are from the last boot and no application would query them. + userState.removeObsoletePrintJobs(); + } + + private void removeUser(int removedUserId) { + synchronized (mLock) { + UserState userState = mUserStates.get(removedUserId); + if (userState != null) { + userState.destroyLocked(); + mUserStates.remove(removedUserId); + } + } + } + + private int resolveCallingAppEnforcingPermissions(int appId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == 0 || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return appId; + } + final int callingAppId = UserHandle.getAppId(callingUid); + if (appId == callingAppId) { + return appId; + } + if (mContext.checkCallingPermission( + "com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS") + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Call from app " + callingAppId + " as app " + + appId + " without com.android.printspooler.permission" + + ".ACCESS_ALL_PRINT_JOBS"); + } + return appId; + } + + private int resolveCallingUserEnforcingPermissions(int userId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == 0 || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return userId; + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (callingUserId == userId) { + return userId; + } + if (mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED + || mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS) + != PackageManager.PERMISSION_GRANTED) { + if (userId == UserHandle.USER_CURRENT_OR_SELF) { + return callingUserId; + } + throw new SecurityException("Call from user " + callingUserId + " as user " + + userId + " without permission INTERACT_ACROSS_USERS or " + + "INTERACT_ACROSS_USERS_FULL not allowed."); + } + if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) { + return mCurrentUserId; + } + throw new IllegalArgumentException("Calling user can be changed to only " + + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); + } + + private String resolveCallingPackageNameEnforcingSecurity(String packageName) { + if (TextUtils.isEmpty(packageName)) { + return null; + } + String[] packages = mContext.getPackageManager().getPackagesForUid( + Binder.getCallingUid()); + final int packageCount = packages.length; + for (int i = 0; i < packageCount; i++) { + if (packageName.equals(packages[i])) { + return packageName; + } + } + return null; + } + + private void showEnableInstalledPrintServiceNotification(ComponentName component, + String label, int userId) { + UserHandle userHandle = new UserHandle(userId); + + Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS); + intent.putExtra(EXTRA_PRINT_SERVICE_COMPONENT_NAME, component.flattenToString()); + + PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null, userHandle); + + Notification.Builder builder = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_print) + .setContentTitle(mContext.getString(R.string.print_service_installed_title, label)) + .setContentText(mContext.getString(R.string.print_service_installed_message)) + .setContentIntent(pendingIntent) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(true) + .setShowWhen(true); + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + String notificationTag = getClass().getName() + ":" + component.flattenToString(); + notificationManager.notifyAsUser(notificationTag, 0, builder.build(), + userHandle); + } +} diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java new file mode 100644 index 0000000..1bb61d2 --- /dev/null +++ b/services/print/java/com/android/server/print/RemotePrintService.java @@ -0,0 +1,806 @@ +/* + * 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.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.UserHandle; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.IPrintService; +import android.printservice.IPrintServiceClient; +import android.util.Slog; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a remote print service. It abstracts away the binding + * and unbinding from the remote implementation. Clients can call methods of + * this class without worrying about when and how to bind/unbind. + */ +final class RemotePrintService implements DeathRecipient { + + private static final String LOG_TAG = "RemotePrintService"; + + private static final boolean DEBUG = false; + + private final Context mContext; + + private final ComponentName mComponentName; + + private final Intent mIntent; + + private final RemotePrintSpooler mSpooler; + + private final PrintServiceCallbacks mCallbacks; + + private final int mUserId; + + private final List<Runnable> mPendingCommands = new ArrayList<Runnable>(); + + private final ServiceConnection mServiceConnection = new RemoteServiceConneciton(); + + private final RemotePrintServiceClient mPrintServiceClient; + + private final Handler mHandler; + + private IPrintService mPrintService; + + private boolean mBinding; + + private boolean mDestroyed; + + private boolean mHasActivePrintJobs; + + private boolean mHasPrinterDiscoverySession; + + private boolean mServiceDied; + + private List<PrinterId> mDiscoveryPriorityList; + + private List<PrinterId> mTrackedPrinterList; + + public static interface PrintServiceCallbacks { + public void onPrintersAdded(List<PrinterInfo> printers); + public void onPrintersRemoved(List<PrinterId> printerIds); + public void onServiceDied(RemotePrintService service); + } + + public RemotePrintService(Context context, ComponentName componentName, int userId, + RemotePrintSpooler spooler, PrintServiceCallbacks callbacks) { + mContext = context; + mCallbacks = callbacks; + mComponentName = componentName; + mIntent = new Intent().setComponent(mComponentName); + mUserId = userId; + mSpooler = spooler; + mHandler = new MyHandler(context.getMainLooper()); + mPrintServiceClient = new RemotePrintServiceClient(this); + } + + public ComponentName getComponentName() { + return mComponentName; + } + + public void destroy() { + mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY); + } + + private void handleDestroy() { + throwIfDestroyed(); + + // Stop tracking printers. + if (mTrackedPrinterList != null) { + final int trackedPrinterCount = mTrackedPrinterList.size(); + for (int i = 0; i < trackedPrinterCount; i++) { + PrinterId printerId = mTrackedPrinterList.get(i); + if (printerId.getServiceName().equals(mComponentName)) { + handleStopPrinterStateTracking(printerId); + } + } + } + + // Stop printer discovery. + if (mDiscoveryPriorityList != null) { + handleStopPrinterDiscovery(); + } + + // Destroy the discovery session. + if (mHasPrinterDiscoverySession) { + handleDestroyPrinterDiscoverySession(); + } + + // Unbind. + ensureUnbound(); + + // Done + mDestroyed = true; + } + + @Override + public void binderDied() { + mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED); + } + + private void handleBinderDied() { + mPrintService.asBinder().unlinkToDeath(this, 0); + mPrintService = null; + mServiceDied = true; + mCallbacks.onServiceDied(this); + } + + public void onAllPrintJobsHandled() { + mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED); + } + + private void handleOnAllPrintJobsHandled() { + throwIfDestroyed(); + mHasActivePrintJobs = false; + if (!isBound()) { + // The service is dead and neither has active jobs nor discovery + // session, so ensure we are unbound since the service has no work. + if (mServiceDied && !mHasPrinterDiscoverySession) { + ensureUnbound(); + return; + } + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleOnAllPrintJobsHandled(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] onAllPrintJobsHandled()"); + } + // If the service has a printer discovery session + // created we should not disconnect from it just yet. + if (!mHasPrinterDiscoverySession) { + ensureUnbound(); + } + } + } + + public void onRequestCancelPrintJob(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB, + printJob).sendToTarget(); + } + + private void handleRequestCancelPrintJob(final PrintJobInfo printJob) { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleRequestCancelPrintJob(printJob); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCancelPrintJob()"); + } + try { + mPrintService.requestCancelPrintJob(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error canceling a pring job.", re); + } + } + } + + public void onPrintJobQueued(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED, + printJob).sendToTarget(); + } + + private void handleOnPrintJobQueued(final PrintJobInfo printJob) { + throwIfDestroyed(); + mHasActivePrintJobs = true; + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleOnPrintJobQueued(printJob); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] onPrintJobQueued()"); + } + try { + mPrintService.onPrintJobQueued(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error announcing queued pring job.", re); + } + } + } + + public void createPrinterDiscoverySession() { + mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION); + } + + private void handleCreatePrinterDiscoverySession() { + throwIfDestroyed(); + mHasPrinterDiscoverySession = true; + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleCreatePrinterDiscoverySession(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()"); + } + try { + mPrintService.createPrinterDiscoverySession(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error creating printer dicovery session.", re); + } + } + } + + public void destroyPrinterDiscoverySession() { + mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION); + } + + private void handleDestroyPrinterDiscoverySession() { + throwIfDestroyed(); + mHasPrinterDiscoverySession = false; + if (!isBound()) { + // The service is dead and neither has active jobs nor discovery + // session, so ensure we are unbound since the service has no work. + if (mServiceDied && !mHasActivePrintJobs) { + ensureUnbound(); + return; + } + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleDestroyPrinterDiscoverySession(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()"); + } + try { + mPrintService.destroyPrinterDiscoverySession(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re); + } + // If the service has no print jobs and no active discovery + // session anymore we should disconnect from it. + if (!mHasActivePrintJobs) { + ensureUnbound(); + } + } + } + + public void startPrinterDiscovery(List<PrinterId> priorityList) { + mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY, + priorityList).sendToTarget(); + } + + private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) { + throwIfDestroyed(); + // Take a note that we are doing discovery. + mDiscoveryPriorityList = new ArrayList<PrinterId>(); + if (priorityList != null) { + mDiscoveryPriorityList.addAll(priorityList); + } + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStartPrinterDiscovery(priorityList); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()"); + } + try { + mPrintService.startPrinterDiscovery(priorityList); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error starting printer dicovery.", re); + } + } + } + + public void stopPrinterDiscovery() { + mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY); + } + + private void handleStopPrinterDiscovery() { + throwIfDestroyed(); + // We are not doing discovery anymore. + mDiscoveryPriorityList = null; + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStopPrinterDiscovery(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()"); + } + try { + mPrintService.stopPrinterDiscovery(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error stopping printer dicovery.", re); + } + } + } + + public void validatePrinters(List<PrinterId> printerIds) { + mHandler.obtainMessage(MyHandler.MSG_VALIDATE_PRINTERS, + printerIds).sendToTarget(); + } + + private void handleValidatePrinters(final List<PrinterId> printerIds) { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleValidatePrinters(printerIds); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] validatePrinters()"); + } + try { + mPrintService.validatePrinters(printerIds); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting printers validation.", re); + } + } + } + + public void startPrinterStateTracking(PrinterId printerId) { + mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_STATE_TRACKING, + printerId).sendToTarget(); + } + + private void handleStartPrinterStateTracking(final PrinterId printerId) { + throwIfDestroyed(); + // Take a note we are tracking the printer. + if (mTrackedPrinterList == null) { + mTrackedPrinterList = new ArrayList<PrinterId>(); + } + mTrackedPrinterList.add(printerId); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStartPrinterStateTracking(printerId); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterTracking()"); + } + try { + mPrintService.startPrinterStateTracking(printerId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting start printer tracking.", re); + } + } + } + + public void stopPrinterStateTracking(PrinterId printerId) { + mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_STATE_TRACKING, + printerId).sendToTarget(); + } + + private void handleStopPrinterStateTracking(final PrinterId printerId) { + throwIfDestroyed(); + // We are no longer tracking the printer. + if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) { + return; + } + if (mTrackedPrinterList.isEmpty()) { + mTrackedPrinterList = null; + } + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStopPrinterStateTracking(printerId); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterTracking()"); + } + try { + mPrintService.stopPrinterStateTracking(printerId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting stop printer tracking.", re); + } + } + } + + public void dump(PrintWriter pw, String prefix) { + String tab = " "; + pw.append(prefix).append("service:").println(); + pw.append(prefix).append(tab).append("componentName=") + .append(mComponentName.flattenToString()).println(); + pw.append(prefix).append(tab).append("destroyed=") + .append(String.valueOf(mDestroyed)).println(); + pw.append(prefix).append(tab).append("bound=") + .append(String.valueOf(isBound())).println(); + pw.append(prefix).append(tab).append("hasDicoverySession=") + .append(String.valueOf(mHasPrinterDiscoverySession)).println(); + pw.append(prefix).append(tab).append("hasActivePrintJobs=") + .append(String.valueOf(mHasActivePrintJobs)).println(); + pw.append(prefix).append(tab).append("isDiscoveringPrinters=") + .append(String.valueOf(mDiscoveryPriorityList != null)).println(); + pw.append(prefix).append(tab).append("trackedPrinters=") + .append((mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null"); + } + + private boolean isBound() { + return mPrintService != null; + } + + private void ensureBound() { + if (isBound() || mBinding) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()"); + } + mBinding = true; + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE, new UserHandle(mUserId)); + } + + private void ensureUnbound() { + if (!isBound() && !mBinding) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()"); + } + mBinding = false; + mPendingCommands.clear(); + mHasActivePrintJobs = false; + mHasPrinterDiscoverySession = false; + mDiscoveryPriorityList = null; + mTrackedPrinterList = null; + if (isBound()) { + try { + mPrintService.setClient(null); + } catch (RemoteException re) { + /* ignore */ + } + mPrintService.asBinder().unlinkToDeath(this, 0); + mPrintService = null; + mContext.unbindService(mServiceConnection); + } + } + + private void throwIfDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed service"); + } + } + + private class RemoteServiceConneciton implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (mDestroyed || !mBinding) { + mContext.unbindService(mServiceConnection); + return; + } + mBinding = false; + mPrintService = IPrintService.Stub.asInterface(service); + try { + service.linkToDeath(RemotePrintService.this, 0); + } catch (RemoteException re) { + handleBinderDied(); + return; + } + try { + mPrintService.setClient(mPrintServiceClient); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting client for: " + service, re); + handleBinderDied(); + return; + } + // If the service died and there is a discovery session, recreate it. + if (mServiceDied && mHasPrinterDiscoverySession) { + handleCreatePrinterDiscoverySession(); + } + // If the service died and there is discovery started, restart it. + if (mServiceDied && mDiscoveryPriorityList != null) { + handleStartPrinterDiscovery(mDiscoveryPriorityList); + } + // If the service died and printers were tracked, start tracking. + if (mServiceDied && mTrackedPrinterList != null) { + final int trackedPrinterCount = mTrackedPrinterList.size(); + for (int i = 0; i < trackedPrinterCount; i++) { + handleStartPrinterStateTracking(mTrackedPrinterList.get(i)); + } + } + // Finally, do all the pending work. + while (!mPendingCommands.isEmpty()) { + Runnable pendingCommand = mPendingCommands.remove(0); + pendingCommand.run(); + } + // We did a best effort to get to the last state if we crashed. + // If we do not have print jobs and no discovery is in progress, + // then no need to be bound. + if (!mHasPrinterDiscoverySession && !mHasActivePrintJobs) { + ensureUnbound(); + } + mServiceDied = false; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBinding = true; + } + } + + private final class MyHandler extends Handler { + public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1; + public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2; + public static final int MSG_START_PRINTER_DISCOVERY = 3; + public static final int MSG_STOP_PRINTER_DISCOVERY = 4; + public static final int MSG_VALIDATE_PRINTERS = 5; + public static final int MSG_START_PRINTER_STATE_TRACKING = 6; + public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7; + public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 8; + public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 9; + public static final int MSG_ON_PRINT_JOB_QUEUED = 10; + public static final int MSG_DESTROY = 11; + public static final int MSG_BINDER_DIED = 12; + + public MyHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { + handleCreatePrinterDiscoverySession(); + } break; + + case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { + handleDestroyPrinterDiscoverySession(); + } break; + + case MSG_START_PRINTER_DISCOVERY: { + List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj; + handleStartPrinterDiscovery(priorityList); + } break; + + case MSG_STOP_PRINTER_DISCOVERY: { + handleStopPrinterDiscovery(); + } break; + + case MSG_VALIDATE_PRINTERS: { + List<PrinterId> printerIds = (List<PrinterId>) message.obj; + handleValidatePrinters(printerIds); + } break; + + case MSG_START_PRINTER_STATE_TRACKING: { + PrinterId printerId = (PrinterId) message.obj; + handleStartPrinterStateTracking(printerId); + } break; + + case MSG_STOP_PRINTER_STATE_TRACKING: { + PrinterId printerId = (PrinterId) message.obj; + handleStopPrinterStateTracking(printerId); + } break; + + case MSG_ON_ALL_PRINT_JOBS_HANDLED: { + handleOnAllPrintJobsHandled(); + } break; + + case MSG_ON_REQUEST_CANCEL_PRINT_JOB: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + handleRequestCancelPrintJob(printJob); + } break; + + case MSG_ON_PRINT_JOB_QUEUED: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + handleOnPrintJobQueued(printJob); + } break; + + case MSG_DESTROY: { + handleDestroy(); + } break; + + case MSG_BINDER_DIED: { + handleBinderDied(); + } break; + } + } + } + + private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub { + private final WeakReference<RemotePrintService> mWeakService; + + public RemotePrintServiceClient(RemotePrintService service) { + mWeakService = new WeakReference<RemotePrintService>(service); + } + + @Override + public List<PrintJobInfo> getPrintJobInfos() { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.getPrintJobInfos(service.mComponentName, + PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return null; + } + + @Override + public PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.getPrintJobInfo(printJobId, + PrintManager.APP_ID_ANY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return null; + } + + @Override + public boolean setPrintJobState(PrintJobId printJobId, int state, String error) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.setPrintJobState(printJobId, state, error); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + @Override + public boolean setPrintJobTag(PrintJobId printJobId, String tag) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.setPrintJobTag(printJobId, tag); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + @Override + public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + service.mSpooler.writePrintJobData(fd, printJobId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public void onPrintersAdded(ParceledListSlice printers) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + List<PrinterInfo> addedPrinters = (List<PrinterInfo>) printers.getList(); + throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, addedPrinters); + final long identity = Binder.clearCallingIdentity(); + try { + service.mCallbacks.onPrintersAdded(addedPrinters); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public void onPrintersRemoved(ParceledListSlice printerIds) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + List<PrinterId> removedPrinterIds = (List<PrinterId>) printerIds.getList(); + throwIfPrinterIdsTampered(service.mComponentName, removedPrinterIds); + final long identity = Binder.clearCallingIdentity(); + try { + service.mCallbacks.onPrintersRemoved(removedPrinterIds); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName, + List<PrinterInfo> printerInfos) { + final int printerInfoCount = printerInfos.size(); + for (int i = 0; i < printerInfoCount; i++) { + PrinterId printerId = printerInfos.get(i).getId(); + throwIfPrinterIdTampered(serviceName, printerId); + } + } + + private void throwIfPrinterIdsTampered(ComponentName serviceName, + List<PrinterId> printerIds) { + final int printerIdCount = printerIds.size(); + for (int i = 0; i < printerIdCount; i++) { + PrinterId printerId = printerIds.get(i); + throwIfPrinterIdTampered(serviceName, printerId); + } + } + + private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) { + if (printerId == null || printerId.getServiceName() == null + || !printerId.getServiceName().equals(serviceName)) { + throw new IllegalArgumentException("Invalid printer id: " + printerId); + } + } + } +} diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java new file mode 100644 index 0000000..ffe9806 --- /dev/null +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -0,0 +1,634 @@ +/* + * 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.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.print.IPrintSpooler; +import android.print.IPrintSpoolerCallbacks; +import android.print.IPrintSpoolerClient; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.util.Slog; +import android.util.TimedRemoteCaller; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import libcore.io.IoUtils; + +/** + * This represents the remote print spooler as a local object to the + * PrintManagerSerivce. It is responsible to connecting to the remote + * spooler if needed, to make the timed remote calls, to handle + * remote exceptions, and to bind/unbind to the remote instance as + * needed. + */ +final class RemotePrintSpooler { + + private static final String LOG_TAG = "RemotePrintSpooler"; + + private static final boolean DEBUG = false; + + private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000; + + private final Object mLock = new Object(); + + private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller(); + + private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller(); + + private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller(); + + private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller(); + + private final ServiceConnection mServiceConnection = new MyServiceConnection(); + + private final Context mContext; + + private final UserHandle mUserHandle; + + private final PrintSpoolerClient mClient; + + private final Intent mIntent; + + private final PrintSpoolerCallbacks mCallbacks; + + private IPrintSpooler mRemoteInstance; + + private boolean mDestroyed; + + private boolean mCanUnbind; + + public static interface PrintSpoolerCallbacks { + public void onPrintJobQueued(PrintJobInfo printJob); + public void onAllPrintJobsForServiceHandled(ComponentName printService); + public void onPrintJobStateChanged(PrintJobInfo printJob); + } + + public RemotePrintSpooler(Context context, int userId, + PrintSpoolerCallbacks callbacks) { + mContext = context; + mUserHandle = new UserHandle(userId); + mCallbacks = callbacks; + mClient = new PrintSpoolerClient(this); + mIntent = new Intent(); + mIntent.setComponent(new ComponentName("com.android.printspooler", + "com.android.printspooler.PrintSpoolerService")); + } + + public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, + int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(), + componentName, state, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error getting print jobs.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error getting print jobs.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return null; + } + + public final void createPrintJob(PrintJobInfo printJob) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().createPrintJob(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error creating print job.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error creating print job.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().writePrintJobData(fd, printJobId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error writing print job data.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error writing print job data.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()"); + } + // We passed the file descriptor across and now the other + // side is responsible to close it, so close the local copy. + IoUtils.closeQuietly(fd); + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(), + printJobId, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error getting print job info.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error getting print job info.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return null; + } + + public final boolean setPrintJobState(PrintJobId printJobId, int state, String error) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(), + printJobId, state, error); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job state.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job state.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return false; + } + + public final boolean setPrintJobTag(PrintJobId printJobId, String tag) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(), + printJobId, tag); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job tag.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job tag.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return false; + } + + public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().setPrintJobCancelling(printJobId, + cancelling); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job cancelling.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job cancelling.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + + "] setPrintJobCancelling()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final void removeObsoletePrintJobs() { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().removeObsoletePrintJobs(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error removing obsolete print jobs .", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error removing obsolete print jobs .", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + + "] removeObsoletePrintJobs()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final void destroy() { + throwIfCalledOnMainThread(); + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()"); + } + synchronized (mLock) { + throwIfDestroyedLocked(); + unbindLocked(); + mDestroyed = true; + mCanUnbind = false; + } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.append(prefix).append("destroyed=") + .append(String.valueOf(mDestroyed)).println(); + pw.append(prefix).append("bound=") + .append((mRemoteInstance != null) ? "true" : "false").println(); + + pw.flush(); + + try { + getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix}); + } catch (TimeoutException te) { + /* ignore */ + } catch (RemoteException re) { + /* ignore */ + } + } + } + + private void onAllPrintJobsHandled() { + synchronized (mLock) { + throwIfDestroyedLocked(); + unbindLocked(); + } + } + + private void onPrintJobStateChanged(PrintJobInfo printJob) { + mCallbacks.onPrintJobStateChanged(printJob); + } + + private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException { + synchronized (mLock) { + if (mRemoteInstance != null) { + return mRemoteInstance; + } + bindLocked(); + return mRemoteInstance; + } + } + + private void bindLocked() throws TimeoutException { + if (mRemoteInstance != null) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked()"); + } + + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE, mUserHandle); + + final long startMillis = SystemClock.uptimeMillis(); + while (true) { + if (mRemoteInstance != null) { + break; + } + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis; + if (remainingMillis <= 0) { + throw new TimeoutException("Cannot get spooler!"); + } + try { + mLock.wait(remainingMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + + mCanUnbind = true; + mLock.notifyAll(); + } + + private void unbindLocked() { + if (mRemoteInstance == null) { + return; + } + while (true) { + if (mCanUnbind) { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()"); + } + clearClientLocked(); + mRemoteInstance = null; + mContext.unbindService(mServiceConnection); + return; + } + try { + mLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + + } + + private void setClientLocked() { + try { + mRemoteInstance.setClient(mClient); + } catch (RemoteException re) { + Slog.d(LOG_TAG, "Error setting print spooler client", re); + } + } + + private void clearClientLocked() { + try { + mRemoteInstance.setClient(null); + } catch (RemoteException re) { + Slog.d(LOG_TAG, "Error clearing print spooler client", re); + } + + } + + private void throwIfDestroyedLocked() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed instance."); + } + } + + private void throwIfCalledOnMainThread() { + if (Thread.currentThread() == mContext.getMainLooper().getThread()) { + throw new RuntimeException("Cannot invoke on the main thread"); + } + } + + private final class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mRemoteInstance = IPrintSpooler.Stub.asInterface(service); + setClientLocked(); + mLock.notifyAll(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + clearClientLocked(); + mRemoteInstance = null; + } + } + } + + private static final class GetPrintJobInfosCaller + extends TimedRemoteCaller<List<PrintJobInfo>> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfosCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) { + onRemoteMethodResult(printJobs, sequence); + } + }; + } + + public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target, + ComponentName componentName, int state, int appId) + throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getPrintJobInfos(mCallback, componentName, state, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfoCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { + onRemoteMethodResult(printJob, sequence); + } + }; + } + + public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId, + int appId) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getPrintJobInfo(printJobId, mCallback, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public SetPrintJobStateCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onSetPrintJobStateResult(boolean success, int sequence) { + onRemoteMethodResult(success, sequence); + } + }; + } + + public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId, + int status, String error) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.setPrintJobState(printJobId, status, error, mCallback, sequence); + return getResultTimed(sequence); + } + } + + private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public SetPrintJobTagCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onSetPrintJobTagResult(boolean success, int sequence) { + onRemoteMethodResult(success, sequence); + } + }; + } + + public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId, + String tag) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.setPrintJobTag(printJobId, tag, mCallback, sequence); + return getResultTimed(sequence); + } + } + + private static abstract class BasePrintSpoolerServiceCallbacks + extends IPrintSpoolerCallbacks.Stub { + @Override + public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) { + /* do nothing */ + } + + @Override + public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { + /* do nothing */ + } + + @Override + public void onCancelPrintJobResult(boolean canceled, int sequence) { + /* do nothing */ + } + + @Override + public void onSetPrintJobStateResult(boolean success, int sequece) { + /* do nothing */ + } + + @Override + public void onSetPrintJobTagResult(boolean success, int sequence) { + /* do nothing */ + } + } + + private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub { + + private final WeakReference<RemotePrintSpooler> mWeakSpooler; + + public PrintSpoolerClient(RemotePrintSpooler spooler) { + mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler); + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJob) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onPrintJobQueued(printJob); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onAllPrintJobsForServiceHandled(ComponentName printService) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onAllPrintJobsHandled() { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.onAllPrintJobsHandled(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onPrintJobStateChanged(PrintJobInfo printJob) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.onPrintJobStateChanged(printJob); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + } +} diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java new file mode 100644 index 0000000..f647814 --- /dev/null +++ b/services/print/java/com/android/server/print/UserState.java @@ -0,0 +1,1674 @@ +/* + * 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) { + enableSystemPrintServicesOnFirstBootLocked(); + } + } + + @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, 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 enableSystemPrintServicesOnFirstBootLocked() { + // 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; + } + // 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(); + } + } + } + } + } +} diff --git a/services/print/java/service.mk b/services/print/java/service.mk new file mode 100644 index 0000000..cba3612 --- /dev/null +++ b/services/print/java/service.mk @@ -0,0 +1,11 @@ +# Include only if the service is required +ifneq ($(findstring print,$(REQUIRED_SERVICES)),) + +SUB_DIR := print/java + +LOCAL_SRC_FILES += \ + $(call all-java-files-under,$(SUB_DIR)) + +#DEFINED_SERVICES += com.android.server.print.PrintManagerService + +endif |