Merge "Per-user content observer APIs" into jb-mr1-dev

This commit is contained in:
Christopher Tate
2012-09-17 17:34:00 -07:00
committed by Android (Google) Code Review
5 changed files with 175 additions and 49 deletions

View File

@@ -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) {
}
}

View File

@@ -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<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
calls);
userHandle, calls);
}
final int numCalls = calls.size();
for (int i=0; i<numCalls; i++) {
@@ -207,7 +264,7 @@ public final class ContentService extends IContentService.Stub {
if (syncToNetwork) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null /* all accounts */, userId,
syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle,
uri.getAuthority());
}
}
@@ -216,6 +273,12 @@ public final class ContentService extends IContentService.Stub {
}
}
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, boolean syncToNetwork) {
notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
UserHandle.getCallingUserId());
}
/**
* Hide this class since it is not part of api,
* but current unittest framework requires it to be public
@@ -543,16 +606,18 @@ public final class ContentService extends IContentService.Stub {
public final IContentObserver observer;
public final int uid;
public final int pid;
public final boolean notifyForDescendents;
public final boolean notifyForDescendants;
private final int userHandle;
private final Object observersLock;
public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
int _uid, int _pid) {
int _uid, int _pid, int _userHandle) {
this.observersLock = observersLock;
observer = o;
uid = _uid;
pid = _pid;
notifyForDescendents = n;
userHandle = _userHandle;
notifyForDescendants = n;
try {
observer.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -571,7 +636,8 @@ public final class ContentService extends IContentService.Stub {
pidCounts.put(pid, pidCounts.get(pid)+1);
pw.print(prefix); pw.print(name); pw.print(": pid=");
pw.print(pid); pw.print(" uid=");
pw.print(uid); pw.print(" target=");
pw.print(uid); pw.print(" user=");
pw.print(userHandle); pw.print(" target=");
pw.println(Integer.toHexString(System.identityHashCode(
observer != null ? observer.asBinder() : null)));
}
@@ -639,17 +705,21 @@ public final class ContentService extends IContentService.Stub {
return uri.getPathSegments().size() + 1;
}
// Invariant: userHandle is either a hard user number or is USER_ALL
public void addObserverLocked(Uri uri, IContentObserver observer,
boolean notifyForDescendents, Object observersLock, int uid, int pid) {
addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock, uid, pid);
boolean notifyForDescendants, Object observersLock,
int uid, int pid, int userHandle) {
addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
uid, pid, userHandle);
}
private void addObserverLocked(Uri uri, int index, IContentObserver observer,
boolean notifyForDescendents, Object observersLock, int uid, int pid) {
boolean notifyForDescendants, Object observersLock,
int uid, int pid, int userHandle) {
// If this is the leaf node add the observer
if (index == countUriSegments(uri)) {
mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock,
uid, pid));
mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
uid, pid, userHandle));
return;
}
@@ -662,8 +732,8 @@ public final class ContentService extends IContentService.Stub {
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (node.mName.equals(segment)) {
node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
observersLock, uid, pid);
node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
observersLock, uid, pid, userHandle);
return;
}
}
@@ -671,8 +741,8 @@ public final class ContentService extends IContentService.Stub {
// No child found, create one
ObserverNode node = new ObserverNode(segment);
mChildren.add(node);
node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
observersLock, uid, pid);
node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
observersLock, uid, pid, userHandle);
}
public boolean removeObserverLocked(IContentObserver observer) {
@@ -705,37 +775,49 @@ public final class ContentService extends IContentService.Stub {
}
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
boolean observerWantsSelfNotifications, int targetUserHandle,
ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
ObserverEntry entry = mObservers.get(i);
// Don't notify the observer if it sent the notification and isn't interesed
// Don't notify the observer if it sent the notification and isn't interested
// in self notifications
boolean selfChange = (entry.observer.asBinder() == observerBinder);
if (selfChange && !observerWantsSelfNotifications) {
continue;
}
// Make sure the observer is interested in the notification
if (leaf || (!leaf && entry.notifyForDescendents)) {
calls.add(new ObserverCall(this, entry.observer, selfChange));
// Does this observer match the target user?
if (targetUserHandle == UserHandle.USER_ALL
|| entry.userHandle == UserHandle.USER_ALL
|| targetUserHandle == entry.userHandle) {
// Make sure the observer is interested in the notification
if (leaf || (!leaf && entry.notifyForDescendants)) {
calls.add(new ObserverCall(this, entry.observer, selfChange));
}
}
}
}
/**
* targetUserHandle is either a hard user handle or is USER_ALL
*/
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
boolean observerWantsSelfNotifications, int targetUserHandle,
ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
collectMyObserversLocked(true, observer, observerWantsSelfNotifications, calls);
collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
// Notify any observers at this level who are interested in descendents
collectMyObserversLocked(false, observer, observerWantsSelfNotifications, calls);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
targetUserHandle, calls);
}
int N = mChildren.size();
@@ -744,7 +826,7 @@ public final class ContentService extends IContentService.Stub {
if (segment == null || node.mName.equals(segment)) {
// We found the child,
node.collectObserversLocked(uri, index + 1,
observer, observerWantsSelfNotifications, calls);
observer, observerWantsSelfNotifications, targetUserHandle, calls);
if (segment != null) {
break;
}

View File

@@ -30,12 +30,28 @@ import android.database.IContentObserver;
* @hide
*/
interface IContentService {
void registerContentObserver(in Uri uri, boolean notifyForDescendentsn,
IContentObserver observer);
void unregisterContentObserver(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.
*/
void registerContentObserver(in Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle);
/**
* 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
* USER_CURRENT are properly interpreted.
*/
void notifyChange(in Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, boolean syncToNetwork);
boolean observerWantsSelfNotifications, boolean syncToNetwork,
int userHandle);
void requestSync(in Account account, String authority, in Bundle extras);
void cancelSync(in Account account, String authority);

View File

@@ -23,6 +23,7 @@ import android.content.ContentService.ObserverNode;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.test.AndroidTestCase;
public class ObserverNodeTest extends AndroidTestCase {
@@ -33,6 +34,8 @@ public class ObserverNodeTest extends AndroidTestCase {
}
public void testUri() {
final int myUserHandle = UserHandle.myUserId();
ObserverNode root = new ObserverNode("");
Uri[] uris = new Uri[] {
Uri.parse("content://c/a/"),
@@ -48,21 +51,25 @@ public class ObserverNodeTest extends AndroidTestCase {
int[] nums = new int[] {4, 7, 1, 4, 2, 2, 3, 3};
// special case
root.addObserverLocked(uris[0], new TestObserver().getContentObserver(), false, root, 0, 0);
root.addObserverLocked(uris[0], new TestObserver().getContentObserver(), false, root,
0, 0, myUserHandle);
for(int i = 1; i < uris.length; i++) {
root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), true, root, 0, 0);
root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), true, root,
0, 0, myUserHandle);
}
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
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<ObserverCall> calls = new ArrayList<ObserverCall>();
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();
}

View File

@@ -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);
}