Multi-user - wallpaper service

- Allow each user to have their own wallpaper (live or static).
- Migrate old wallpaper on upgrade.
- Update SystemBackupAgent to backup/restore from primary user's
  new wallpaper directory.

Reduce dependency on Binder.getOrigCallingUser() by passing the
userId for bindService.

Change-Id: I19c8c3296d3d2efa7f28f951d4b84407489e2166
This commit is contained in:
Amith Yamasani
2012-02-06 12:04:42 -08:00
parent 11ca31729c
commit 37ce3a8af6
16 changed files with 612 additions and 336 deletions

View File

@ -670,8 +670,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String resolvedType = data.readString();
b = data.readStrongBinder();
int fl = data.readInt();
int userId = data.readInt();
IServiceConnection conn = IServiceConnection.Stub.asInterface(b);
int res = bindService(app, token, service, resolvedType, conn, fl);
int res = bindService(app, token, service, resolvedType, conn, fl, userId);
reply.writeNoException();
reply.writeInt(res);
return true;
@ -2288,7 +2289,7 @@ class ActivityManagerProxy implements IActivityManager
}
public int bindService(IApplicationThread caller, IBinder token,
Intent service, String resolvedType, IServiceConnection connection,
int flags) throws RemoteException {
int flags, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@ -2298,6 +2299,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(resolvedType);
data.writeStrongBinder(connection.asBinder());
data.writeInt(flags);
data.writeInt(userId);
mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();

View File

@ -1125,6 +1125,12 @@ class ContextImpl extends Context {
@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
return bindService(service, conn, flags, UserId.getUserId(Process.myUid()));
}
/** @hide */
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
IServiceConnection sd;
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
@ -1143,7 +1149,7 @@ class ContextImpl extends Context {
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
sd, flags);
sd, flags, userId);
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);

View File

@ -166,7 +166,7 @@ public interface IActivityManager extends IInterface {
int id, Notification notification, boolean keepNotification) throws RemoteException;
public int bindService(IApplicationThread caller, IBinder token,
Intent service, String resolvedType,
IServiceConnection connection, int flags) throws RemoteException;
IServiceConnection connection, int flags, int userId) throws RemoteException;
public boolean unbindService(IServiceConnection connection) throws RemoteException;
public void publishService(IBinder token,
Intent intent, IBinder service) throws RemoteException;

View File

@ -30,6 +30,7 @@ import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;

View File

