• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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