diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index ece88416af167..cd1e88223de5b 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1218,10 +1218,17 @@ public abstract class ContentResolver { */ public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer) + { + registerContentObserver(uri, notifyForDescendents, observer, UserHandle.myUserId()); + } + + /** @hide - designated user version */ + public final void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer, int userHandle) { try { getContentService().registerContentObserver(uri, notifyForDescendents, - observer.getContentObserver()); + observer.getContentObserver(), userHandle); } catch (RemoteException e) { } } @@ -1276,10 +1283,21 @@ 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()); + } + + /** + * Notify registered observers within the designated user(s) that a row was updated. + * + * @hide + */ + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork, + int userHandle) { try { getContentService().notifyChange( uri, observer == null ? null : observer.getContentObserver(), - observer != null && observer.deliverSelfNotifications(), syncToNetwork); + observer != null && observer.deliverSelfNotifications(), syncToNetwork, + userHandle); } catch (RemoteException e) { } } diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 472fe9480bfa3..0f6488a41a26d 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -17,6 +17,7 @@ package android.content; import android.accounts.Account; +import android.app.ActivityManager; import android.database.IContentObserver; import android.database.sqlite.SQLiteException; import android.net.Uri; @@ -33,6 +34,7 @@ import android.Manifest; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -138,17 +140,47 @@ public final class ContentService extends IContentService.Stub { getSyncManager(); } - public void registerContentObserver(Uri uri, boolean notifyForDescendents, - IContentObserver observer) { + /** + * Register a content observer tied to a specific user's view of the provider. + * @param userHandle the user whose view of the provider is to be observed. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly handled; all other pseudousers are forbidden. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer, int userHandle) { if (observer == null || uri == null) { throw new IllegalArgumentException("You must pass a valid uri and observer"); } - synchronized (mRootNode) { - mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode, - Binder.getCallingUid(), Binder.getCallingPid()); - if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri + - " with notifyForDescendents " + notifyForDescendents); + + 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) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for registerContentObserver: " + + userHandle); + } + } + + synchronized (mRootNode) { + mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode, + Binder.getCallingUid(), Binder.getCallingPid(), userHandle); + if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri + + " with notifyForDescendants " + notifyForDescendants); + } + } + + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer) { + registerContentObserver(uri, notifyForDescendants, observer, + UserHandle.getCallingUserId()); } public void unregisterContentObserver(IContentObserver observer) { @@ -161,14 +193,39 @@ public final class ContentService extends IContentService.Stub { } } + /** + * Notify observers of a particular user's view of the provider. + * @param userHandle the user whose view of the provider is to be notified. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly interpreted; no other pseudousers are allowed. + */ + @Override public void notifyChange(Uri uri, IContentObserver observer, - boolean observerWantsSelfNotifications, boolean syncToNetwork) { + boolean observerWantsSelfNotifications, boolean syncToNetwork, + int userHandle) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Notifying update of " + uri + " from observer " + observer - + ", syncToNetwork " + syncToNetwork); + Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle + + " from observer " + observer + ", syncToNetwork " + syncToNetwork); + } + + // Notify for any user other than the caller's own requires permission. + final int callingUserHandle = UserHandle.getCallingUserId(); + if (userHandle != callingUserHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to notify other users"); + } + + // We passed the permission check; resolve pseudouser targets as appropriate + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for notifyChange: " + + userHandle); + } } - int userId = UserHandle.getCallingUserId(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. long identityToken = clearCallingIdentity(); @@ -176,7 +233,7 @@ public final class ContentService extends IContentService.Stub { ArrayList calls = new ArrayList(); synchronized (mRootNode) { mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications, - calls); + userHandle, calls); } final int numCalls = calls.size(); for (int i=0; i calls = new ArrayList(); for (int i = nums.length - 1; i >=0; --i) { - root.collectObserversLocked(uris[i], 0, null, false, calls); + root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls); assertEquals(nums[i], calls.size()); calls.clear(); } } public void testUriNotNotify() { + final int myUserHandle = UserHandle.myUserId(); + ObserverNode root = new ObserverNode(""); Uri[] uris = new Uri[] { Uri.parse("content://c/"), @@ -77,13 +84,14 @@ public class ObserverNodeTest extends AndroidTestCase { int[] nums = new int[] {7, 1, 3, 3, 1, 1, 1, 1}; for(int i = 0; i < uris.length; i++) { - root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), false, root, 0, 0); + root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), false, root, + 0, 0, myUserHandle); } ArrayList calls = new ArrayList(); for (int i = uris.length - 1; i >=0; --i) { - root.collectObserversLocked(uris[i], 0, null, false, calls); + root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls); assertEquals(nums[i], calls.size()); calls.clear(); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 7cc5decdd7ae5..a0ae9e31ac730 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -317,13 +317,14 @@ public class SettingsProvider extends ContentProvider { boolean backedUpDataChanged = false; String property = null, table = uri.getPathSegments().get(0); + final boolean isGlobal = table.equals(TABLE_GLOBAL); if (table.equals(TABLE_SYSTEM)) { property = Settings.System.SYS_PROP_SETTING_VERSION + '_' + userHandle; backedUpDataChanged = true; } else if (table.equals(TABLE_SECURE)) { property = Settings.Secure.SYS_PROP_SETTING_VERSION + '_' + userHandle; backedUpDataChanged = true; - } else if (table.equals(TABLE_GLOBAL)) { + } else if (isGlobal) { property = Settings.Global.SYS_PROP_SETTING_VERSION; // this one is global backedUpDataChanged = true; } @@ -342,8 +343,9 @@ public class SettingsProvider extends ContentProvider { String notify = uri.getQueryParameter("notify"); if (notify == null || "true".equals(notify)) { - getContext().getContentResolver().notifyChange(uri, null); - if (LOCAL_LOGV) Log.v(TAG, "notifying: " + uri); + final int notifyTarget = isGlobal ? UserHandle.USER_ALL : userHandle; + getContext().getContentResolver().notifyChange(uri, null, true, notifyTarget); + if (LOCAL_LOGV) Log.v(TAG, "notifying for " + notifyTarget + ": " + uri); } else { if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri); }