1. Added support for selecting a printer from the all printers activity that is not in the initial printer selection drop down. The user initially sees a sub set of the printers in the drop down and the last option is to see all printers in a separate activity. Some of the printers in the all printers activity are not shown in the initial drop down. 2. Refactored printer discovery by adding (private for now) printer discovery app facing APIs. These APIs are needed to support multiple printer selection activities (print dialog and all printers activities) and also the settings for showing all printers for a service. Now multiple apps can request observing for printers and there is a centralized mediator that ensures the same printer discovery session is used. The mediator dispatches printer discovery specific requests to print services. It also aggregates discovered printers and delivers them to the interested apps. The mediator minimizes printer discovery session creation and starting and stopping discovery by sharing the same discovery session and discovery window with multiple apps. Lastly, the mediator takes care of print services enabled during discovery by bringing them up to the current discovery state (create discovery session and start discovery if needed). The mediator also reports disappearing of the printers of a service removed during discovery and notifies a newly registered observers for the currnet printers if the observers are added during an active printer discovery session. 3. Fixed bugs in the print UI and implemented some UX tweaks. Change-Id: I4d0b0c5a6c6f1809b2ba5dbc8e9d63ab3d48f1ef
471 lines
18 KiB
Java
471 lines
18 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 requestPrinterUpdate(PrinterId printerId, int userId) {
|
|
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
|
|
final UserState userState;
|
|
synchronized (mLock) {
|
|
userState = getOrCreateUserStateLocked(resolvedUserId);
|
|
}
|
|
final long identity = Binder.clearCallingIdentity();
|
|
try {
|
|
userState.requestPrinterUpdate(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(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.");
|
|
}
|
|
}
|