Merge "Per-user content observer APIs" into jb-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f1aa107b18
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user