Files
frameworks_base/services/java/com/android/server/print/PrintManagerService.java
Svetoslav Ganov 8c43376ea8 First cut of the print notifications.
1. Added notifications for a queued print job, for a started print job,
       for ongoing canceling a print job, and for a failed print job. The
       notifications for queued and started state have a cancel action. The
       notification for failed print job has a cancel and a restart action.

    2. Propagating failure message from the print service to the notifications.

    3. PrintJobConfigActivity was not setting the initial value for the
       print job copies and was not updating the UI immediately after creation.

    4. Refactored PrintJobConfigActivity to avoid using the hack to avoid
       reaction for item selection change in a spinner for an event that
       happened before the callback was registered.

    5. Removed the label attribute from PrinterInfo and now PrinterId is
       composed of the printer name and the service component name. This
       is nice since for restarting print jobs we do not need to store
       information about the printer except the printer id which is
       already part of the PrintJobInfo's data. Also the printer name
       is not expected to change anyway.

    6. Allowing cancellation of a queued print job. Also no print job is
       cancelled without asking the managing print service to do that.
       Before we were immediately canceling print jobs in queued state
       but it was possible for a buggy print service to not set the
       print job state to started before starting to do expensive work
       that will not be canceled.

    7. PrintServiceInfo was throwing an exception the the meta-data
       XML for the print service was not well-formed which would crash
       the system process. Now we just ignore not well-formed meta-data.

    8. Removed unused permissions from the PrintSpooler's manifest.

Change-Id: Iba2dd14b487f56e137b90d1da17c3033422ab5e6
2013-08-02 14:22:22 -07:00

391 lines
16 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.PrintAttributes;
import android.print.PrintJobInfo;
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().notifyClientForActivteJobs();
}
}
});
}
@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 = getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
if (printJobInfo == null) {
return;
}
if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
ComponentName printServiceName = printJobInfo.getPrinterId().getServiceName();
RemotePrintService printService = null;
synchronized (mLock) {
printService = userState.getActiveServices().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);
}
}
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().notifyClientForActivteJobs();
}
}
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(Manifest.permission.ACCESS_ALL_PRINT_JOBS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Call from app " + callingAppId + " as app "
+ appId + " without 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.");
}
}