Files
frameworks_base/services/java/com/android/server/print/PrintManagerService.java
Svetoslav Ganov d26d4898fc Print spooler security and some new print service facing APIs.
1. Updated the security mode of the print spooler. Now the spooler
   is not signed with the system key, it is not a privileged app so if
   it gets compromised (PDF rendering is a potential attack vector)
   it cannot access dangerous permissions. Also only the system
   can bind to the spooler.

2. Added APIs for asking a print service to start and stop tracking
   a given printer. This is need for the case when the user selects
   the printer and the print service should do a best effort to keep
   the system updated for the current state of the printer.

3. Added APIs for putting a print job in a blocked state. A print
   service would report the print job as blocked if for some reason
   the printer cannot proceed, e.g. 99 pages are printed but there
   is no paper for the last one. The user has to add more paper
   and the print service can resume the job.

4. Changed the read/write APIs to use ParcelFileDescriptor instead
   of FileDescriptor since the latter does not have a clean API for
   detaching the wrapped Linux file descriptor when one wants to
   push it to native.

5. Added API for getting the size of the printed document so the
   print service can avoid handling big filed over cellular network
   or ask the user if needed.

6. Now the print services that are preinstalled on the system image
   are automatically enabled.

Change-Id: Ia06c311d3d21cabb9e1368f13928e11cd0030918
2013-08-29 15:39:44 -07:00

503 lines
20 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.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.print.IPrintClient;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintManager;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.provider.Settings;
import android.util.SparseArray;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
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 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() {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
userState.updateIfNeededLocked();
userState.getSpoolerLocked().start();
}
}
});
}
@Override
public PrintJobInfo print(String printJobName, IPrintClient client,
IPrintDocumentAdapter documentAdapter, PrintAttributes attributes, int appId,
int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final UserState userState;
final RemotePrintSpooler spooler;
synchronized (mLock) {
userState = getOrCreateUserStateLocked(resolvedUserId);
spooler = userState.getSpoolerLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
return spooler.createPrintJob(printJobName, client, documentAdapter,
attributes, 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;
final RemotePrintSpooler spooler;
synchronized (mLock) {
userState = getOrCreateUserStateLocked(resolvedUserId);
spooler = userState.getSpoolerLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
return spooler.getPrintJobInfos(null, PrintJobInfo.STATE_ANY,
resolvedAppId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public PrintJobInfo getPrintJobInfo(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final UserState userState;
final RemotePrintSpooler spooler;
synchronized (mLock) {
userState = getOrCreateUserStateLocked(resolvedUserId);
spooler = userState.getSpoolerLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
return spooler.getPrintJobInfo(printJobId, resolvedAppId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void cancelPrintJob(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final UserState userState;
final RemotePrintSpooler spooler;
synchronized (mLock) {
userState = getOrCreateUserStateLocked(resolvedUserId);
spooler = userState.getSpoolerLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
PrintJobInfo printJobInfo = spooler.getPrintJobInfo(printJobId, resolvedAppId);
if (printJobInfo == null) {
return;
}
if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
ComponentName printServiceName = printJobInfo.getPrinterId().getServiceName();
RemotePrintService printService = null;
synchronized (mLock) {
printService = userState.getActiveServicesLocked().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.
spooler.setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED, null);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void restartPrintJob(int printJobId, int appId, int userId) {
final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final RemotePrintSpooler spooler;
synchronized (mLock) {
spooler = getOrCreateUserStateLocked(resolvedUserId).getSpoolerLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
return;
}
spooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null);
} 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);
}
}
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;
}
}
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);
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) {
synchronized (mLock) {
if (newUserId == mCurrentUserId) {
return;
}
mCurrentUserId = newUserId;
UserState userState = getCurrentUserStateLocked();
userState.updateIfNeededLocked();
userState.getSpoolerLocked().start();
}
}
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.");
}
}