summaryrefslogtreecommitdiffstats
path: root/services/print
diff options
context:
space:
mode:
authorAmith Yamasani <yamasani@google.com>2013-11-22 08:25:26 -0800
committerAmith Yamasani <yamasani@google.com>2013-12-19 15:25:37 -0800
commit9158825f9c41869689d6b1786d7c7aa8bdd524ce (patch)
treef41944461539f0c70030668b4558296469c307d3 /services/print
parent30d032928a294fbb6f385e9d0367a75b7bf2649b (diff)
downloadframeworks_base-9158825f9c41869689d6b1786d7c7aa8bdd524ce.zip
frameworks_base-9158825f9c41869689d6b1786d7c7aa8bdd524ce.tar.gz
frameworks_base-9158825f9c41869689d6b1786d7c7aa8bdd524ce.tar.bz2
Move some system services to separate directories
Refactored the directory structure so that services can be optionally excluded. This is step 1. Will be followed by another change that makes it possible to remove services from the build. Change-Id: Ideacedfd34b5e213217ad3ff4ebb21c4a8e73f85
Diffstat (limited to 'services/print')
-rw-r--r--services/print/java/com/android/server/print/PrintManagerService.java656
-rw-r--r--services/print/java/com/android/server/print/RemotePrintService.java806
-rw-r--r--services/print/java/com/android/server/print/RemotePrintSpooler.java634
-rw-r--r--services/print/java/com/android/server/print/UserState.java1628
-rw-r--r--services/print/java/service.mk11
5 files changed, 3735 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..98acc27
--- /dev/null
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -0,0 +1,656 @@
+/*
+ * 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 boolean onPackageChanged(String packageName, int uid, String[] components) {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(getChangingUserId());
+ Iterator<ComponentName> iterator = userState.getEnabledServices().iterator();
+ while (iterator.hasNext()) {
+ ComponentName componentName = iterator.next();
+ if (packageName.equals(componentName.getPackageName())) {
+ userState.updateIfNeededLocked();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(getChangingUserId());
+ Iterator<ComponentName> iterator = userState.getEnabledServices().iterator();
+ while (iterator.hasNext()) {
+ ComponentName componentName = iterator.next();
+ if (packageName.equals(componentName.getPackageName())) {
+ iterator.remove();
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_PRINT_SERVICES,
+ userState.getEnabledServices(), getChangingUserId());
+ userState.updateIfNeededLocked();
+ return;
+ }
+ }
+ }
+ }
+
+ @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..f23a992
--- /dev/null
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -0,0 +1,1628 @@
+/*
+ * 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.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(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));
+ }
+
+ if (!tempPrintServices.equals(mInstalledServices)) {
+ 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() {
+ 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);
+ 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);
+ }
+ }
+ }
+ }
+
+ 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