Use flags to indicate reason for Uri changes.
As part of MediaProvider becoming a Mainline module, some partners need to understand more details about what triggered a Uri change notification. This helps listening apps avoid making expensive and race-condition-prone calls back into MediaProvider; typically when they're only interested in insert and delete operations. This change uses the existing "flags" argument to communicate the reason. This change adds overloads to ContentObserver for listening apps to receive these flags. In addition, we add overloads that deliver a clustered set of multiple Uris together in a single Binder transaction to improve overall efficiency. (This matches well with the existing CR.notifyChange() API that we added earlier this year, since they both work with Iterable<Uri>.) Tests to verify that we only collapse Uris together when all other method arguments are identical. Bug: 147778404, 144464323 Test: atest CtsDatabaseTestCases Test: atest CtsContentTestCases:android.content.cts.ContentResolverTest Test: atest FrameworksServicesTests:com.android.server.content.ObserverNodeTest Test: atest --test-mapping packages/providers/MediaProvider Change-Id: I0bbd8a8b4a898ab6f891d085de0ecb4d68cbe302
This commit is contained in:
@@ -9921,8 +9921,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";
|
||||
@@ -12965,9 +12968,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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user