1. Now a user state has ins own spooler since the spooler app is running per user. The user state registers an observer for the state of the spooler to get information needed to orchestrate unbinding from print serivces that have no work and eventually unbinding from the spooler when all no service has any work. 2. Abstracted a remote print service from the perspective of the system in a class that is transparently managing binding and unbinding to the remote instance. 3. Abstracted the remote print spooler to transparently manage binding and unbinding to the remote instance when there is work and when there is no work, respectively. 4. Cleaned up the print document adapter (ex-PrintAdapter) APIs to enable implementing the all callbacks on a thread of choice. If the document is really small, using the main thread makes sense. Now if an app that does not need the UI state to layout the printed content, it can schedule all the work for allocating resources, laying out, writing, and releasing resources on a dedicated thread. 5. Added info class for the printed document that is now propagated the the print services. A print service gets an instance of a new document class that encapsulates the document info and a method to access the document's data. 6. Added APIs for describing the type of a document to the new document info class. This allows a print service to do smarts based on the doc type. For now we have only photo and document types. 7. Renamed the systemReady method for system services that implement it with different semantics to systemRunning. Such methods assume the the service can run third-party code which is not the same as systemReady. 8. Cleaned up the print job configuration activity. 9. Sigh... code clean up here and there. Factoring out classes to improve readability. Change-Id: I637ba28412793166cbf519273fdf022241159a92
368 lines
15 KiB
Java
368 lines
15 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 {
|
|
if (spooler.cancelPrintJob(printJobId, resolvedAppId)) {
|
|
return;
|
|
}
|
|
PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
|
|
if (printJobInfo == null) {
|
|
return;
|
|
}
|
|
ComponentName printServiceName = printJobInfo.getPrinterId().getService();
|
|
RemotePrintService printService = null;
|
|
synchronized (mLock) {
|
|
printService = userState.getActiveServices().get(printServiceName);
|
|
}
|
|
if (printService == null) {
|
|
return;
|
|
}
|
|
printService.onRequestCancelPrintJob(printJobInfo);
|
|
} 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.");
|
|
}
|
|
}
|