1. The UI of a printing app was freezing a little when calling the print method since the print manager service was waiting for it to bind to the print spooler which generated the print job id (and the initial print job info really). Now the print manager service is responsible for job id generation and does not not wait for the print spooler to spin. Hence, the app UI is not blocked at all. Note that the print manager initiates the binding to the spooler and as soon as it completes the spooler shows the print UI which is hosted in its process. It is not possible to show the print UI before the system is bound to the spooler since during this binding the system passes a callback to the spooler so the latter can talk to the system. 2. Changed the print job id to be an opaque class allowing us to vary the way we generate print job ids in the future. 3. The queued print job state was hidden but the print job returned by the print method of the print manager is in that state. Now now hidden. 4. We were incorrecly removing print job infos if they are completed or cancelled. Doing that is problematic since the print job returned by the print method allows the app to query for the job info after the job has been say completed. Hence, an app can initiate printing and get a print job whose state is "created" and hold onto it until after the job is completed, now if the app asks for the print job info it will get an info in "created" state even though the job is "completed" since the spooler was not retaining the completed jobs. Now the spooler removes the PDF files for the completed and cancelled print jobs but keeps around the infos (also persisting them to disc) so it can answer questions about them. On first boot or switch to a user we purge the persisted print jobs in completed/cancelled state since they are obsolete - no app can have a handle to them. 5. Removed the print method that takes a file since we have a public PrintDocumentAdapter implementation for printing files. Once can instantiate a PrintFileDocumentAdapter and pass it to the print method. This class also allows overriding of the finish method to know when the data is spooled and deleted the file if desired, etc. 6. Replaced the wrong code to slice a large list of parcelables to use ParceledListSlice class. bug:10748093 Change-Id: I1ebeeb47576e88fce550851cdd3e401fcede6e2b
526 lines
20 KiB
Java
526 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.PrintJobId;
|
|
import android.print.PrintJobInfo;
|
|
import android.print.PrinterId;
|
|
import android.printservice.PrintServiceInfo;
|
|
import android.provider.Settings;
|
|
import android.util.SparseArray;
|
|
|
|
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 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 PrintJobInfo print(String printJobName, final IPrintClient client,
|
|
final IPrintDocumentAdapter documentAdapter, PrintAttributes attributes,
|
|
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.print(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;
|
|
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 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 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) {
|
|
pw.println("PRINT MANAGER STATE (dumpsys print)");
|
|
final int userStateCount = mUserStates.size();
|
|
for (int i = 0; i < userStateCount; i++) {
|
|
UserState userState = mUserStates.get(i);
|
|
userState.dump(fd, pw, "");
|
|
pw.println();
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
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.");
|
|
}
|
|
}
|