Merge "Use flags to indicate reason for Uri changes." into rvc-dev am: 189e00a46b am: aeb824386b am: de37673a75
Change-Id: I51c3a5f6378b3c05cd84402b173a3624123c3502
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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