summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/ContentService.java
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2012-09-17 16:23:44 -0700
committerChristopher Tate <ctate@google.com>2012-09-17 16:35:36 -0700
commit16aa9736175f5bbe924a6e5587a2ca47c2dd702b (patch)
tree40a40cd4aee50183ced46dbc34106c29c09c4787 /core/java/android/content/ContentService.java
parent9f5f80e7a64d9388e3e73763b5c73dcddaa729fc (diff)
downloadframeworks_base-16aa9736175f5bbe924a6e5587a2ca47c2dd702b.zip
frameworks_base-16aa9736175f5bbe924a6e5587a2ca47c2dd702b.tar.gz
frameworks_base-16aa9736175f5bbe924a6e5587a2ca47c2dd702b.tar.bz2
Per-user content observer APIs
Callers with INTERACT_ACROSS_USERS_FULL permission can now observe content for a given user's view (and can notify content uri changes targeted to a specific user). An observer watching for UserHandle.USER_ALL will see all notifications for the given uri across all users; similarly, a notifier who specifies USER_ALL will broadcast the change to all observers across all users. The API handles both USER_ALL or USER_CURRENT, and explicitly forbids any other "pseudouser" designations. This CL also revs the Settings provider to notify with USER_ALL for changes to global settings, and with only the affected user's handle for all other changes. Bug 7122169 Change-Id: I94248b11aa91d1beb0a36432e82fe5725bb1264f
Diffstat (limited to 'core/java/android/content/ContentService.java')
-rw-r--r--core/java/android/content/ContentService.java150
1 files changed, 116 insertions, 34 deletions
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 472fe94..0f6488a 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,19 +140,49 @@ 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");
}
+
+ 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, notifyForDescendents, mRootNode,
- Binder.getCallingUid(), Binder.getCallingPid());
+ mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
+ Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
- " with notifyForDescendents " + notifyForDescendents);
+ " with notifyForDescendants " + notifyForDescendants);
}
}
+ public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ IContentObserver observer) {
+ registerContentObserver(uri, notifyForDescendants, observer,
+ UserHandle.getCallingUserId());
+ }
+
public void unregisterContentObserver(IContentObserver observer) {
if (observer == null) {
throw new IllegalArgumentException("You must pass a valid 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;
}