From b7564454297ba1706670ccab0562cac6676d0a77 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Wed, 19 Sep 2012 16:21:18 -0700 Subject: [PATCH] Multiuser awareness in content observer infrastructure Content observers are registered under the calling user's identity, not under the provider host's identity (unless the caller is using the new permissioned entry points that allow observers to be registered for a different user's view of the data). The most important implication of this is that when the settings provider is directly queried, the Cursor returned to the app is wired for change notifications based on that calling app's user. Note that it is not possible to use query() / insert() to read/write data for different users. All such manipulations should use the standard get* / put* methods in Settings.*. Bug 7122169 Change-Id: If5d9ec44927e5e56e4e7635438f4ef48a5430986 --- .../java/android/content/ContentResolver.java | 4 +- core/java/android/content/ContentService.java | 4 - .../providers/settings/SettingsProvider.java | 100 +++++++++--------- 3 files changed, 53 insertions(+), 55 deletions(-) diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index cd1e88223de5b..4ab8272f2f84a 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1219,7 +1219,7 @@ public abstract class ContentResolver { public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer) { - registerContentObserver(uri, notifyForDescendents, observer, UserHandle.myUserId()); + registerContentObserver(uri, notifyForDescendents, observer, UserHandle.getCallingUserId()); } /** @hide - designated user version */ @@ -1283,7 +1283,7 @@ public abstract class ContentResolver { * @see #requestSync(android.accounts.Account, String, android.os.Bundle) */ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { - notifyChange(uri, observer, syncToNetwork, UserHandle.myUserId()); + notifyChange(uri, observer, syncToNetwork, UserHandle.getCallingUserId()); } /** diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 5986dcd25936c..0f6488a41a26d 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -154,15 +154,11 @@ public final class ContentService extends IContentService.Stub { throw new IllegalArgumentException("You must pass a valid uri and observer"); } - // STOPSHIP: disable the multi-user permission checks until a solid fix for the - // content provider / observer case is in place. - /* final int callingUser = UserHandle.getCallingUserId(); if (callingUser != userHandle) { mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, "no permission to observe other users' provider view"); } - */ if (userHandle < 0) { if (userHandle == UserHandle.USER_CURRENT) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 9839c16569fb8..f0b8812937103 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import android.app.ActivityManager; -import android.app.ActivityManagerNative; import android.app.backup.BackupManager; import android.content.BroadcastReceiver; import android.content.ContentProvider; @@ -33,20 +32,17 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteQueryBuilder; -import android.database.sqlite.SQLiteStatement; import android.media.RingtoneManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.FileObserver; import android.os.ParcelFileDescriptor; -import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -656,53 +652,59 @@ public class SettingsProvider extends ContentProvider { } } - // Note: we assume that get/put operations for moved-to-global names have already - // been directed to the new location on the caller side (otherwise we'd fix them - // up here). + // Okay, permission checks have cleared. Reset to our own identity so we can + // manipulate all users' data with impunity. + long oldId = Binder.clearCallingIdentity(); + try { + // Note: we assume that get/put operations for moved-to-global names have already + // been directed to the new location on the caller side (otherwise we'd fix them + // up here). + DatabaseHelper dbHelper; + SettingsCache cache; - DatabaseHelper dbHelper; - SettingsCache cache; + // Get methods + if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) { + if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser); + dbHelper = getOrEstablishDatabase(callingUser); + cache = sSystemCaches.get(callingUser); + return lookupValue(dbHelper, TABLE_SYSTEM, cache, request); + } + if (Settings.CALL_METHOD_GET_SECURE.equals(method)) { + if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser); + dbHelper = getOrEstablishDatabase(callingUser); + cache = sSecureCaches.get(callingUser); + return lookupValue(dbHelper, TABLE_SECURE, cache, request); + } + if (Settings.CALL_METHOD_GET_GLOBAL.equals(method)) { + if (LOCAL_LOGV) Slog.v(TAG, "call(global:" + request + ") for " + callingUser); + // fast path: owner db & cache are immutable after onCreate() so we need not + // guard on the attempt to look them up + return lookupValue(getOrEstablishDatabase(UserHandle.USER_OWNER), TABLE_GLOBAL, + sGlobalCache, request); + } - // Get methods - if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser); - dbHelper = getOrEstablishDatabase(callingUser); - cache = sSystemCaches.get(callingUser); - return lookupValue(dbHelper, TABLE_SYSTEM, cache, request); - } - if (Settings.CALL_METHOD_GET_SECURE.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser); - dbHelper = getOrEstablishDatabase(callingUser); - cache = sSecureCaches.get(callingUser); - return lookupValue(dbHelper, TABLE_SECURE, cache, request); - } - if (Settings.CALL_METHOD_GET_GLOBAL.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call(global:" + request + ") for " + callingUser); - // fast path: owner db & cache are immutable after onCreate() so we need not - // guard on the attempt to look them up - return lookupValue(getOrEstablishDatabase(UserHandle.USER_OWNER), TABLE_GLOBAL, - sGlobalCache, request); - } + // Put methods - new value is in the args bundle under the key named by + // the Settings.NameValueTable.VALUE static. + final String newValue = (args == null) + ? null : args.getString(Settings.NameValueTable.VALUE); - // Put methods - new value is in the args bundle under the key named by - // the Settings.NameValueTable.VALUE static. - final String newValue = (args == null) - ? null : args.getString(Settings.NameValueTable.VALUE); - - final ContentValues values = new ContentValues(); - values.put(Settings.NameValueTable.NAME, request); - values.put(Settings.NameValueTable.VALUE, newValue); - if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for " + callingUser); - insertForUser(Settings.System.CONTENT_URI, values, callingUser); - } else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for " + callingUser); - insertForUser(Settings.Secure.CONTENT_URI, values, callingUser); - } else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for " + callingUser); - insertForUser(Settings.Global.CONTENT_URI, values, callingUser); - } else { - Slog.w(TAG, "call() with invalid method: " + method); + final ContentValues values = new ContentValues(); + values.put(Settings.NameValueTable.NAME, request); + values.put(Settings.NameValueTable.VALUE, newValue); + if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) { + if (LOCAL_LOGV) Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for " + callingUser); + insertForUser(Settings.System.CONTENT_URI, values, callingUser); + } else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) { + if (LOCAL_LOGV) Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for " + callingUser); + insertForUser(Settings.Secure.CONTENT_URI, values, callingUser); + } else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) { + if (LOCAL_LOGV) Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for " + callingUser); + insertForUser(Settings.Global.CONTENT_URI, values, callingUser); + } else { + Slog.w(TAG, "call() with invalid method: " + method); + } + } finally { + Binder.restoreCallingIdentity(oldId); } return null; @@ -758,7 +760,7 @@ public class SettingsProvider extends ContentProvider { return queryForUser(url, select, where, whereArgs, sort, UserHandle.getCallingUserId()); } - public Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs, + private Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs, String sort, int forUser) { if (LOCAL_LOGV) Slog.v(TAG, "query(" + url + ") for user " + forUser); SqlArguments args = new SqlArguments(url, where, whereArgs);