diff options
| author | 2020-03-06 00:04:24 +0000 | |
|---|---|---|
| committer | 2020-03-06 00:04:24 +0000 | |
| commit | 189e00a46bcd9273dbb928ab6791426f7bc766bc (patch) | |
| tree | a183df97cf8730503fbe22d0f491b644a61957db | |
| parent | 9a967db5750d62581c02038126e84dceacd240e6 (diff) | |
| parent | d70325359ed681dbe90ab5d0e6562f795c73417a (diff) | |
Merge "Use flags to indicate reason for Uri changes." into rvc-dev
| -rw-r--r-- | api/current.txt | 11 | ||||
| -rw-r--r-- | core/java/android/content/ContentResolver.java | 35 | ||||
| -rw-r--r-- | core/java/android/database/ContentObserver.java | 163 | ||||
| -rw-r--r-- | core/java/android/database/CursorToBulkCursorAdaptor.java | 18 | ||||
| -rw-r--r-- | core/java/android/database/IContentObserver.aidl | 10 | ||||
| -rw-r--r-- | services/core/java/com/android/server/content/ContentService.java | 153 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java | 113 |
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); } } } |