summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@google.com> 2020-03-06 00:04:24 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-03-06 00:04:24 +0000
commit189e00a46bcd9273dbb928ab6791426f7bc766bc (patch)
treea183df97cf8730503fbe22d0f491b644a61957db
parent9a967db5750d62581c02038126e84dceacd240e6 (diff)
parentd70325359ed681dbe90ab5d0e6562f795c73417a (diff)
Merge "Use flags to indicate reason for Uri changes." into rvc-dev
-rw-r--r--api/current.txt11
-rw-r--r--core/java/android/content/ContentResolver.java35
-rw-r--r--core/java/android/database/ContentObserver.java163
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java18
-rw-r--r--core/java/android/database/IContentObserver.aidl10
-rw-r--r--services/core/java/com/android/server/content/ContentService.java153
-rw-r--r--services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java113
7 files changed, 359 insertions, 144 deletions
diff --git a/api/current.txt b/api/current.txt
index 563665fb519a..1cf68321ae22 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9924,8 +9924,11 @@ package android.content {
field public static final String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
field public static final String EXTRA_SIZE = "android.content.extra.SIZE";
field public static final String EXTRA_TOTAL_COUNT = "android.content.extra.TOTAL_COUNT";
+ field public static final int NOTIFY_DELETE = 16; // 0x10
+ field public static final int NOTIFY_INSERT = 4; // 0x4
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
+ field public static final int NOTIFY_UPDATE = 8; // 0x8
field public static final String QUERY_ARG_GROUP_COLUMNS = "android:query-arg-group-columns";
field public static final String QUERY_ARG_LIMIT = "android:query-arg-limit";
field public static final String QUERY_ARG_OFFSET = "android:query-arg-offset";
@@ -12967,9 +12970,13 @@ package android.database {
ctor public ContentObserver(android.os.Handler);
method public boolean deliverSelfNotifications();
method @Deprecated public final void dispatchChange(boolean);
- method public final void dispatchChange(boolean, android.net.Uri);
+ method public final void dispatchChange(boolean, @Nullable android.net.Uri);
+ method public final void dispatchChange(boolean, @Nullable android.net.Uri, int);
+ method public final void dispatchChange(boolean, @NonNull Iterable<android.net.Uri>, int);
method public void onChange(boolean);
- method public void onChange(boolean, android.net.Uri);
+ method public void onChange(boolean, @Nullable android.net.Uri);
+ method public void onChange(boolean, @Nullable android.net.Uri, int);
+ method public void onChange(boolean, @NonNull Iterable<android.net.Uri>, int);
}
public interface CrossProcessCursor extends android.database.Cursor {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index c7f42cb85943..ae786aa30ae0 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -629,7 +629,10 @@ public abstract class ContentResolver implements ContentInterface {
/** @hide */
@IntDef(flag = true, prefix = { "NOTIFY_" }, value = {
NOTIFY_SYNC_TO_NETWORK,
- NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS
+ NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS,
+ NOTIFY_INSERT,
+ NOTIFY_UPDATE,
+ NOTIFY_DELETE
})
@Retention(RetentionPolicy.SOURCE)
public @interface NotifyFlags {}
@@ -651,6 +654,36 @@ public abstract class ContentResolver implements ContentInterface {
public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
/**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of an {@link ContentProvider#insert} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_INSERT = 1 << 2;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of an {@link ContentProvider#update} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_UPDATE = 1 << 3;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of a {@link ContentProvider#delete} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_DELETE = 1 << 4;
+
+ /**
* No exception, throttled by app standby normally.
* @hide
*/
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 69ca581e1559..ede264d042ce 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -16,11 +16,17 @@
package android.database;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver.NotifyFlags;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
+import java.util.Arrays;
+
/**
* Receives call backs for changes to content.
* Must be implemented by objects which are added to a {@link ContentObservable}.
@@ -101,12 +107,10 @@ public abstract class ContentObserver {
* This method is called when a content change occurs.
* Includes the changed content Uri when available.
* <p>
- * Subclasses should override this method to handle content changes.
- * To ensure correct operation on older versions of the framework that
- * did not provide a Uri argument, applications should also implement
- * the {@link #onChange(boolean)} overload of this method whenever they
- * implement the {@link #onChange(boolean, Uri)} overload.
- * </p><p>
+ * Subclasses should override this method to handle content changes. To
+ * ensure correct operation on older versions of the framework that did not
+ * provide richer arguments, applications should implement all overloads.
+ * <p>
* Example implementation:
* <pre><code>
* // Implement the onChange(boolean) method to delegate the change notification to
@@ -126,38 +130,63 @@ public abstract class ContentObserver {
* </p>
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
+ * @param uri The Uri of the changed content.
*/
- public void onChange(boolean selfChange, Uri uri) {
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
onChange(selfChange);
}
/**
- * Dispatches a change notification to the observer. Includes the changed
- * content Uri when available and also the user whose content changed.
+ * This method is called when a content change occurs. Includes the changed
+ * content Uri when available.
+ * <p>
+ * Subclasses should override this method to handle content changes. To
+ * ensure correct operation on older versions of the framework that did not
+ * provide richer arguments, applications should implement all overloads.
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
- * @param userId The user whose content changed. Can be either a specific
- * user or {@link UserHandle#USER_ALL}.
- *
- * @hide
+ * @param uri The Uri of the changed content.
+ * @param flags Flags indicating details about this change.
*/
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, @Nullable Uri uri, @NotifyFlags int flags) {
onChange(selfChange, uri);
}
/**
- * Dispatches a change notification to the observer.
+ * This method is called when a content change occurs. Includes the changed
+ * content Uris when available.
* <p>
- * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
- * then a call to the {@link #onChange} method is posted to the handler's message queue.
- * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
- * </p>
+ * Subclasses should override this method to handle content changes. To
+ * ensure correct operation on older versions of the framework that did not
+ * provide richer arguments, applications should implement all overloads.
*
* @param selfChange True if this is a self-change notification.
+ * @param uris The Uris of the changed content.
+ * @param flags Flags indicating details about this change.
+ */
+ public void onChange(boolean selfChange, @NonNull Iterable<Uri> uris, @NotifyFlags int flags) {
+ for (Uri uri : uris) {
+ onChange(selfChange, uri, flags);
+ }
+ }
+
+ /** @hide */
+ public void onChange(boolean selfChange, @NonNull Iterable<Uri> uris, @NotifyFlags int flags,
+ @UserIdInt int userId) {
+ onChange(selfChange, uris, flags);
+ }
+
+ /**
+ * Dispatches a change notification to the observer.
+ * <p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
*
- * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead.
+ * @deprecated Callers should migrate towards using a richer overload that
+ * provides more details about the change, such as
+ * {@link #dispatchChange(boolean, Iterable, int)}.
*/
@Deprecated
public final void dispatchChange(boolean selfChange) {
@@ -165,57 +194,66 @@ public abstract class ContentObserver {
}
/**
- * Dispatches a change notification to the observer.
- * Includes the changed content Uri when available.
+ * Dispatches a change notification to the observer. Includes the changed
+ * content Uri when available.
* <p>
- * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
- * then a call to the {@link #onChange} method is posted to the handler's message queue.
- * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
- * </p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
+ * @param uri The Uri of the changed content.
*/
- public final void dispatchChange(boolean selfChange, Uri uri) {
- dispatchChange(selfChange, uri, UserHandle.getCallingUserId());
+ public final void dispatchChange(boolean selfChange, @Nullable Uri uri) {
+ dispatchChange(selfChange, Arrays.asList(uri), 0, UserHandle.getCallingUserId());
}
/**
* Dispatches a change notification to the observer. Includes the changed
- * content Uri when available and also the user whose content changed.
+ * content Uri when available.
* <p>
- * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
- * then a call to the {@link #onChange} method is posted to the handler's message queue.
- * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
- * </p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
- * @param userId The user whose content changed.
+ * @param uri The Uri of the changed content.
+ * @param flags Flags indicating details about this change.
*/
- private void dispatchChange(boolean selfChange, Uri uri, int userId) {
- if (mHandler == null) {
- onChange(selfChange, uri, userId);
- } else {
- mHandler.post(new NotificationRunnable(selfChange, uri, userId));
- }
+ public final void dispatchChange(boolean selfChange, @Nullable Uri uri,
+ @NotifyFlags int flags) {
+ dispatchChange(selfChange, Arrays.asList(uri), flags, UserHandle.getCallingUserId());
}
+ /**
+ * Dispatches a change notification to the observer. Includes the changed
+ * content Uris when available.
+ * <p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
+ *
+ * @param selfChange True if this is a self-change notification.
+ * @param uris The Uri of the changed content.
+ * @param flags Flags indicating details about this change.
+ */
+ public final void dispatchChange(boolean selfChange, @NonNull Iterable<Uri> uris,
+ @NotifyFlags int flags) {
+ dispatchChange(selfChange, uris, flags, UserHandle.getCallingUserId());
+ }
- private final class NotificationRunnable implements Runnable {
- private final boolean mSelfChange;
- private final Uri mUri;
- private final int mUserId;
-
- public NotificationRunnable(boolean selfChange, Uri uri, int userId) {
- mSelfChange = selfChange;
- mUri = uri;
- mUserId = userId;
- }
-
- @Override
- public void run() {
- ContentObserver.this.onChange(mSelfChange, mUri, mUserId);
+ /** @hide */
+ public final void dispatchChange(boolean selfChange, @NonNull Iterable<Uri> uris,
+ @NotifyFlags int flags, @UserIdInt int userId) {
+ if (mHandler == null) {
+ onChange(selfChange, uris, flags, userId);
+ } else {
+ mHandler.post(() -> {
+ onChange(selfChange, uris, flags, userId);
+ });
}
}
@@ -228,9 +266,16 @@ public abstract class ContentObserver {
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
+ // This is kept intact purely for apps using hidden APIs, to
+ // redirect to the updated implementation
+ onChangeEtc(selfChange, new Uri[] { uri }, 0, userId);
+ }
+
+ @Override
+ public void onChangeEtc(boolean selfChange, Uri[] uris, int flags, int userId) {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
- contentObserver.dispatchChange(selfChange, uri, userId);
+ contentObserver.dispatchChange(selfChange, Arrays.asList(uris), flags, userId);
}
}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 02eddf239dca..1855dd254ad4 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -16,9 +16,14 @@
package android.database;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver.NotifyFlags;
import android.net.Uri;
import android.os.*;
+import java.util.ArrayList;
+
/**
* Wraps a BulkCursor around an existing Cursor making it remotable.
@@ -76,9 +81,18 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
+ public void onChange(boolean selfChange, @NonNull Iterable<Uri> uris,
+ @NotifyFlags int flags, @UserIdInt int userId) {
+ // Since we deliver changes from the most-specific to least-specific
+ // overloads, we only need to redirect from the most-specific local
+ // method to the most-specific remote method
+
+ final ArrayList<Uri> asList = new ArrayList<>();
+ uris.forEach(asList::add);
+ final Uri[] asArray = asList.toArray(new Uri[asList.size()]);
+
try {
- mRemote.onChange(selfChange, uri, android.os.Process.myUid());
+ mRemote.onChangeEtc(selfChange, asArray, flags, userId);
} catch (RemoteException ex) {
// Do nothing, the far side is dead
}
diff --git a/core/java/android/database/IContentObserver.aidl b/core/java/android/database/IContentObserver.aidl
index 623556695341..19284cf40617 100644
--- a/core/java/android/database/IContentObserver.aidl
+++ b/core/java/android/database/IContentObserver.aidl
@@ -22,8 +22,7 @@ import android.net.Uri;
/**
* @hide
*/
-interface IContentObserver
-{
+interface IContentObserver {
/**
* This method is called when an update occurs to the cursor that is being
* observed. selfUpdate is true if the update was caused by a call to
@@ -31,4 +30,11 @@ interface IContentObserver
*/
@UnsupportedAppUsage
oneway void onChange(boolean selfUpdate, in Uri uri, int userId);
+
+ /**
+ * This method is called when an update occurs to the cursor that is being
+ * observed. selfUpdate is true if the update was caused by a call to
+ * commit on the cursor that is being observed.
+ */
+ oneway void onChangeEtc(boolean selfUpdate, in Uri[] uri, int flags, int userId);
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 74c1e63172fa..61b18eedfc6e 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -83,6 +83,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
/**
* {@hide}
@@ -399,15 +400,22 @@ public final class ContentService extends IContentService.Stub {
public void notifyChange(Uri[] uris, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags, int userHandle,
int targetSdkVersion, String callingPackage) {
+ final ObserverCollector collector = new ObserverCollector();
for (Uri uri : uris) {
notifyChange(uri, observer, observerWantsSelfNotifications, flags, userHandle,
- targetSdkVersion, callingPackage);
+ targetSdkVersion, callingPackage, collector);
+ }
+ final long token = clearCallingIdentity();
+ try {
+ collector.dispatch();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags, int userHandle,
- int targetSdkVersion, String callingPackage) {
+ int targetSdkVersion, String callingPackage, ObserverCollector collector) {
if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle
+ " from observer " + observer + ", flags " + Integer.toHexString(flags));
@@ -442,22 +450,9 @@ public final class ContentService extends IContentService.Stub {
// process rather than the caller's process. We will restore this before returning.
long identityToken = clearCallingIdentity();
try {
- ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
- flags, userHandle, calls);
- }
- final int numCalls = calls.size();
- for (int i = 0; i < numCalls; i++) {
- // Immediately dispatch notifications to foreground apps that
- // are important to the user; all other background observers are
- // delayed to avoid stampeding
- final ObserverCall oc = calls.get(i);
- if (oc.mProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- oc.run();
- } else {
- BackgroundThread.getHandler().postDelayed(oc, BACKGROUND_OBSERVER_DELAY);
- }
+ flags, userHandle, collector);
}
if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
SyncManager syncManager = getSyncManager();
@@ -487,40 +482,84 @@ public final class ContentService extends IContentService.Stub {
}
}
- public void notifyChange(Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork,
- String callingPackage) {
- notifyChange(uri, observer, observerWantsSelfNotifications,
- syncToNetwork ? ContentResolver.NOTIFY_SYNC_TO_NETWORK : 0,
- UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
- }
-
- /** {@hide} */
+ /**
+ * Collection of detected change notifications that should be delivered.
+ * <p>
+ * To help reduce Binder transaction overhead, this class clusters together
+ * multiple {@link Uri} where all other arguments are identical.
+ */
@VisibleForTesting
- public static final class ObserverCall implements Runnable {
- final IContentObserver mObserver;
- final boolean mSelfChange;
- final Uri mUri;
- final int mUserId;
- final int mProcState;
-
- ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
- int procState) {
- mObserver = observer;
- mSelfChange = selfChange;
- mUri = uri;
- mUserId = userId;
- mProcState = procState;
+ public static class ObserverCollector {
+ private final ArrayMap<Key, List<Uri>> collected = new ArrayMap<>();
+
+ private static class Key {
+ final IContentObserver observer;
+ final int uid;
+ final boolean selfChange;
+ final int flags;
+ final int userId;
+
+ Key(IContentObserver observer, int uid, boolean selfChange, int flags, int userId) {
+ this.observer = observer;
+ this.uid = uid;
+ this.selfChange = selfChange;
+ this.flags = flags;
+ this.userId = userId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Key)) {
+ return false;
+ }
+ final Key other = (Key) o;
+ return Objects.equals(observer, other.observer)
+ && (uid == other.uid)
+ && (selfChange == other.selfChange)
+ && (flags == other.flags)
+ && (userId == other.userId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(observer, uid, selfChange, flags, userId);
+ }
}
- @Override
- public void run() {
- try {
- mObserver.onChange(mSelfChange, mUri, mUserId);
- if (DEBUG) Slog.d(TAG, "Notified " + mObserver + " of update at " + mUri);
- } catch (RemoteException ignored) {
- // We already have a death observer that will clean up if the
- // remote process dies
+ public void collect(IContentObserver observer, int uid, boolean selfChange, Uri uri,
+ int flags, int userId) {
+ final Key key = new Key(observer, uid, selfChange, flags, userId);
+ List<Uri> value = collected.get(key);
+ if (value == null) {
+ value = new ArrayList<>();
+ collected.put(key, value);
+ }
+ value.add(uri);
+ }
+
+ public void dispatch() {
+ for (int i = 0; i < collected.size(); i++) {
+ final Key key = collected.keyAt(i);
+ final List<Uri> value = collected.valueAt(i);
+
+ final Runnable task = () -> {
+ try {
+ key.observer.onChangeEtc(key.selfChange,
+ value.toArray(new Uri[value.size()]), key.flags, key.userId);
+ } catch (RemoteException ignored) {
+ }
+ };
+
+ // Immediately dispatch notifications to foreground apps that
+ // are important to the user; all other background observers are
+ // delayed to avoid stampeding
+ final int procState = LocalServices.getService(ActivityManagerInternal.class)
+ .getUidProcessState(key.uid);
+ if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ task.run();
+ } else {
+ BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY);
+ }
}
}
}
@@ -1455,10 +1494,6 @@ public final class ContentService extends IContentService.Stub {
}
}
- public static final int INSERT_TYPE = 0;
- public static final int UPDATE_TYPE = 1;
- public static final int DELETE_TYPE = 2;
-
private String mName;
private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
@@ -1588,7 +1623,7 @@ public final class ContentService extends IContentService.Stub {
private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
- int targetUserHandle, ArrayList<ObserverCall> calls) {
+ int targetUserHandle, ObserverCollector collector) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
@@ -1628,10 +1663,8 @@ public final class ContentService extends IContentService.Stub {
if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
+ " flags=" + Integer.toHexString(flags)
+ " desc=" + entry.notifyForDescendants);
- final int procState = LocalServices.getService(ActivityManagerInternal.class)
- .getUidProcessState(entry.uid);
- calls.add(new ObserverCall(entry.observer, selfChange, uri,
- targetUserHandle, procState));
+ collector.collect(entry.observer, entry.uid, selfChange, uri, flags,
+ targetUserHandle);
}
}
}
@@ -1641,21 +1674,21 @@ public final class ContentService extends IContentService.Stub {
*/
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
- int targetUserHandle, ArrayList<ObserverCall> calls) {
+ int targetUserHandle, ObserverCollector collector) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
- flags, targetUserHandle, calls);
+ flags, targetUserHandle, collector);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
+ segment);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
- flags, targetUserHandle, calls);
+ flags, targetUserHandle, collector);
}
int N = mChildren.size();
@@ -1664,7 +1697,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, flags, targetUserHandle, calls);
+ observerWantsSelfNotifications, flags, targetUserHandle, collector);
if (segment != null) {
break;
}
diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
index 891ca74a545f..0e4d2be49b0e 100644
--- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
@@ -16,30 +16,52 @@
package com.android.server.content;
-import java.util.ArrayList;
-
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.ContentResolver;
import android.database.ContentObserver;
+import android.database.IContentObserver;
import android.net.Uri;
+import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
-import com.android.server.content.ContentService.ObserverCall;
+import com.android.server.LocalServices;
+import com.android.server.content.ContentService.ObserverCollector;
import com.android.server.content.ContentService.ObserverNode;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+import java.util.Arrays;
+
/**
* atest FrameworksServicesTests:com.android.server.content.ObserverNodeTest
*/
-@SmallTest
-public class ObserverNodeTest extends AndroidTestCase {
- static class TestObserver extends ContentObserver {
+@RunWith(AndroidJUnit4.class)
+public class ObserverNodeTest {
+ static class TestObserver extends ContentObserver {
public TestObserver() {
super(new Handler(Looper.getMainLooper()));
}
}
+ @Test
public void testUri() {
final int myUserHandle = UserHandle.myUserId();
@@ -65,15 +87,15 @@ public class ObserverNodeTest extends AndroidTestCase {
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, 0, myUserHandle, calls);
- assertEquals(nums[i], calls.size());
- calls.clear();
+ final ObserverCollector collector = mock(ObserverCollector.class);
+ root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, collector);
+ verify(collector, times(nums[i])).collect(
+ any(), anyInt(), anyBoolean(), any(), anyInt(), anyInt());
}
}
+ @Test
public void testUriNotNotify() {
final int myUserHandle = UserHandle.myUserId();
@@ -95,12 +117,67 @@ public class ObserverNodeTest extends AndroidTestCase {
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, 0, myUserHandle, calls);
- assertEquals(nums[i], calls.size());
- calls.clear();
+ final ObserverCollector collector = mock(ObserverCollector.class);
+ root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, collector);
+ verify(collector, times(nums[i])).collect(
+ any(), anyInt(), anyBoolean(), any(), anyInt(), anyInt());
+ }
+ }
+
+ @Test
+ public void testCluster() throws Exception {
+ final int myUserHandle = UserHandle.myUserId();
+
+ // Assume everything is foreground during our test
+ final ActivityManagerInternal ami = mock(ActivityManagerInternal.class);
+ when(ami.getUidProcessState(anyInt()))
+ .thenReturn(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, ami);
+
+ final IContentObserver observer = mock(IContentObserver.class);
+ when(observer.asBinder()).thenReturn(new Binder());
+
+ final ObserverNode root = new ObserverNode("");
+ root.addObserverLocked(Uri.parse("content://authority/"), observer,
+ true, root, 0, 1000, myUserHandle);
+
+ final ObserverCollector collector = new ObserverCollector();
+ root.collectObserversLocked(Uri.parse("content://authority/1"), 0, null, false,
+ 0, myUserHandle, collector);
+ root.collectObserversLocked(Uri.parse("content://authority/1"), 0, null, false,
+ ContentResolver.NOTIFY_INSERT, myUserHandle, collector);
+ root.collectObserversLocked(Uri.parse("content://authority/2"), 0, null, false,
+ ContentResolver.NOTIFY_INSERT, myUserHandle, collector);
+ root.collectObserversLocked(Uri.parse("content://authority/2"), 0, null, false,
+ ContentResolver.NOTIFY_UPDATE, myUserHandle, collector);
+ collector.dispatch();
+
+ // We should only cluster when all other arguments are equal
+ verify(observer).onChangeEtc(eq(false), argThat(new UriSetMatcher(
+ Uri.parse("content://authority/1"))),
+ eq(0), anyInt());
+ verify(observer).onChangeEtc(eq(false), argThat(new UriSetMatcher(
+ Uri.parse("content://authority/1"),
+ Uri.parse("content://authority/2"))),
+ eq(ContentResolver.NOTIFY_INSERT), anyInt());
+ verify(observer).onChangeEtc(eq(false), argThat(new UriSetMatcher(
+ Uri.parse("content://authority/2"))),
+ eq(ContentResolver.NOTIFY_UPDATE), anyInt());
+ }
+
+ private static class UriSetMatcher implements ArgumentMatcher<Uri[]> {
+ private final ArraySet<Uri> uris;
+
+ public UriSetMatcher(Uri... uris) {
+ this.uris = new ArraySet<>(Arrays.asList(uris));
+ }
+
+ @Override
+ public boolean matches(Uri[] uris) {
+ final ArraySet<Uri> test = new ArraySet<>(Arrays.asList(uris));
+ return this.uris.equals(test);
}
}
}