Merge "Use flags to indicate reason for Uri changes." into rvc-dev am: 189e00a46b am: aeb824386b am: de37673a75 am: b605e547fe

Change-Id: I9a65c811250b9255a0d6229ad922bd4ce36c57fd
This commit is contained in:
Automerger Merge Worker
2020-03-06 01:51:48 +00:00
7 changed files with 358 additions and 143 deletions

View File

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

View File

@@ -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 {}
@@ -650,6 +653,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

View File

@@ -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);
}
/**
* This method is called when a content change occurs. Includes the changed
* content Uris 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 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.
* </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.
*
* @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);
}
}

View File

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

View File

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

View File

@@ -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;
public static class ObserverCollector {
private final ArrayMap<Key, List<Uri>> collected = new ArrayMap<>();
ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
int procState) {
mObserver = observer;
mSelfChange = selfChange;
mUri = uri;
mUserId = userId;
mProcState = procState;
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;
}

View File

@@ -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 com.android.server.content.ContentService.ObserverCall;
import androidx.test.runner.AndroidJUnit4;
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);
}
}
}