1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.media.photopicker.v2; 18 19 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.ALBUM_PATH_SEGMENT; 20 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.AVAILABLE_PROVIDERS_PATH_SEGMENT; 21 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.MEDIA_PATH_SEGMENT; 22 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.MEDIA_SETS_PATH_SEGMENT; 23 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.MEDIA_SET_CONTENTS_PATH_SEGMENT; 24 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.PICKER_INTERNAL_PATH_SEGMENT; 25 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.PICKER_V2_PATH_SEGMENT; 26 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.SEARCH_RESULT_MEDIA_PATH_SEGMENT; 27 import static com.android.providers.media.photopicker.v2.PickerUriResolverV2.UPDATE_PATH_SEGMENT; 28 29 import static java.util.Objects.requireNonNull; 30 31 import android.annotation.NonNull; 32 import android.content.ContentResolver; 33 import android.content.Context; 34 import android.net.Uri; 35 import android.provider.MediaStore; 36 import android.util.Log; 37 38 import com.android.providers.media.photopicker.PickerSyncController; 39 40 public class PickerNotificationSender { 41 private static final String TAG = "PickerNotificationSender"; 42 43 /** 44 * Flag for {@link #notifyChange(Uri, ContentObserver, int)} to indicate that this notification 45 * should not be subject to any delays when dispatching to apps running in the background. 46 * Using this flag may negatively impact system health and performance, and should be used 47 * sparingly. 48 */ 49 public static final int NOTIFY_NO_DELAY = 1 << 15; 50 51 private static final Uri AVAILABLE_PROVIDERS_UPDATE_URI = new Uri.Builder() 52 .scheme(ContentResolver.SCHEME_CONTENT) 53 .authority(MediaStore.AUTHORITY) 54 .appendPath(PICKER_INTERNAL_PATH_SEGMENT) 55 .appendPath(PICKER_V2_PATH_SEGMENT) 56 .appendPath(AVAILABLE_PROVIDERS_PATH_SEGMENT) 57 .appendPath(UPDATE_PATH_SEGMENT) 58 .build(); 59 60 private static final Uri MEDIA_UPDATE_URI = new Uri.Builder() 61 .scheme(ContentResolver.SCHEME_CONTENT) 62 .authority(MediaStore.AUTHORITY) 63 .appendPath(PICKER_INTERNAL_PATH_SEGMENT) 64 .appendPath(PICKER_V2_PATH_SEGMENT) 65 .appendPath(MEDIA_PATH_SEGMENT) 66 .appendPath(UPDATE_PATH_SEGMENT) 67 .build(); 68 69 private static final Uri ALBUM_UPDATE_URI = new Uri.Builder() 70 .scheme(ContentResolver.SCHEME_CONTENT) 71 .authority(MediaStore.AUTHORITY) 72 .appendPath(PICKER_INTERNAL_PATH_SEGMENT) 73 .appendPath(PICKER_V2_PATH_SEGMENT) 74 .appendPath(ALBUM_PATH_SEGMENT) 75 .appendPath(UPDATE_PATH_SEGMENT) 76 .build(); 77 78 private static final Uri SEARCH_RESULTS_UPDATE_URI = new Uri.Builder() 79 .scheme(ContentResolver.SCHEME_CONTENT) 80 .authority(MediaStore.AUTHORITY) 81 .appendPath(PICKER_INTERNAL_PATH_SEGMENT) 82 .appendPath(PICKER_V2_PATH_SEGMENT) 83 .appendPath(SEARCH_RESULT_MEDIA_PATH_SEGMENT) 84 .appendPath(UPDATE_PATH_SEGMENT) 85 .build(); 86 87 private static final Uri MEDIA_SETS_UPDATE_URI = new Uri.Builder() 88 .scheme(ContentResolver.SCHEME_CONTENT) 89 .authority(MediaStore.AUTHORITY) 90 .appendPath(PICKER_INTERNAL_PATH_SEGMENT) 91 .appendPath(PICKER_V2_PATH_SEGMENT) 92 .appendPath(MEDIA_SETS_PATH_SEGMENT) 93 .appendPath(UPDATE_PATH_SEGMENT) 94 .build(); 95 96 private static final Uri MEDIA_SET_CONTENT_UPDATE_URI = new Uri.Builder() 97 .scheme(ContentResolver.SCHEME_CONTENT) 98 .authority(MediaStore.AUTHORITY) 99 .appendPath(PICKER_INTERNAL_PATH_SEGMENT) 100 .appendPath(PICKER_V2_PATH_SEGMENT) 101 .appendPath(MEDIA_SET_CONTENTS_PATH_SEGMENT) 102 .appendPath(UPDATE_PATH_SEGMENT) 103 .build(); 104 105 /** 106 * Send media update notification to the registered {@link android.database.ContentObserver}-s. 107 * @param context The application context. 108 */ notifyAvailableProvidersChange(@onNull Context context)109 public static void notifyAvailableProvidersChange(@NonNull Context context) { 110 Log.d(TAG, "Sending a notification for available providers update"); 111 context.getContentResolver().notifyChange( 112 AVAILABLE_PROVIDERS_UPDATE_URI, /* observer= */ null, NOTIFY_NO_DELAY); 113 } 114 115 /** 116 * Send media update notification to the registered {@link android.database.ContentObserver}-s. 117 * @param context The application context. 118 */ notifyMediaChange(@onNull Context context)119 public static void notifyMediaChange(@NonNull Context context) { 120 Log.d(TAG, "Sending a notification for media update"); 121 context.getContentResolver().notifyChange(MEDIA_UPDATE_URI, /* observer= */ null); 122 notifyMergedAlbumMediaChange(context, PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY); 123 } 124 125 /** 126 * Send album media update notification to the registered 127 * {@link android.database.ContentObserver}-s. 128 * @param context The application context. 129 * @param albumAuthority authority of the updated album 130 * @param albumId ID of the updated album 131 */ notifyAlbumMediaChange( @onNull Context context, @NonNull String albumAuthority, @NonNull String albumId)132 public static void notifyAlbumMediaChange( 133 @NonNull Context context, 134 @NonNull String albumAuthority, 135 @NonNull String albumId) { 136 Log.d(TAG, "Sending a notification for album media update " + albumId); 137 context.getContentResolver().notifyChange( 138 getAlbumMediaUpdateUri(albumAuthority, albumId), 139 /* observer= */ null); 140 } 141 142 /** 143 * Send album media update notification to the registered 144 * {@link android.database.ContentObserver}-s for all merged album updates. 145 * @param context The application context. 146 * @param localAuthority authority of the local provider. 147 */ notifyMergedAlbumMediaChange( @onNull Context context, @NonNull String localAuthority)148 public static void notifyMergedAlbumMediaChange( 149 @NonNull Context context, 150 @NonNull String localAuthority) { 151 for (String mergedAlbumId: PickerDataLayerV2.MERGED_ALBUMS) { 152 Log.d(TAG, "Sending a notification for merged album media update " + mergedAlbumId); 153 154 // By default, always keep merged album authority as local. 155 notifyAlbumMediaChange(context, localAuthority, mergedAlbumId); 156 } 157 } 158 159 /** 160 * Send search results update notification to the registered 161 * {@link android.database.ContentObserver}-s. 162 * @param context The application context. 163 * @param searchRequestId Search request ID corresponding for which the search results 164 * have updated. 165 */ notifySearchResultsChange( @onNull Context context, int searchRequestId)166 public static void notifySearchResultsChange( 167 @NonNull Context context, 168 int searchRequestId) { 169 Log.d(TAG, "Sending a notification for search results update " + searchRequestId); 170 context.getContentResolver().notifyChange( 171 getSearchResultsUpdateUri(searchRequestId), 172 /* observer= */ null); 173 } 174 175 /** 176 * Send media sets update notification to the registered 177 * {@link android.database.ContentObserver}-s. 178 * @param context The application context. 179 * @param categoryId Category ID for which the media sets have updated. 180 */ notifyMediaSetsChange( @onNull Context context, @NonNull String categoryId)181 public static void notifyMediaSetsChange( 182 @NonNull Context context, 183 @NonNull String categoryId) { 184 requireNonNull(context); 185 requireNonNull(categoryId); 186 Log.d(TAG, "Sending notification for media sets update for the " 187 + "given categoryId " + categoryId); 188 context.getContentResolver().notifyChange( 189 getMediaSetsUpdateUri(categoryId), /* observer */ null 190 ); 191 } 192 getMediaSetsUpdateUri(@onNull String categoryId)193 private static Uri getMediaSetsUpdateUri(@NonNull String categoryId) { 194 return MEDIA_SETS_UPDATE_URI 195 .buildUpon() 196 .appendPath(categoryId) 197 .build(); 198 } 199 200 /** 201 * Send media set content update notification to the registered 202 * {@link android.database.ContentObserver}-s. 203 * @param context The application context. 204 * @param mediaSetId MediaSet ID for which the media content has updated. 205 */ notifyMediaSetContentChange( @onNull Context context, @NonNull String mediaSetId)206 public static void notifyMediaSetContentChange( 207 @NonNull Context context, @NonNull String mediaSetId) { 208 requireNonNull(context); 209 requireNonNull(mediaSetId); 210 Log.d(TAG, "Sending notification for media set content update for the " 211 + "given mediaSet " + mediaSetId); 212 context.getContentResolver().notifyChange( 213 getMediaSetContentUpdateUri(mediaSetId), /* observer */ null 214 ); 215 } 216 getAlbumMediaUpdateUri( @onNull String albumAuthority, @NonNull String albumId)217 private static Uri getAlbumMediaUpdateUri( 218 @NonNull String albumAuthority, 219 @NonNull String albumId) { 220 return ALBUM_UPDATE_URI 221 .buildUpon() 222 .appendPath(requireNonNull(albumAuthority)) 223 .appendPath(requireNonNull(albumId)) 224 .build(); 225 } 226 getSearchResultsUpdateUri(int searchRequestId)227 private static Uri getSearchResultsUpdateUri(int searchRequestId) { 228 return SEARCH_RESULTS_UPDATE_URI 229 .buildUpon() 230 .appendPath(Integer.toString(searchRequestId)) 231 .build(); 232 } 233 getMediaSetContentUpdateUri(@onNull String mediaSetId)234 private static Uri getMediaSetContentUpdateUri(@NonNull String mediaSetId) { 235 return MEDIA_SET_CONTENT_UPDATE_URI 236 .buildUpon() 237 .appendPath(mediaSetId) 238 .build(); 239 } 240 } 241