@ -38,15 +38,23 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
private static final boolean DEBUG = false;
// This path must match what the WallpaperManagerService uses
private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper";
// TODO: Will need to change if backing up non-primary user's wallpaper
public static final String WALLPAPER_IMAGE = "/data/system/users/0/wallpaper";
public static final String WALLPAPER_INFO = "/data/system/users/0/wallpaper_info.xml";
// Use old keys to keep legacy data compatibility and avoid writing two wallpapers
public static final String WALLPAPER_IMAGE_KEY =
"/data/data/com.android.settings/files/wallpaper";
public static final String WALLPAPER_INFO_KEY = "/data/system/wallpaper_info.xml";
// Stage file - should be adjacent to the WALLPAPER_IMAGE location. The wallpapers
// will be saved to this file from the restore stream, then renamed to the proper
// location if it's deemed suitable.
private static final String STAGE_FILE = "/data/data/com.android.settings/files/wallpaper-tmp";
// TODO: Will need to change if backing up non-primary user's wallpaper
private static final String STAGE_FILE = "/data/system/users/0/wallpaper-tmp";
Context mContext;
String[] mFiles;
String[] mKeys;
double mDesiredMinWidth;
double mDesiredMinHeight;
@ -57,11 +65,12 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
* @param context
* @param files
*/
public WallpaperBackupHelper(Context context, String... files) {
public WallpaperBackupHelper(Context context, String[] files, String[] keys) {
super(context);
mContext = context;
mFiles = files;
mKeys = keys;
WallpaperManager wpm;
wpm = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
@ -89,7 +98,7 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
*/
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
performBackup_checked(oldState, data, newState, mFiles, mFiles);
performBackup_checked(oldState, data, newState, mFiles, mKeys);
}
/**
@ -99,8 +108,8 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
*/
public void restoreEntity(BackupDataInputStream data) {
final String key = data.getKey();
if (isKeyInList(key, mFiles)) {
if (key.equals(WALLPAPER_IMAGE)) {
if (isKeyInList(key, mKeys)) {
if (key.equals(WALLPAPER_IMAGE_KEY)) {
// restore the file to the stage for inspection
File f = new File(STAGE_FILE);
if (writeFile(f, data)) {
@ -135,9 +144,9 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
f.delete();
}
}
} else {
// Some other normal file; just decode it to its destination
File f = new File(key);
} else if (key.equals(WALLPAPER_INFO_KEY)) {
// XML file containing wallpaper info
File f = new File(WALLPAPER_INFO);
writeFile(f, data);
}
}

View File

@ -1306,6 +1306,15 @@ public abstract class Context {
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);
/**
* Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userId
* argument for use by system server and other multi-user aware code.
* @hide
*/
public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
/**
* Disconnect from an application service. You will no longer receive
* calls as the service is restarted, and the service is now allowed to

View File

@ -370,6 +370,12 @@ public class ContextWrapper extends Context {
return mBase.bindService(service, conn, flags);
}
/** @hide */
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
return mBase.bindService(service, conn, flags, userId);
}
@Override
public void unbindService(ServiceConnection conn) {
mBase.unbindService(conn);

View File

@ -3380,7 +3380,8 @@ public class PackageParser {
public static final ServiceInfo generateServiceInfo(Service s, int flags, int userId) {
if (s == null) return null;
if (!copyNeeded(flags, s.owner, s.metaData) && userId == 0) {
if (!copyNeeded(flags, s.owner, s.metaData)
&& userId == UserId.getUserId(s.info.applicationInfo.uid)) {
return s.info;
}
// Make shallow copies so we can store the metadata safely

View File

@ -73,6 +73,10 @@ public final class UserId {
}
}
public static final int getCallingUserId() {
return getUserId(Binder.getCallingUid());
}
/**
* Returns the uid that is composed from the userId and the appId.
* @hide

View File

@ -52,7 +52,6 @@ writeEntityHeader_native(JNIEnv* env, jobject clazz, int w, jstring key, int dat
if (keyUTF == NULL) {
return -1;
}
err = writer->WriteEntityHeader(String8(keyUTF), dataSize);
env->ReleaseStringUTFChars(key, keyUTF);

View File

@ -573,12 +573,13 @@ class AppWidgetServiceImpl {
mBoundRemoteViewsServices.remove(key);
}
int userId = UserId.getUserId(id.provider.uid);
// Bind to the RemoteViewsService (which will trigger a callback to the
// RemoteViewsAdapter.onServiceConnected())
final long token = Binder.clearCallingIdentity();
try {
conn = new ServiceConnectionProxy(key, connection);
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId);
mBoundRemoteViewsServices.put(key, conn);
} finally {
Binder.restoreCallingIdentity(token);
@ -638,11 +639,11 @@ class AppWidgetServiceImpl {
// Check if we need to destroy any services (if no other app widgets are
// referencing the same service)
decrementAppWidgetServiceRefCount(appWidgetId);
decrementAppWidgetServiceRefCount(id);
}
// Destroys the cached factory on the RemoteViewsService's side related to the specified intent
private void destroyRemoteViewsService(final Intent intent) {
private void destroyRemoteViewsService(final Intent intent, AppWidgetId id) {
final ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@ -663,11 +664,12 @@ class AppWidgetServiceImpl {
}
};
int userId = UserId.getUserId(id.provider.uid);
// Bind to the service and remove the static intent->factory mapping in the
// RemoteViewsService.
final long token = Binder.clearCallingIdentity();
try {
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
@ -687,16 +689,16 @@ class AppWidgetServiceImpl {
// Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
// the ref-count reaches zero.
private void decrementAppWidgetServiceRefCount(int appWidgetId) {
private void decrementAppWidgetServiceRefCount(AppWidgetId id) {
Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator();
while (it.hasNext()) {
final FilterComparison key = it.next();
final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
if (ids.remove(appWidgetId)) {
if (ids.remove(id.appWidgetId)) {
// If we have removed the last app widget referencing this service, then we
// should destroy it and remove it from this set
if (ids.isEmpty()) {
destroyRemoteViewsService(key.getIntent());
destroyRemoteViewsService(key.getIntent(), id);
it.remove();
}
}
@ -888,10 +890,11 @@ class AppWidgetServiceImpl {
}
};
int userId = UserId.getUserId(id.provider.uid);
// Bind to the service and call onDataSetChanged()
final long token = Binder.clearCallingIdentity();
try {
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
@ -1343,7 +1346,6 @@ class AppWidgetServiceImpl {
void readStateFromFileLocked(FileInputStream stream) {
boolean success = false;
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, null);
@ -1475,7 +1477,7 @@ class AppWidgetServiceImpl {
}
AtomicFile savedStateFile() {
int userId = Binder.getOrigCallingUser();
int userId = UserId.getCallingUserId();
File dir = new File("/data/system/users/" + userId);
File settingsFile = new File(dir, SETTINGS_FILENAME);
if (!dir.exists()) {

View File

@ -44,12 +44,16 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final String WALLPAPER_IMAGE_FILENAME = "wallpaper";
private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml";
private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files";
private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/" + WALLPAPER_IMAGE_FILENAME;
private static final String WALLPAPER_INFO_DIR = "/data/system";
private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/" + WALLPAPER_INFO_FILENAME;
// TODO: Will need to change if backing up non-primary user's wallpaper
private static final String WALLPAPER_IMAGE_DIR = "/data/system/users/0";
private static final String WALLPAPER_IMAGE = WallpaperBackupHelper.WALLPAPER_IMAGE;
// TODO: Will need to change if backing up non-primary user's wallpaper
private static final String WALLPAPER_INFO_DIR = "/data/system/users/0";
private static final String WALLPAPER_INFO = WallpaperBackupHelper.WALLPAPER_INFO;
// Use old keys to keep legacy data compatibility and avoid writing two wallpapers
private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY;
private static final String WALLPAPER_INFO_KEY = WallpaperBackupHelper.WALLPAPER_INFO_KEY;
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
@ -58,13 +62,15 @@ public class SystemBackupAgent extends BackupAgentHelper {
WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService(
Context.WALLPAPER_SERVICE);
String[] files = new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO };
if (wallpaper != null && wallpaper.mName != null && wallpaper.mName.length() > 0) {
String[] keys = new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY };
if (wallpaper != null && wallpaper.getName() != null && wallpaper.getName().length() > 0) {
// When the wallpaper has a name, back up the info by itself.
// TODO: Don't rely on the innards of the service object like this!
// TODO: Send a delete for any stored wallpaper image in this case?
files = new String[] { WALLPAPER_INFO };
keys = new String[] { WALLPAPER_INFO_KEY };
}
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files));
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys));
super.onBackup(oldState, data, newState);
}
@ -90,9 +96,11 @@ public class SystemBackupAgent extends BackupAgentHelper {
throws IOException {
// On restore, we also support a previous data schema "system_files"
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this,
new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO }));
new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO },
new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY} ));
addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this,
new String[] { WALLPAPER_IMAGE }));
new String[] { WALLPAPER_IMAGE },
new String[] { WALLPAPER_IMAGE_KEY} ));
try {
super.onRestore(data, appVersionCode, newState);

File diff suppressed because it is too large Load Diff

View File

@ -49,6 +49,7 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.app.WallpaperManager;
import android.app.backup.IBackupManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@ -1363,24 +1364,6 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
final ArrayList mCancelledThumbnails = new ArrayList();
/**
* All of the currently running global content providers. Keys are a
* string containing the provider name and values are a
* ContentProviderRecord object containing the data about it. Note
* that a single provider may be published under multiple names, so
* there may be multiple entries here for a single one in mProvidersByClass.
*/
final HashMap<String, ContentProviderRecord> mProvidersByName
= new HashMap<String, ContentProviderRecord>();
/**
* All of the currently running global content providers. Keys are a
* string containing the provider's implementation class and values are a
* ContentProviderRecord object containing the data about it.
*/
final HashMap<ComponentName, ContentProviderRecord> mProvidersByClass
= new HashMap<ComponentName, ContentProviderRecord>();
final ProviderMap mProviderMap = new ProviderMap();
/**
@ -4434,7 +4417,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
for (ContentProviderRecord provider : mProvidersByClass.values()) {
for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(-1).values()) {
if (provider.info.packageName.equals(name)
&& (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
if (!doit) {
@ -11361,18 +11344,18 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
String resolvedType, int callingPid, int callingUid) {
String resolvedType, int callingPid, int callingUid, int userId) {
ServiceRecord r = null;
if (DEBUG_SERVICE)
Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType
+ " origCallingUid=" + callingUid);
+ " callingUid=" + callingUid);
if (service.getComponent() != null) {
r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser());
r = mServiceMap.getServiceByName(service.getComponent(), userId);
}
if (r == null) {
Intent.FilterComparison filter = new Intent.FilterComparison(service);
r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser());
r = mServiceMap.getServiceByIntent(filter, userId);
}
if (r == null) {
try {
@ -11386,13 +11369,12 @@ public final class ActivityManagerService extends ActivityManagerNative
": not found");
return null;
}
if (Binder.getOrigCallingUser() > 0) {
sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo,
Binder.getOrigCallingUser());
if (userId > 0) {
sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
}
ComponentName name = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser());
r = mServiceMap.getServiceByName(name, userId);
if (r == null) {
Intent.FilterComparison filter = new Intent.FilterComparison(
service.cloneFilter());
@ -11956,7 +11938,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType,
callingPid, callingUid);
callingPid, callingUid, UserId.getUserId(callingUid));
if (res == null) {
return null;
}
@ -12206,13 +12188,15 @@ public final class ActivityManagerService extends ActivityManagerNative
public int bindService(IApplicationThread caller, IBinder token,
Intent service, String resolvedType,
IServiceConnection connection, int flags) {
IServiceConnection connection, int flags, int userId) {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
checkValidCaller(Binder.getCallingUid(), userId);
synchronized(this) {
if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
@ -12262,7 +12246,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType,
Binder.getCallingPid(), Binder.getOrigCallingUid());
Binder.getCallingPid(), Binder.getCallingUid(), userId);
if (res == null) {
return 0;
}
@ -15259,6 +15243,25 @@ public final class ActivityManagerService extends ActivityManagerNative
private int mCurrentUserId;
private SparseIntArray mLoggedInUsers = new SparseIntArray(5);
private ArrayList<UserListener> mUserListeners = new ArrayList<UserListener>(3);
public interface UserListener {
public void onUserChanged(int userId);
public void onUserAdded(int userId);
public void onUserRemoved(int userId);
public void onUserLoggedOut(int userId);
}
public void addUserListener(UserListener listener) {
synchronized (this) {
if (!mUserListeners.contains(listener)) {
mUserListeners.add(listener);
}
}
}
public boolean switchUser(int userId) {
final int callingUid = Binder.getCallingUid();
@ -15269,6 +15272,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mCurrentUserId == userId)
return true;
ArrayList<UserListener> listeners;
synchronized (this) {
// Check if user is already logged in, otherwise check if user exists first before
// adding to the list of logged in users.
@ -15284,6 +15289,12 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!haveActivities) {
startHomeActivityLocked(userId);
}
listeners = (ArrayList<UserListener>) mUserListeners.clone();
}
// Inform the listeners
for (UserListener listener : listeners) {
listener.onUserChanged(userId);
}
return true;
}
@ -15303,6 +15314,12 @@ public final class ActivityManagerService extends ActivityManagerNative
return false;
}
private void checkValidCaller(int uid, int userId) {
if (UserId.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0) return;
throw new SecurityException("Caller uid=" + uid
+ " is not privileged to communicate with user=" + userId);
}
private int applyUserId(int uid, int userId) {
return UserId.getUid(userId, uid);

View File

@ -158,7 +158,7 @@ public class ProviderMap {
}
}
private HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int optionalUserId) {
HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int optionalUserId) {
final int userId = optionalUserId >= 0
? optionalUserId : Binder.getOrigCallingUser();
final HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.get(userId);

View File

@ -333,6 +333,12 @@ public class MockContext extends Context {
throw new UnsupportedOperationException();
}
/** @hide */
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
throw new UnsupportedOperationException();
}
@Override
public void unbindService(ServiceConnection conn) {
throw new UnsupportedOperationException();