Allow sending of bulk Uri change notifications.
MediaProvider makes heavy use of Uri change notifications, which
currently need to be delivered one at a time through the Binder
interface. To optimize this, allow callers to provide a collection
of multiple Uris to notify with a single Binder call.
Bug: 134170767
Test: atest cts/tests/tests/content/src/android/content/cts/ContentResolverTest.java
Change-Id: Ifef778e88bb772b5580f70929c6f2e9c166d1c0e
diff --git a/api/current.txt b/api/current.txt
index d5ad60c..98c77fc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9650,8 +9650,9 @@
method public static boolean isSyncPending(android.accounts.Account, String);
method @NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, @NonNull android.util.Size, @Nullable android.os.CancellationSignal) throws java.io.IOException;
method public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver);
- method public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, boolean);
+ method @Deprecated public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, boolean);
method public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, int);
+ method public void notifyChange(@NonNull Iterable<android.net.Uri>, @Nullable android.database.ContentObserver, int);
method @Nullable public final android.content.res.AssetFileDescriptor openAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
method @Nullable public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException;
method @Nullable public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2657cc5..61c8db5d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -65,6 +65,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.Size;
+import android.util.SparseArray;
import com.android.internal.util.MimeIconUtils;
import com.android.internal.util.Preconditions;
@@ -2381,15 +2382,15 @@
* true.
* @param syncToNetwork If true, same as {@link #NOTIFY_SYNC_TO_NETWORK}.
* @see #requestSync(android.accounts.Account, String, android.os.Bundle)
+ * @deprecated callers should consider migrating to
+ * {@link #notifyChange(Uri, ContentObserver, int)}, as it
+ * offers support for many more options than just
+ * {@link #NOTIFY_SYNC_TO_NETWORK}.
*/
+ @Deprecated
public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
boolean syncToNetwork) {
- Preconditions.checkNotNull(uri, "uri");
- notifyChange(
- ContentProvider.getUriWithoutUserId(uri),
- observer,
- syncToNetwork,
- ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+ notifyChange(uri, observer, syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0);
}
/**
@@ -2398,10 +2399,10 @@
* To observe events sent through this call, use
* {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
* <p>
- * If syncToNetwork is true, this will attempt to schedule a local sync
- * using the sync adapter that's registered for the authority of the
- * provided uri. No account will be passed to the sync adapter, so all
- * matching accounts will be synchronized.
+ * If {@link #NOTIFY_SYNC_TO_NETWORK} is set, this will attempt to schedule
+ * a local sync using the sync adapter that's registered for the authority
+ * of the provided uri. No account will be passed to the sync adapter, so
+ * all matching accounts will be synchronized.
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#O}, all content
* notifications must be backed by a valid {@link ContentProvider}.
@@ -2427,21 +2428,71 @@
}
/**
+ * Notify registered observers that several rows have been updated.
+ * <p>
+ * To observe events sent through this call, use
+ * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+ * <p>
+ * If {@link #NOTIFY_SYNC_TO_NETWORK} is set, this will attempt to schedule
+ * a local sync using the sync adapter that's registered for the authority
+ * of the provided uri. No account will be passed to the sync adapter, so
+ * all matching accounts will be synchronized.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+ * notifications must be backed by a valid {@link ContentProvider}.
+ *
+ * @param uris The uris of the content that was changed.
+ * @param observer The observer that originated the change, may be
+ * <code>null</null>. The observer that originated the change
+ * will only receive the notification if it has requested to
+ * receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return
+ * true.
+ * @param flags Flags such as {@link #NOTIFY_SYNC_TO_NETWORK} or
+ * {@link #NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS}.
+ */
+ public void notifyChange(@NonNull Iterable<Uri> uris, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ Preconditions.checkNotNull(uris, "uris");
+
+ // Cluster based on user ID
+ final SparseArray<ArrayList<Uri>> clusteredByUser = new SparseArray<>();
+ for (Uri uri : uris) {
+ final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+ ArrayList<Uri> list = clusteredByUser.get(userId);
+ if (list == null) {
+ list = new ArrayList<>();
+ clusteredByUser.put(userId, list);
+ }
+ list.add(ContentProvider.getUriWithoutUserId(uri));
+ }
+
+ for (int i = 0; i < clusteredByUser.size(); i++) {
+ final int userId = clusteredByUser.keyAt(i);
+ final ArrayList<Uri> list = clusteredByUser.valueAt(i);
+ notifyChange(list.toArray(new Uri[list.size()]), observer, flags, userId);
+ }
+ }
+
+ /**
* Notify registered observers within the designated user(s) that a row was updated.
*
+ * @deprecated callers should consider migrating to
+ * {@link #notifyChange(Uri, ContentObserver, int)}, as it
+ * offers support for many more options than just
+ * {@link #NOTIFY_SYNC_TO_NETWORK}.
* @hide
*/
+ @Deprecated
public void notifyChange(@NonNull Uri uri, ContentObserver observer, boolean syncToNetwork,
@UserIdInt int userHandle) {
- try {
- getContentService().notifyChange(
- uri, observer == null ? null : observer.getContentObserver(),
- observer != null && observer.deliverSelfNotifications(),
- syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
- userHandle, mTargetSdkVersion, mContext.getPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ notifyChange(uri, observer, syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0, userHandle);
+ }
+
+ /** {@hide} */
+ public void notifyChange(@NonNull Uri uri, ContentObserver observer, @NotifyFlags int flags,
+ @UserIdInt int userHandle) {
+ notifyChange(new Uri[] { uri }, observer, flags, userHandle);
}
/**
@@ -2449,11 +2500,11 @@
*
* @hide
*/
- public void notifyChange(@NonNull Uri uri, ContentObserver observer, @NotifyFlags int flags,
+ public void notifyChange(@NonNull Uri[] uris, ContentObserver observer, @NotifyFlags int flags,
@UserIdInt int userHandle) {
try {
getContentService().notifyChange(
- uri, observer == null ? null : observer.getContentObserver(),
+ uris, observer == null ? null : observer.getContentObserver(),
observer != null && observer.deliverSelfNotifications(), flags,
userHandle, mTargetSdkVersion, mContext.getPackageName());
} catch (RemoteException e) {
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index a34a995..03c99e1 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -51,7 +51,7 @@
* hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL
* USER_CURRENT are properly interpreted.
*/
- void notifyChange(in Uri uri, IContentObserver observer,
+ void notifyChange(in Uri[] uris, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
int userHandle, int targetSdkVersion, String callingPackage);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 4a62bc5..bc7307b 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -394,6 +394,15 @@
* allowed.
*/
@Override
+ public void notifyChange(Uri[] uris, IContentObserver observer,
+ boolean observerWantsSelfNotifications, int flags, int userHandle,
+ int targetSdkVersion, String callingPackage) {
+ for (Uri uri : uris) {
+ notifyChange(uri, observer, observerWantsSelfNotifications, flags, userHandle,
+ targetSdkVersion, callingPackage);
+ }
+ }
+
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags, int userHandle,
int targetSdkVersion, String callingPackage) {
diff --git a/test-mock/src/android/test/mock/MockContentResolver.java b/test-mock/src/android/test/mock/MockContentResolver.java
index a70152c..8283019 100644
--- a/test-mock/src/android/test/mock/MockContentResolver.java
+++ b/test-mock/src/android/test/mock/MockContentResolver.java
@@ -16,6 +16,8 @@
package android.test.mock;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -130,17 +132,47 @@
}
/**
- * Overrides {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver, boolean)
- * ContentResolver.notifChange(Uri, ContentObserver, boolean)}. All parameters are ignored.
- * The method hides providers linked to MockContentResolver from other observers in the system.
- *
- * @param uri (Ignored) The uri of the content provider.
- * @param observer (Ignored) The observer that originated the change.
- * @param syncToNetwork (Ignored) If true, attempt to sync the change to the network.
+ * Overrides the behavior from the parent class to completely ignore any
+ * content notifications sent to this object. This effectively hides clients
+ * from observers elsewhere in the system.
*/
@Override
- public void notifyChange(Uri uri,
- ContentObserver observer,
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer) {
+ }
+
+ /**
+ * Overrides the behavior from the parent class to completely ignore any
+ * content notifications sent to this object. This effectively hides clients
+ * from observers elsewhere in the system.
+ *
+ * @deprecated callers should consider migrating to
+ * {@link #notifyChange(Uri, ContentObserver, int)}, as it
+ * offers support for many more options than just
+ * {@link #NOTIFY_SYNC_TO_NETWORK}.
+ */
+ @Override
+ @Deprecated
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
boolean syncToNetwork) {
}
+
+ /**
+ * Overrides the behavior from the parent class to completely ignore any
+ * content notifications sent to this object. This effectively hides clients
+ * from observers elsewhere in the system.
+ */
+ @Override
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ }
+
+ /**
+ * Overrides the behavior from the parent class to completely ignore any
+ * content notifications sent to this object. This effectively hides clients
+ * from observers elsewhere in the system.
+ */
+ @Override
+ public void notifyChange(@NonNull Iterable<Uri> uris, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ }
}