1 /* 2 * Copyright (C) 2023 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.viewmodel; 18 19 import static com.android.providers.media.photopicker.DataLoaderThread.TOKEN; 20 21 import android.annotation.UserIdInt; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.os.UserHandle; 25 import android.util.Log; 26 27 import androidx.annotation.MainThread; 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 import androidx.annotation.VisibleForTesting; 31 import androidx.lifecycle.LiveData; 32 import androidx.lifecycle.MutableLiveData; 33 34 import com.android.modules.utils.build.SdkLevel; 35 import com.android.providers.media.ConfigStore; 36 import com.android.providers.media.photopicker.DataLoaderThread; 37 import com.android.providers.media.photopicker.data.UserIdManager; 38 import com.android.providers.media.photopicker.data.UserManagerState; 39 import com.android.providers.media.photopicker.util.ThreadUtils; 40 import com.android.providers.media.util.PerUser; 41 42 class BannerManager { 43 private static final String TAG = "BannerManager"; 44 private static final int DELAY_MILLIS = 0; 45 46 private final UserIdManager mUserIdManager; 47 private final UserManagerState mUserManagerState; 48 49 // Authority of the current CloudMediaProvider of the current user 50 private final MutableLiveData<String> mCloudMediaProviderAuthority = new MutableLiveData<>(); 51 // Label of the current CloudMediaProvider of the current user 52 private final MutableLiveData<String> mCloudMediaProviderLabel = new MutableLiveData<>(); 53 // Account name of the current CloudMediaProvider of the current user 54 private final MutableLiveData<String> mCloudMediaAccountName = new MutableLiveData<>(); 55 // Account selection activity intent of the current CloudMediaProvider of the current user 56 private Intent mChooseCloudMediaAccountActivityIntent = null; 57 58 // Boolean Choose App Banner visibility 59 private final MutableLiveData<Boolean> mShowChooseAppBanner = new MutableLiveData<>(false); 60 // Boolean Cloud Media Available Banner visibility 61 private final MutableLiveData<Boolean> mShowCloudMediaAvailableBanner = 62 new MutableLiveData<>(false); 63 // Boolean 'Account Updated' banner visibility 64 private final MutableLiveData<Boolean> mShowAccountUpdatedBanner = new MutableLiveData<>(false); 65 // Boolean 'Choose Account' banner visibility 66 private final MutableLiveData<Boolean> mShowChooseAccountBanner = new MutableLiveData<>(false); 67 68 // The banner controllers per user 69 private final PerUser<BannerController> mBannerControllers; 70 private ConfigStore mConfigStore; 71 BannerManager(@onNull Context context, @NonNull UserIdManager userIdManager, @NonNull ConfigStore configStore)72 BannerManager(@NonNull Context context, @NonNull UserIdManager userIdManager, 73 @NonNull ConfigStore configStore) { 74 this(context, userIdManager, null, configStore); 75 } 76 BannerManager(@onNull Context context, @NonNull UserManagerState userManagerState, @NonNull ConfigStore configStore)77 BannerManager(@NonNull Context context, @NonNull UserManagerState userManagerState, 78 @NonNull ConfigStore configStore) { 79 this(context, null, userManagerState, configStore); 80 } 81 BannerManager(@onNull Context context, UserIdManager userIdManager, UserManagerState userManagerState, ConfigStore configStore)82 private BannerManager(@NonNull Context context, UserIdManager userIdManager, 83 UserManagerState userManagerState, ConfigStore configStore) { 84 mConfigStore = configStore; 85 mUserManagerState = userManagerState; 86 mUserIdManager = userIdManager; 87 mBannerControllers = new PerUser<BannerController>() { 88 @NonNull 89 @Override 90 protected BannerController create(@UserIdInt int userId) { 91 return createBannerController(context, UserHandle.of(userId), configStore); 92 } 93 }; 94 maybeInitialiseAndSetBannersForCurrentUser(); 95 } 96 97 @VisibleForTesting 98 @NonNull createBannerController(@onNull Context context, @NonNull UserHandle userHandle, @NonNull ConfigStore configStore)99 BannerController createBannerController(@NonNull Context context, 100 @NonNull UserHandle userHandle, @NonNull ConfigStore configStore) { 101 mConfigStore = configStore; 102 return new BannerController(context, userHandle, configStore); 103 } 104 getCurrentUserProfileId()105 @UserIdInt int getCurrentUserProfileId() { 106 if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) { 107 return mUserManagerState.getCurrentUserProfileId().getIdentifier(); 108 } 109 return mUserIdManager.getCurrentUserProfileId().getIdentifier(); 110 } 111 getBannerControllersPerUser()112 PerUser<BannerController> getBannerControllersPerUser() { 113 return mBannerControllers; 114 } 115 116 /** 117 * @return a {@link LiveData} that holds the value (once it's fetched) of the 118 * {@link android.content.ContentProvider#mAuthority authority} of the current 119 * {@link android.provider.CloudMediaProvider}. 120 */ 121 @NonNull getCloudMediaProviderAuthorityLiveData()122 MutableLiveData<String> getCloudMediaProviderAuthorityLiveData() { 123 return mCloudMediaProviderAuthority; 124 } 125 126 /** 127 * @return a {@link LiveData} that holds the value (once it's fetched) of the label 128 * of the current {@link android.provider.CloudMediaProvider}. 129 */ 130 @NonNull getCloudMediaProviderAppTitleLiveData()131 MutableLiveData<String> getCloudMediaProviderAppTitleLiveData() { 132 return mCloudMediaProviderLabel; 133 } 134 135 /** 136 * @return the account selection activity {@link Intent} of the current 137 * {@link android.provider.CloudMediaProvider}. 138 */ 139 @Nullable getChooseCloudMediaAccountActivityIntent()140 Intent getChooseCloudMediaAccountActivityIntent() { 141 return mChooseCloudMediaAccountActivityIntent; 142 } 143 144 145 /** 146 * Update the account selection activity {@link Intent} of the current 147 * {@link android.provider.CloudMediaProvider}. 148 */ setChooseCloudMediaAccountActivityIntent(Intent intent)149 void setChooseCloudMediaAccountActivityIntent(Intent intent) { 150 mChooseCloudMediaAccountActivityIntent = intent; 151 } 152 153 /** 154 * @return a {@link LiveData} that holds the value (once it's fetched) of the account name 155 * of the current {@link android.provider.CloudMediaProvider}. 156 */ 157 @NonNull getCloudMediaAccountNameLiveData()158 MutableLiveData<String> getCloudMediaAccountNameLiveData() { 159 return mCloudMediaAccountName; 160 } 161 162 /** 163 * @return the {@link LiveData} of the 'Choose App' banner visibility. 164 */ 165 @NonNull shouldShowChooseAppBannerLiveData()166 MutableLiveData<Boolean> shouldShowChooseAppBannerLiveData() { 167 return mShowChooseAppBanner; 168 } 169 170 /** 171 * @return the {@link LiveData} of the 'Cloud Media Available' banner visibility. 172 */ 173 @NonNull shouldShowCloudMediaAvailableBannerLiveData()174 MutableLiveData<Boolean> shouldShowCloudMediaAvailableBannerLiveData() { 175 return mShowCloudMediaAvailableBanner; 176 } 177 178 /** 179 * @return the {@link LiveData} of the 'Account Updated' banner visibility. 180 */ 181 @NonNull shouldShowAccountUpdatedBannerLiveData()182 MutableLiveData<Boolean> shouldShowAccountUpdatedBannerLiveData() { 183 return mShowAccountUpdatedBanner; 184 } 185 186 /** 187 * @return the {@link LiveData} of the 'Choose Account' banner visibility. 188 */ 189 @NonNull shouldShowChooseAccountBannerLiveData()190 MutableLiveData<Boolean> shouldShowChooseAccountBannerLiveData() { 191 return mShowChooseAccountBanner; 192 } 193 194 /** 195 * Dismiss (hide) the 'Choose App' banner for the current user. 196 */ 197 @MainThread onUserDismissedChooseAppBanner()198 void onUserDismissedChooseAppBanner() { 199 ThreadUtils.assertMainThread(); 200 201 if (Boolean.FALSE.equals(mShowChooseAppBanner.getValue())) { 202 Log.d(TAG, "Choose App banner visibility live data value is false on dismiss"); 203 } else { 204 mShowChooseAppBanner.setValue(false); 205 } 206 207 final BannerController bannerController = getCurrentBannerController(); 208 if (bannerController != null) { 209 bannerController.onUserDismissedChooseAppBanner(); 210 } 211 } 212 213 /** 214 * Dismiss (hide) the 'Cloud Media Available' banner for the current user. 215 */ 216 @MainThread onUserDismissedCloudMediaAvailableBanner()217 void onUserDismissedCloudMediaAvailableBanner() { 218 ThreadUtils.assertMainThread(); 219 220 if (Boolean.FALSE.equals(mShowCloudMediaAvailableBanner.getValue())) { 221 Log.d(TAG, "Cloud Media Available banner visibility live data value is false on " 222 + "dismiss"); 223 } else { 224 mShowCloudMediaAvailableBanner.setValue(false); 225 } 226 227 final BannerController bannerController = getCurrentBannerController(); 228 if (bannerController != null) { 229 bannerController.onUserDismissedCloudMediaAvailableBanner(); 230 } 231 } 232 233 /** 234 * Dismiss (hide) the 'Account Updated' banner for the current user. 235 */ 236 @MainThread onUserDismissedAccountUpdatedBanner()237 void onUserDismissedAccountUpdatedBanner() { 238 ThreadUtils.assertMainThread(); 239 240 if (Boolean.FALSE.equals(mShowAccountUpdatedBanner.getValue())) { 241 Log.d(TAG, "Account Updated banner visibility live data value is false on dismiss"); 242 } else { 243 mShowAccountUpdatedBanner.setValue(false); 244 } 245 246 final BannerController bannerController = getCurrentBannerController(); 247 if (bannerController != null) { 248 bannerController.onUserDismissedAccountUpdatedBanner(); 249 } 250 } 251 252 /** 253 * Dismiss (hide) the 'Choose Account' banner for the current user. 254 */ 255 @MainThread onUserDismissedChooseAccountBanner()256 void onUserDismissedChooseAccountBanner() { 257 ThreadUtils.assertMainThread(); 258 259 if (Boolean.FALSE.equals(mShowChooseAccountBanner.getValue())) { 260 Log.d(TAG, "Choose Account banner visibility live data value is false on dismiss"); 261 } else { 262 mShowChooseAccountBanner.setValue(false); 263 } 264 265 final BannerController bannerController = getCurrentBannerController(); 266 if (bannerController != null) { 267 bannerController.onUserDismissedChooseAccountBanner(); 268 } 269 } 270 271 @Nullable getCurrentBannerController()272 private BannerController getCurrentBannerController() { 273 final int currentUserProfileId = getCurrentUserProfileId(); 274 return mBannerControllers.get(currentUserProfileId); 275 } 276 277 /** 278 * Resets the banner controller per user and sets the banner data for the current user. 279 * 280 * Note - Since {@link BannerController#reset()} cannot be called in the Main thread, using 281 * {@link DataLoaderThread} here. 282 */ reset()283 void reset() { 284 for (int arrayIndex = 0, numControllers = mBannerControllers.size(); 285 arrayIndex < numControllers; arrayIndex++) { 286 final BannerController bannerController = mBannerControllers.valueAt(arrayIndex); 287 DataLoaderThread.getHandler().postDelayed(bannerController::reset, TOKEN, DELAY_MILLIS); 288 } 289 290 // Set the banner data for the current user 291 maybeInitialiseAndSetBannersForCurrentUser(); 292 } 293 294 /** 295 * Hide all the banners in the DataLoader thread. 296 * 297 * Since this is always followed by a reset, they need to be done in the same threads (currently 298 * DataLoaderThread thread). For the case when multiple hideAllBanners & reset are triggered 299 * simultaneously, this ensures that they are called sequentially for each such trigger. 300 * 301 * Post all the banner {@link LiveData} values as {@code false}. 302 */ hideAllBanners()303 void hideAllBanners() { 304 DataLoaderThread.getHandler().postDelayed(() -> { 305 mShowChooseAppBanner.postValue(false); 306 mShowCloudMediaAvailableBanner.postValue(false); 307 mShowAccountUpdatedBanner.postValue(false); 308 mShowChooseAccountBanner.postValue(false); 309 }, TOKEN, DELAY_MILLIS); 310 } 311 312 313 /** 314 * Initialise and set the banner data for the current user. 315 * 316 * No-op by default, overridden for cloud. 317 */ maybeInitialiseAndSetBannersForCurrentUser()318 void maybeInitialiseAndSetBannersForCurrentUser() { 319 // No-op, may be overridden 320 } 321 322 static class CloudBannerManager extends BannerManager { CloudBannerManager(@onNull Context context, @NonNull UserIdManager userIdManager, @NonNull ConfigStore configStore)323 CloudBannerManager(@NonNull Context context, @NonNull UserIdManager userIdManager, 324 @NonNull ConfigStore configStore) { 325 super(context, userIdManager, configStore); 326 } 327 CloudBannerManager(@onNull Context context, @NonNull UserManagerState userManagerState, @NonNull ConfigStore configStore)328 CloudBannerManager(@NonNull Context context, @NonNull UserManagerState userManagerState, 329 @NonNull ConfigStore configStore) { 330 super(context, userManagerState, configStore); 331 } 332 333 334 /** 335 * Initialise and set the banner data for the current user. 336 * 337 * 1. Get or create the {@link BannerController} for 338 * {@link UserIdManager#getCurrentUserProfileId()} using {@link PerUser#forUser(int)}. 339 * Since, the {@link BannerController} construction cannot be done in the Main thread, 340 * using {@link DataLoaderThread} here. 341 * 342 * 2. Post the updated {@link BannerController} {@link LiveData} values. 343 */ 344 @Override maybeInitialiseAndSetBannersForCurrentUser()345 void maybeInitialiseAndSetBannersForCurrentUser() { 346 final int currentUserProfileId = getCurrentUserProfileId(); 347 DataLoaderThread.getHandler().postDelayed(() -> { 348 // Get (iff exists) or create the banner controller for the current user 349 final BannerController bannerController = 350 getBannerControllersPerUser().forUser(currentUserProfileId); 351 // Post the banner related live data values from this current user banner controller 352 getCloudMediaProviderAuthorityLiveData() 353 .postValue(bannerController.getCloudMediaProviderAuthority()); 354 getCloudMediaProviderAppTitleLiveData() 355 .postValue(bannerController.getCloudMediaProviderLabel()); 356 getCloudMediaAccountNameLiveData() 357 .postValue(bannerController.getCloudMediaProviderAccountName()); 358 setChooseCloudMediaAccountActivityIntent( 359 bannerController.getChooseCloudMediaAccountActivityIntent()); 360 shouldShowChooseAppBannerLiveData() 361 .postValue(bannerController.shouldShowChooseAppBanner()); 362 shouldShowCloudMediaAvailableBannerLiveData() 363 .postValue(bannerController.shouldShowCloudMediaAvailableBanner()); 364 shouldShowAccountUpdatedBannerLiveData() 365 .postValue(bannerController.shouldShowAccountUpdatedBanner()); 366 shouldShowChooseAccountBannerLiveData() 367 .postValue(bannerController.shouldShowChooseAccountBanner()); 368 }, TOKEN, DELAY_MILLIS); 369 } 370 } 371 } 372