Files
frameworks_base/services/java/com/android/server/print/PrintManagerService.java

790 lines
29 KiB
Java

/*
* 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.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.print.IPrintAdapter;
import android.print.IPrintClient;
import android.print.IPrintManager;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintAttributes;
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.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.Slog;
import com.android.internal.content.PackageMonitor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
public final class PrintManagerService extends IPrintManager.Stub {
private static final String LOG_TAG = PrintManagerService.class.getSimpleName();
private static final char COMPONENT_NAME_SEPARATOR = ':';
private final Object mLock = new Object();
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
private final Map<ComponentName, PrintServiceClient> mServices =
new HashMap<ComponentName, PrintServiceClient>();
private final List<PrintServiceInfo> mInstalledServices = new ArrayList<PrintServiceInfo>();
private final Set<ComponentName> mEnabledServiceNames = new HashSet<ComponentName>();
private final Context mContext;
private final RemoteSpooler mSpooler;
private final int mMyUid;
private int mCurrentUserId = UserHandle.USER_OWNER;
private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
public PrintManagerService(Context context) {
mContext = context;
mSpooler = new RemoteSpooler(context);
mMyUid = android.os.Process.myUid();
registerContentObservers();
registerBoradcastreceivers();
}
@Override
public PrintJobInfo print(String printJobName, IPrintClient client, IPrintAdapter printAdapter,
PrintAttributes attributes, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
final long identity = Binder.clearCallingIdentity();
try {
return mSpooler.createPrintJob(printJobName, client, printAdapter,
attributes, resolvedAppId, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public List<PrintJobInfo> getPrintJobs(int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
// TODO: Do we want to return jobs in STATE_CREATED? We should probably
// have additional argument for the types to get
final long identity = Binder.clearCallingIdentity();
try {
return mSpooler.getPrintJobs(null, PrintJobInfo.STATE_ANY,
resolvedAppId, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public PrintJobInfo getPrintJob(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
final long identity = Binder.clearCallingIdentity();
try {
return mSpooler.getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void cancelPrintJob(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId);
final long identity = Binder.clearCallingIdentity();
try {
if (mSpooler.cancelPrintJob(printJobId, resolvedAppId, resolvedUserId)) {
return;
}
PrintJobInfo printJob = getPrintJob(printJobId, resolvedAppId, resolvedUserId);
if (printJob == null) {
return;
}
ComponentName printServiceName = printJob.getPrinterId().getServiceComponentName();
PrintServiceClient printService = null;
synchronized (mLock) {
printService = mServices.get(printServiceName);
}
if (printService == null) {
return;
}
printService.requestCancelPrintJob(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
// Called only from the spooler.
@Override
public void onPrintJobQueued(PrinterId printerId, PrintJobInfo printJob) {
throwIfCallerNotSignedWithSystemKey();
PrintServiceClient printService = null;
synchronized (mLock) {
ComponentName printServiceName = printerId.getServiceComponentName();
printService = mServices.get(printServiceName);
}
if (printService != null) {
final long identity = Binder.clearCallingIdentity();
try {
printService.notifyPrintJobQueued(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// Called only from the spooler.
@Override
public void startDiscoverPrinters(IPrinterDiscoveryObserver observer) {
throwIfCallerNotSignedWithSystemKey();
List<PrintServiceClient> services = new ArrayList<PrintServiceClient>();
synchronized (mLock) {
mPrinterDiscoveryObserver = observer;
services.addAll(mServices.values());
}
final int serviceCount = services.size();
if (serviceCount <= 0) {
return;
}
final long identity = Binder.clearCallingIdentity();
try {
for (int i = 0; i < serviceCount; i++) {
PrintServiceClient service = services.get(i);
service.startPrinterDiscovery();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
// Called only from the spooler.
@Override
public void stopDiscoverPrinters() {
throwIfCallerNotSignedWithSystemKey();
List<PrintServiceClient> services = new ArrayList<PrintServiceClient>();
synchronized (mLock) {
mPrinterDiscoveryObserver = null;
services.addAll(mServices.values());
}
final int serviceCount = services.size();
if (serviceCount <= 0) {
return;
}
final long identity = Binder.clearCallingIdentity();
try {
for (int i = 0; i < serviceCount; i++) {
PrintServiceClient service = services.get(i);
service.stopPrintersDiscovery();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private void registerContentObservers() {
final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
Settings.Secure.ENABLED_PRINT_SERVICES);
ContentObserver observer = new ContentObserver(new Handler(mContext.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (enabledPrintServicesUri.equals(uri)) {
synchronized (mLock) {
if (readEnabledPrintServicesChangedLocked()) {
onUserStateChangedLocked();
}
}
}
}
};
mContext.getContentResolver().registerContentObserver(enabledPrintServicesUri,
false, observer, UserHandle.USER_ALL);
}
private void registerBoradcastreceivers() {
PackageMonitor monitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
synchronized (mLock) {
if (getChangingUserId() != mCurrentUserId) {
return;
}
if (readConfigurationForUserStateLocked()) {
onUserStateChangedLocked();
}
}
}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
if (getChangingUserId() != mCurrentUserId) {
return;
}
Iterator<ComponentName> iterator = mEnabledServiceNames.iterator();
while (iterator.hasNext()) {
ComponentName componentName = iterator.next();
if (packageName.equals(componentName.getPackageName())) {
iterator.remove();
onEnabledServiceNamesChangedLocked();
return;
}
}
}
}
@Override
public boolean onHandleForceStop(Intent intent, String[] stoppedPackages,
int uid, boolean doit) {
synchronized (mLock) {
if (getChangingUserId() != mCurrentUserId) {
return false;
}
Iterator<ComponentName> iterator = mEnabledServiceNames.iterator();
while (iterator.hasNext()) {
ComponentName componentName = iterator.next();
String componentPackage = componentName.getPackageName();
for (String stoppedPackage : stoppedPackages) {
if (componentPackage.equals(stoppedPackage)) {
if (!doit) {
return true;
}
iterator.remove();
onEnabledServiceNamesChangedLocked();
}
}
}
return false;
}
}
private void onEnabledServiceNamesChangedLocked() {
// Update the enabled services setting.
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_PRINT_SERVICES,
mEnabledServiceNames, mCurrentUserId);
// Update the current user state.
onUserStateChangedLocked();
}
};
// package changes
monitor.register(mContext, null, UserHandle.ALL, true);
// user changes
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
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));
}
}
}, UserHandle.ALL, intentFilter, null, null);
}
private void throwIfCallerNotSignedWithSystemKey() {
if (mContext.getPackageManager().checkSignatures(
mMyUid, Binder.getCallingUid()) != PackageManager.SIGNATURE_MATCH) {
throw new SecurityException("Caller must be signed with the system key!");
}
}
private void onUserStateChangedLocked() {
manageServicesLocked();
}
private void manageServicesLocked() {
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 (mEnabledServiceNames.contains(serviceName)) {
if (!mServices.containsKey(serviceName)) {
new PrintServiceClient(serviceName, mCurrentUserId).ensureBoundLocked();
}
} else {
PrintServiceClient service = mServices.get(serviceName);
if (service != null) {
service.ensureUnboundLocked();
}
}
}
}
private boolean readConfigurationForUserStateLocked() {
boolean somethingChanged = false;
somethingChanged |= readInstalledPrintServiceLocked();
somethingChanged |= readEnabledPrintServicesChangedLocked();
return somethingChanged;
}
private boolean readEnabledPrintServicesChangedLocked() {
Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>();
readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
mCurrentUserId, tempEnabledServiceNameSet);
if (!tempEnabledServiceNameSet.equals(mEnabledServiceNames)) {
mEnabledServiceNames.clear();
mEnabledServiceNames.addAll(tempEnabledServiceNameSet);
return true;
}
return false;
}
private boolean readInstalledPrintServiceLocked() {
Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
List<ResolveInfo> installedServices = mContext.getPackageManager()
.queryIntentServicesAsUser(
new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
mCurrentUserId);
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 void readComponentNamesFromSettingLocked(String settingName, int userId,
Set<ComponentName> outComponentNames) {
String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
settingName, userId);
outComponentNames.clear();
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) {
outComponentNames.add(componentName);
}
}
}
}
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);
}
private void switchUser(int newUserId) {
synchronized (mLock) {
// Disconnect services for the old user.
mEnabledServiceNames.clear();
onUserStateChangedLocked();
// The user changed.
mCurrentUserId = newUserId;
// Update the user state based on current settings.
readConfigurationForUserStateLocked();
onUserStateChangedLocked();
}
// Unbind the spooler for the old user).
mSpooler.unbind();
// If we have queued jobs, advertise it, or we do
// not need the spooler for now.
if (notifyQueuedPrintJobs()) {
mSpooler.unbind();
}
}
private boolean notifyQueuedPrintJobs() {
Map<PrintServiceClient, List<PrintJobInfo>> notifications =
new HashMap<PrintServiceClient, List<PrintJobInfo>>();
synchronized (mLock) {
for (PrintServiceClient service : mServices.values()) {
List<PrintJobInfo> printJobs = mSpooler.getPrintJobs(
service.mComponentName, PrintJobInfo.STATE_QUEUED,
PrintManager.APP_ID_ANY, service.mUserId);
notifications.put(service, printJobs);
}
}
if (notifications.isEmpty()) {
return false;
}
for (Map.Entry<PrintServiceClient, List<PrintJobInfo>> notification
: notifications.entrySet()) {
PrintServiceClient service = notification.getKey();
List<PrintJobInfo> printJobs = notification.getValue();
final int printJobIdCount = printJobs.size();
for (int i = 0; i < printJobIdCount; i++) {
service.notifyPrintJobQueued(printJobs.get(i));
}
}
return true;
}
private int resolveCallingUserEnforcingPermissionsIdLocked(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 int resolveCallingAppEnforcingPermissionsLocked(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(Manifest.permission.ACCESS_ALL_PRINT_JOBS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Call from app " + callingAppId + " as app "
+ appId + " without permission INTERACT_ACROSS_APPS");
}
return appId;
}
private final class PrintServiceClient extends IPrintServiceClient.Stub
implements ServiceConnection, DeathRecipient {
private final ComponentName mComponentName;
private final Intent mIntent;
private final int mUserId;
private IPrintService mInterface;
private boolean mBinding;
private boolean mWasConnectedAndDied;
public PrintServiceClient(ComponentName componentName, int userId) {
mComponentName = componentName;
mIntent = new Intent().setComponent(mComponentName);
mUserId = userId;
}
@Override
public List<PrintJobInfo> getPrintJobs() {
return mSpooler.getPrintJobs(mComponentName, PrintJobInfo.STATE_ANY,
PrintManager.APP_ID_ANY, mUserId);
}
@Override
public PrintJobInfo getPrintJob(int printJobId) {
return mSpooler.getPrintJobInfo(printJobId,
PrintManager.APP_ID_ANY, mUserId);
}
@Override
public boolean setPrintJobState(int printJobId, int state) {
return mSpooler.setPrintJobState(printJobId, state, mUserId);
}
@Override
public boolean setPrintJobTag(int printJobId, String tag) {
return mSpooler.setPrintJobTag(printJobId, tag, mUserId);
}
@Override
public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
mSpooler.writePrintJobData(fd, printJobId, mUserId);
}
@Override
public void addDiscoveredPrinters(List<PrinterInfo> printers) {
throwIfPrinterIdsForPrinterInfoTampered(printers);
synchronized (mLock) {
if (mPrinterDiscoveryObserver != null) {
try {
mPrinterDiscoveryObserver.addDiscoveredPrinters(printers);
} catch (RemoteException re) {
/* ignore */
}
}
}
}
@Override
public void removeDiscoveredPrinters(List<PrinterId> printerIds) {
throwIfPrinterIdsTampered(printerIds);
synchronized (mLock) {
if (mPrinterDiscoveryObserver != null) {
try {
mPrinterDiscoveryObserver.removeDiscoveredPrinters(printerIds);
} catch (RemoteException re) {
/* ignore */
}
}
}
}
public void requestCancelPrintJob(PrintJobInfo printJob) {
synchronized (mLock) {
try {
mInterface.requestCancelPrintJob(printJob);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error canceling pring job!", re);
}
}
}
public void notifyPrintJobQueued(PrintJobInfo printJob) {
IPrintService service = mInterface;
if (service != null) {
try {
service.onPrintJobQueued(printJob);
} catch (RemoteException re) {
/* ignore */
}
}
}
public void startPrinterDiscovery() {
IPrintService service = mInterface;
if (service != null) {
try {
service.startPrinterDiscovery();
} catch (RemoteException re) {
/* ignore */
}
}
}
public void stopPrintersDiscovery() {
IPrintService service = mInterface;
if (service != null) {
try {
service.stopPrinterDiscovery();
} catch (RemoteException re) {
/* ignore */
}
}
}
public void ensureBoundLocked() {
if (mBinding) {
return;
}
if (mInterface == null) {
mBinding = true;
mContext.bindServiceAsUser(mIntent, this,
Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
}
}
public void ensureUnboundLocked() {
if (mBinding) {
mBinding = false;
return;
}
if (mInterface != null) {
mContext.unbindService(this);
destroyLocked();
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mInterface = IPrintService.Stub.asInterface(service);
mServices.put(mComponentName, this);
try {
mInterface.asBinder().linkToDeath(this, 0);
} catch (RemoteException re) {
destroyLocked();
return;
}
if (mUserId != mCurrentUserId) {
destroyLocked();
return;
}
if (mBinding || mWasConnectedAndDied) {
mBinding = false;
mWasConnectedAndDied = false;
onUserStateChangedLocked();
try {
mInterface.setClient(this);
} catch (RemoteException re) {
Slog.w(LOG_TAG, "Error while setting client for service: "
+ service, re);
}
} else {
destroyLocked();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
/* do nothing - #binderDied takes care */
}
@Override
public void binderDied() {
synchronized (mLock) {
if (isConnectedLocked()) {
mWasConnectedAndDied = true;
}
destroyLocked();
}
}
private void destroyLocked() {
if (mServices.remove(mComponentName) == null) {
return;
}
if (isConnectedLocked()) {
try {
mInterface.asBinder().unlinkToDeath(this, 0);
} catch (NoSuchElementException nse) {
/* ignore */
}
try {
mInterface.setClient(null);
} catch (RemoteException re) {
/* ignore */
}
mInterface = null;
}
mBinding = false;
}
private boolean isConnectedLocked() {
return (mInterface != null);
}
private void throwIfPrinterIdsForPrinterInfoTampered(List<PrinterInfo> printerInfos) {
final int printerInfoCount = printerInfos.size();
for (int i = 0; i < printerInfoCount; i++) {
PrinterId printerId = printerInfos.get(i).getId();
throwIfPrinterIdTampered(printerId);
}
}
private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
final int printerIdCount = printerIds.size();
for (int i = 0; i < printerIdCount; i++) {
PrinterId printerId = printerIds.get(i);
throwIfPrinterIdTampered(printerId);
}
}
private void throwIfPrinterIdTampered(PrinterId printerId) {
if (printerId == null || printerId.getServiceComponentName() == null
|| !printerId.getServiceComponentName().equals(mComponentName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}
}
}