diff --git a/api/current.txt b/api/current.txt index d5ad60cfee9e0..98c77fc6d59ca 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9650,8 +9650,9 @@ package android.content { 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, @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 2657cc54b91ae..61c8db5db124a 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -65,6 +65,7 @@ import android.text.TextUtils; 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 @@ public abstract class ContentResolver implements ContentInterface { * 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 @@ public abstract class ContentResolver implements ContentInterface { * To observe events sent through this call, use * {@link #registerContentObserver(Uri, boolean, ContentObserver)}. *

- * 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. *

* Starting in {@link android.os.Build.VERSION_CODES#O}, all content * notifications must be backed by a valid {@link ContentProvider}. @@ -2426,22 +2427,72 @@ public abstract class ContentResolver implements ContentInterface { ContentProvider.getUserIdFromUri(uri, mContext.getUserId())); } + /** + * Notify registered observers that several rows have been updated. + *

+ * To observe events sent through this call, use + * {@link #registerContentObserver(Uri, boolean, ContentObserver)}. + *

+ * 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. + *

+ * 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 + * 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 uris, @Nullable ContentObserver observer, + @NotifyFlags int flags) { + Preconditions.checkNotNull(uris, "uris"); + + // Cluster based on user ID + final SparseArray> clusteredByUser = new SparseArray<>(); + for (Uri uri : uris) { + final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId()); + ArrayList 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 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 @@ public abstract class ContentResolver implements ContentInterface { * * @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 a34a9951671cc..03c99e1a23442 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -51,7 +51,7 @@ interface IContentService { * 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 4a62bc507d921..bc7307b3ee6cd 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 @@ public final class ContentService extends IContentService.Stub { * 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 a70152c8b7327..8283019a10ec4 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 @@ public class MockContentResolver extends ContentResolver { } /** - * 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 uris, @Nullable ContentObserver observer, + @NotifyFlags int flags) { + } }