1 /* 2 * Copyright (C) 2021 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 android.content.Intent.ACTION_GET_CONTENT; 20 import static android.content.Intent.EXTRA_LOCAL_ONLY; 21 22 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED; 23 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; 24 25 import android.annotation.SuppressLint; 26 import android.app.Application; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.database.Cursor; 30 import android.provider.MediaStore; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.annotation.UiThread; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.lifecycle.AndroidViewModel; 39 import androidx.lifecycle.LiveData; 40 import androidx.lifecycle.MutableLiveData; 41 import androidx.lifecycle.Observer; 42 43 import com.android.internal.logging.InstanceId; 44 import com.android.internal.logging.InstanceIdSequence; 45 import com.android.modules.utils.build.SdkLevel; 46 import com.android.providers.media.ConfigStore; 47 import com.android.providers.media.MediaApplication; 48 import com.android.providers.media.photopicker.data.ItemsProvider; 49 import com.android.providers.media.photopicker.data.MuteStatus; 50 import com.android.providers.media.photopicker.data.Selection; 51 import com.android.providers.media.photopicker.data.UserIdManager; 52 import com.android.providers.media.photopicker.data.model.Category; 53 import com.android.providers.media.photopicker.data.model.Item; 54 import com.android.providers.media.photopicker.data.model.UserId; 55 import com.android.providers.media.photopicker.metrics.PhotoPickerUiEventLogger; 56 import com.android.providers.media.photopicker.util.MimeFilterUtils; 57 import com.android.providers.media.util.ForegroundThread; 58 import com.android.providers.media.util.MimeUtils; 59 60 import java.util.ArrayList; 61 import java.util.List; 62 63 /** 64 * PickerViewModel to store and handle data for PhotoPickerActivity. 65 */ 66 public class PickerViewModel extends AndroidViewModel { 67 public static final String TAG = "PhotoPicker"; 68 69 private static final int RECENT_MINIMUM_COUNT = 12; 70 71 private static final int INSTANCE_ID_MAX = 1 << 15; 72 73 @NonNull 74 @SuppressLint("StaticFieldLeak") 75 private final Context mAppContext; 76 77 private final Selection mSelection; 78 private final MuteStatus mMuteStatus; 79 80 // TODO(b/193857982): We keep these four data sets now, we may need to find a way to reduce the 81 // data set to reduce memories. 82 // The list of Items with all photos and videos 83 private MutableLiveData<List<Item>> mItemList; 84 // The list of Items with all photos and videos in category 85 private MutableLiveData<List<Item>> mCategoryItemList; 86 // The list of categories. 87 private MutableLiveData<List<Category>> mCategoryList; 88 89 private ItemsProvider mItemsProvider; 90 private UserIdManager mUserIdManager; 91 private BannerManager mBannerManager; 92 93 private InstanceId mInstanceId; 94 private PhotoPickerUiEventLogger mLogger; 95 96 private String[] mMimeTypeFilters = null; 97 private int mBottomSheetState; 98 99 private Category mCurrentCategory; 100 101 // Note - Must init banner manager on mIsUserSelectForApp / mIsLocalOnly updates 102 private boolean mIsUserSelectForApp; 103 private boolean mIsLocalOnly; 104 PickerViewModel(@onNull Application application)105 public PickerViewModel(@NonNull Application application) { 106 super(application); 107 mAppContext = application.getApplicationContext(); 108 mItemsProvider = new ItemsProvider(mAppContext); 109 mSelection = new Selection(); 110 mUserIdManager = UserIdManager.create(mAppContext); 111 mMuteStatus = new MuteStatus(); 112 mInstanceId = new InstanceIdSequence(INSTANCE_ID_MAX).newInstanceId(); 113 mLogger = new PhotoPickerUiEventLogger(); 114 mIsUserSelectForApp = false; 115 mIsLocalOnly = false; 116 // Must init banner manager on mIsUserSelectForApp / mIsLocalOnly updates 117 initBannerManager(); 118 } 119 120 @VisibleForTesting setItemsProvider(@onNull ItemsProvider itemsProvider)121 public void setItemsProvider(@NonNull ItemsProvider itemsProvider) { 122 mItemsProvider = itemsProvider; 123 } 124 125 @VisibleForTesting setUserIdManager(@onNull UserIdManager userIdManager)126 public void setUserIdManager(@NonNull UserIdManager userIdManager) { 127 mUserIdManager = userIdManager; 128 } 129 130 /** 131 * @return {@link UserIdManager} for this context. 132 */ getUserIdManager()133 public UserIdManager getUserIdManager() { 134 return mUserIdManager; 135 } 136 137 /** 138 * @return {@code mSelection} that manages the selection 139 */ getSelection()140 public Selection getSelection() { 141 return mSelection; 142 } 143 144 145 /** 146 * @return {@code mMuteStatus} that tracks the volume mute status of the video preview 147 */ getMuteStatus()148 public MuteStatus getMuteStatus() { 149 return mMuteStatus; 150 } 151 152 /** 153 * @return {@code mIsUserSelectForApp} if the picker is currently being used 154 * for the {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP} action. 155 */ isUserSelectForApp()156 public boolean isUserSelectForApp() { 157 return mIsUserSelectForApp; 158 } 159 160 /** 161 * @return a {@link LiveData} that holds the value (once it's fetched) of the 162 * {@link android.content.ContentProvider#mAuthority authority} of the current 163 * {@link android.provider.CloudMediaProvider}. 164 */ 165 @NonNull getCloudMediaProviderAuthorityLiveData()166 public LiveData<String> getCloudMediaProviderAuthorityLiveData() { 167 return mBannerManager.getCloudMediaProviderAuthorityLiveData(); 168 } 169 170 /** 171 * @return a {@link LiveData} that holds the value (once it's fetched) of the label 172 * of the current {@link android.provider.CloudMediaProvider}. 173 */ 174 @NonNull getCloudMediaProviderAppTitleLiveData()175 public LiveData<String> getCloudMediaProviderAppTitleLiveData() { 176 return mBannerManager.getCloudMediaProviderAppTitleLiveData(); 177 } 178 179 /** 180 * @return a {@link LiveData} that holds the value (once it's fetched) of the account name 181 * of the current {@link android.provider.CloudMediaProvider}. 182 */ 183 @NonNull getCloudMediaAccountNameLiveData()184 public LiveData<String> getCloudMediaAccountNameLiveData() { 185 return mBannerManager.getCloudMediaAccountNameLiveData(); 186 } 187 188 /** 189 * Reset PickerViewModel. 190 * @param switchToPersonalProfile is true then set personal profile as current profile. 191 */ 192 @UiThread reset(boolean switchToPersonalProfile)193 public void reset(boolean switchToPersonalProfile) { 194 // 1. Clear Selected items 195 mSelection.clearSelectedItems(); 196 // 2. Change profile to personal user 197 if (switchToPersonalProfile) { 198 mUserIdManager.setPersonalAsCurrentUserProfile(); 199 } 200 // 3. Update Item and Category lists 201 updateItems(); 202 updateCategories(); 203 // 4. Update Banners 204 // Note - Banners should always be updated after the items & categories to ensure a 205 // consistent UI. 206 mBannerManager.maybeResetAllBannerData(); 207 mBannerManager.maybeUpdateBannerLiveDatas(); 208 } 209 210 /** 211 * Update items, categories & banners on profile switched by the user. 212 */ 213 @UiThread onUserSwitchedProfile()214 public void onUserSwitchedProfile() { 215 updateItems(); 216 updateCategories(); 217 // Note - Banners should always be updated after the items & categories to ensure a 218 // consistent UI. 219 mBannerManager.maybeUpdateBannerLiveDatas(); 220 } 221 222 /** 223 * @return the list of Items with all photos and videos {@link #mItemList} on the device. 224 */ getItems()225 public LiveData<List<Item>> getItems() { 226 if (mItemList == null) { 227 updateItems(); 228 } 229 return mItemList; 230 } 231 loadItems(Category category, UserId userId)232 private List<Item> loadItems(Category category, UserId userId) { 233 final List<Item> items = new ArrayList<>(); 234 235 try (Cursor cursor = fetchItems(category, userId)) { 236 if (cursor == null || cursor.getCount() == 0) { 237 Log.d(TAG, "Didn't receive any items for " + category 238 + ", either cursor is null or cursor count is zero"); 239 return items; 240 } 241 242 while (cursor.moveToNext()) { 243 // TODO(b/188394433): Return userId in the cursor so that we do not need to pass it 244 // here again. 245 items.add(Item.fromCursor(cursor, userId)); 246 } 247 } 248 249 Log.d(TAG, "Loaded " + items.size() + " items in " + category + " for user " 250 + userId.toString()); 251 return items; 252 } 253 fetchItems(Category category, UserId userId)254 private Cursor fetchItems(Category category, UserId userId) { 255 if (shouldShowOnlyLocalFeatures()) { 256 return mItemsProvider.getLocalItems(category, /* limit */ -1, mMimeTypeFilters, userId); 257 } else { 258 return mItemsProvider.getAllItems(category, /* limit */ -1, mMimeTypeFilters, userId); 259 } 260 } 261 loadItemsAsync()262 private void loadItemsAsync() { 263 final UserId userId = mUserIdManager.getCurrentUserProfileId(); 264 ForegroundThread.getExecutor().execute(() -> { 265 mItemList.postValue(loadItems(Category.DEFAULT, userId)); 266 }); 267 } 268 269 /** 270 * Update the item List {@link #mItemList} 271 */ updateItems()272 public void updateItems() { 273 if (mItemList == null) { 274 mItemList = new MutableLiveData<>(); 275 } 276 loadItemsAsync(); 277 } 278 279 /** 280 * Get the list of all photos and videos with the specific {@code category} on the device. 281 * 282 * In our use case, we only keep the list of current category {@link #mCurrentCategory} in 283 * {@link #mCategoryItemList}. If the {@code category} and {@link #mCurrentCategory} are 284 * different, we will create the new LiveData to {@link #mCategoryItemList}. 285 * 286 * @param category the category we want to be queried 287 * @return the list of all photos and videos with the specific {@code category} 288 * {@link #mCategoryItemList} 289 */ getCategoryItems(@onNull Category category)290 public LiveData<List<Item>> getCategoryItems(@NonNull Category category) { 291 if (mCategoryItemList == null || !TextUtils.equals(mCurrentCategory.getId(), 292 category.getId())) { 293 mCategoryItemList = new MutableLiveData<>(); 294 mCurrentCategory = category; 295 } 296 updateCategoryItems(); 297 return mCategoryItemList; 298 } 299 loadCategoryItemsAsync()300 private void loadCategoryItemsAsync() { 301 final UserId userId = mUserIdManager.getCurrentUserProfileId(); 302 ForegroundThread.getExecutor().execute(() -> { 303 mCategoryItemList.postValue(loadItems(mCurrentCategory, userId)); 304 }); 305 } 306 307 /** 308 * Update the item List with the {@link #mCurrentCategory} {@link #mCategoryItemList} 309 * 310 * @throws IllegalStateException category and category items is not initiated before calling 311 * this method 312 */ 313 @VisibleForTesting updateCategoryItems()314 public void updateCategoryItems() { 315 if (mCategoryItemList == null || mCurrentCategory == null) { 316 throw new IllegalStateException("mCurrentCategory and mCategoryItemList are not" 317 + " initiated. Please call getCategoryItems before calling this method"); 318 } 319 loadCategoryItemsAsync(); 320 } 321 322 /** 323 * @return the list of Categories {@link #mCategoryList} 324 */ getCategories()325 public LiveData<List<Category>> getCategories() { 326 if (mCategoryList == null) { 327 updateCategories(); 328 } 329 return mCategoryList; 330 } 331 loadCategories(UserId userId)332 private List<Category> loadCategories(UserId userId) { 333 final List<Category> categoryList = new ArrayList<>(); 334 try (Cursor cursor = fetchCategories(userId)) { 335 if (cursor == null || cursor.getCount() == 0) { 336 Log.d(TAG, "Didn't receive any categories, either cursor is null or" 337 + " cursor count is zero"); 338 return categoryList; 339 } 340 341 while (cursor.moveToNext()) { 342 final Category category = Category.fromCursor(cursor, userId); 343 categoryList.add(category); 344 } 345 346 Log.d(TAG, 347 "Loaded " + categoryList.size() + " categories for user " + userId.toString()); 348 } 349 return categoryList; 350 } 351 fetchCategories(UserId userId)352 private Cursor fetchCategories(UserId userId) { 353 if (shouldShowOnlyLocalFeatures()) { 354 return mItemsProvider.getLocalCategories(mMimeTypeFilters, userId); 355 } else { 356 return mItemsProvider.getAllCategories(mMimeTypeFilters, userId); 357 } 358 } 359 loadCategoriesAsync()360 private void loadCategoriesAsync() { 361 final UserId userId = mUserIdManager.getCurrentUserProfileId(); 362 ForegroundThread.getExecutor().execute(() -> { 363 mCategoryList.postValue(loadCategories(userId)); 364 }); 365 } 366 367 /** 368 * Update the category List {@link #mCategoryList} 369 */ updateCategories()370 public void updateCategories() { 371 if (mCategoryList == null) { 372 mCategoryList = new MutableLiveData<>(); 373 } 374 loadCategoriesAsync(); 375 } 376 377 /** 378 * Return whether the {@link #mMimeTypeFilters} is {@code null} or not 379 */ hasMimeTypeFilters()380 public boolean hasMimeTypeFilters() { 381 return mMimeTypeFilters != null && mMimeTypeFilters.length > 0; 382 } 383 isAllImagesFilter()384 private boolean isAllImagesFilter() { 385 return mMimeTypeFilters != null && mMimeTypeFilters.length == 1 386 && MimeUtils.isAllImagesMimeType(mMimeTypeFilters[0]); 387 } 388 isAllVideosFilter()389 private boolean isAllVideosFilter() { 390 return mMimeTypeFilters != null && mMimeTypeFilters.length == 1 391 && MimeUtils.isAllVideosMimeType(mMimeTypeFilters[0]); 392 } 393 394 /** 395 * Parse values from {@code intent} and set corresponding fields 396 */ parseValuesFromIntent(Intent intent)397 public void parseValuesFromIntent(Intent intent) throws IllegalArgumentException { 398 mUserIdManager.setIntentAndCheckRestrictions(intent); 399 400 mMimeTypeFilters = MimeFilterUtils.getMimeTypeFilters(intent); 401 402 mSelection.parseSelectionValuesFromIntent(intent); 403 404 mIsLocalOnly = intent.getBooleanExtra(EXTRA_LOCAL_ONLY, false); 405 406 mIsUserSelectForApp = 407 MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.equals(intent.getAction()); 408 if (!SdkLevel.isAtLeastU() && mIsUserSelectForApp) { 409 throw new IllegalArgumentException("ACTION_USER_SELECT_IMAGES_FOR_APP is not enabled " 410 + " for this OS version"); 411 } 412 413 // Ensure that if Photopicker is being used for permissions the target app UID is present 414 // in the extras. 415 if (mIsUserSelectForApp 416 && (intent.getExtras() == null 417 || !intent.getExtras() 418 .containsKey(Intent.EXTRA_UID))) { 419 throw new IllegalArgumentException( 420 "EXTRA_UID is required for" + " ACTION_USER_SELECT_IMAGES_FOR_APP"); 421 } 422 423 // Must init banner manager on mIsUserSelectForApp / mIsLocalOnly updates 424 initBannerManager(); 425 } 426 initBannerManager()427 private void initBannerManager() { 428 mBannerManager = shouldShowOnlyLocalFeatures() 429 ? new BannerManager(mAppContext, mUserIdManager) 430 : new BannerManager.CloudBannerManager(mAppContext, mUserIdManager); 431 } 432 433 /** 434 * Set BottomSheet state 435 */ setBottomSheetState(int state)436 public void setBottomSheetState(int state) { 437 mBottomSheetState = state; 438 } 439 440 /** 441 * @return BottomSheet state 442 */ getBottomSheetState()443 public int getBottomSheetState() { 444 return mBottomSheetState; 445 } 446 447 /** 448 * Log picker opened metrics 449 */ logPickerOpened(int callingUid, String callingPackage, String intentAction)450 public void logPickerOpened(int callingUid, String callingPackage, String intentAction) { 451 if (getUserIdManager().isManagedUserSelected()) { 452 mLogger.logPickerOpenWork(mInstanceId, callingUid, callingPackage); 453 } else { 454 mLogger.logPickerOpenPersonal(mInstanceId, callingUid, callingPackage); 455 } 456 457 // TODO(b/235326735): Optimise logging multiple times on picker opened 458 // TODO(b/235326736): Check if we should add a metric for PICK_IMAGES intent to simplify 459 // metrics reading 460 if (ACTION_GET_CONTENT.equals(intentAction)) { 461 mLogger.logPickerOpenViaGetContent(mInstanceId, callingUid, callingPackage); 462 } 463 464 if (mBottomSheetState == STATE_COLLAPSED) { 465 mLogger.logPickerOpenInHalfScreen(mInstanceId, callingUid, callingPackage); 466 } else if (mBottomSheetState == STATE_EXPANDED) { 467 mLogger.logPickerOpenInFullScreen(mInstanceId, callingUid, callingPackage); 468 } 469 470 if (mSelection != null && mSelection.canSelectMultiple()) { 471 mLogger.logPickerOpenInMultiSelect(mInstanceId, callingUid, callingPackage); 472 } else { 473 mLogger.logPickerOpenInSingleSelect(mInstanceId, callingUid, callingPackage); 474 } 475 476 if (isAllImagesFilter()) { 477 mLogger.logPickerOpenWithFilterAllImages(mInstanceId, callingUid, callingPackage); 478 } else if (isAllVideosFilter()) { 479 mLogger.logPickerOpenWithFilterAllVideos(mInstanceId, callingUid, callingPackage); 480 } else if (hasMimeTypeFilters()) { 481 mLogger.logPickerOpenWithAnyOtherFilter(mInstanceId, callingUid, callingPackage); 482 } 483 484 maybeLogPickerOpenedWithCloudProvider(); 485 } 486 487 // TODO(b/245745412): Fix log params (uid & package name) 488 // TODO(b/245745424): Solve for active cloud provider without a logged in account maybeLogPickerOpenedWithCloudProvider()489 private void maybeLogPickerOpenedWithCloudProvider() { 490 if (shouldShowOnlyLocalFeatures()) { 491 return; 492 } 493 494 final LiveData<String> cloudMediaProviderAuthorityLiveData = 495 getCloudMediaProviderAuthorityLiveData(); 496 cloudMediaProviderAuthorityLiveData.observeForever(new Observer<String>() { 497 @Override 498 public void onChanged(@Nullable String providerAuthority) { 499 Log.d(TAG, "logPickerOpenedWithCloudProvider() provider=" + providerAuthority 500 + ", log=" + (providerAuthority != null)); 501 502 if (providerAuthority != null) { 503 mLogger.logPickerOpenWithActiveCloudProvider( 504 mInstanceId, /* cloudProviderUid */ -1, providerAuthority); 505 } 506 // We only need to get the value once. 507 cloudMediaProviderAuthorityLiveData.removeObserver(this); 508 } 509 }); 510 } 511 512 /** 513 * Log metrics to notify that the user has clicked Browse to open DocumentsUi 514 */ logBrowseToDocumentsUi(int callingUid, String callingPackage)515 public void logBrowseToDocumentsUi(int callingUid, String callingPackage) { 516 mLogger.logBrowseToDocumentsUi(mInstanceId, callingUid, callingPackage); 517 } 518 519 /** 520 * Log metrics to notify that the user has confirmed selection 521 */ logPickerConfirm(int callingUid, String callingPackage, int countOfItemsConfirmed)522 public void logPickerConfirm(int callingUid, String callingPackage, int countOfItemsConfirmed) { 523 if (getUserIdManager().isManagedUserSelected()) { 524 mLogger.logPickerConfirmWork(mInstanceId, callingUid, callingPackage, 525 countOfItemsConfirmed); 526 } else { 527 mLogger.logPickerConfirmPersonal(mInstanceId, callingUid, callingPackage, 528 countOfItemsConfirmed); 529 } 530 } 531 532 /** 533 * Log metrics to notify that the user has exited Picker without any selection 534 */ logPickerCancel(int callingUid, String callingPackage)535 public void logPickerCancel(int callingUid, String callingPackage) { 536 if (getUserIdManager().isManagedUserSelected()) { 537 mLogger.logPickerCancelWork(mInstanceId, callingUid, callingPackage); 538 } else { 539 mLogger.logPickerCancelPersonal(mInstanceId, callingUid, callingPackage); 540 } 541 } 542 getInstanceId()543 public InstanceId getInstanceId() { 544 return mInstanceId; 545 } 546 setInstanceId(InstanceId parcelable)547 public void setInstanceId(InstanceId parcelable) { 548 mInstanceId = parcelable; 549 } 550 551 // Return whether hotopicker's launch intent has extra {@link EXTRA_LOCAL_ONLY} set to true 552 // or not. 553 @VisibleForTesting isLocalOnly()554 boolean isLocalOnly() { 555 return mIsLocalOnly; 556 } 557 558 /** 559 * Return whether only the local features should be shown (the cloud features should be hidden). 560 * 561 * Show only the local features in the following cases - 562 * 1. Photo Picker is launched by the {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP} 563 * action for the permission flow. 564 * 2. Photo Picker is launched with the {@link Intent#EXTRA_LOCAL_ONLY} as {@code true} in the 565 * {@link Intent#ACTION_GET_CONTENT} or {@link MediaStore#ACTION_PICK_IMAGES} action. 566 * 3. Cloud Media in Photo picker is disabled, i.e., 567 * {@link ConfigStore#isCloudMediaInPhotoPickerEnabled()} is {@code false}. 568 * 569 * @return {@code true} iff either {@link #isUserSelectForApp()} or {@link #isLocalOnly()} is 570 * {@code true}, OR if {@link ConfigStore#isCloudMediaInPhotoPickerEnabled()} is {@code false}. 571 */ shouldShowOnlyLocalFeatures()572 public boolean shouldShowOnlyLocalFeatures() { 573 return isUserSelectForApp() || isLocalOnly() 574 || !getConfigStore().isCloudMediaInPhotoPickerEnabled(); 575 } 576 577 @VisibleForTesting getConfigStore()578 protected ConfigStore getConfigStore() { 579 return MediaApplication.getConfigStore(); 580 } 581 582 /** 583 * @return the {@link LiveData} of the 'Choose App' banner visibility. 584 */ 585 @NonNull shouldShowChooseAppBannerLiveData()586 public LiveData<Boolean> shouldShowChooseAppBannerLiveData() { 587 return mBannerManager.shouldShowChooseAppBannerLiveData(); 588 } 589 590 /** 591 * @return the {@link LiveData} of the 'Cloud Media Available' banner visibility. 592 */ 593 @NonNull shouldShowCloudMediaAvailableBannerLiveData()594 public LiveData<Boolean> shouldShowCloudMediaAvailableBannerLiveData() { 595 return mBannerManager.shouldShowCloudMediaAvailableBannerLiveData(); 596 } 597 598 /** 599 * @return the {@link LiveData} of the 'Account Updated' banner visibility. 600 */ 601 @NonNull shouldShowAccountUpdatedBannerLiveData()602 public LiveData<Boolean> shouldShowAccountUpdatedBannerLiveData() { 603 return mBannerManager.shouldShowAccountUpdatedBannerLiveData(); 604 } 605 606 /** 607 * @return the {@link LiveData} of the 'Choose Account' banner visibility. 608 */ 609 @NonNull shouldShowChooseAccountBannerLiveData()610 public LiveData<Boolean> shouldShowChooseAccountBannerLiveData() { 611 return mBannerManager.shouldShowChooseAccountBannerLiveData(); 612 } 613 614 /** 615 * Dismiss (hide) the 'Choose App' banner for the current user. 616 */ 617 @UiThread onUserDismissedChooseAppBanner()618 public void onUserDismissedChooseAppBanner() { 619 mBannerManager.onUserDismissedChooseAppBanner(); 620 } 621 622 /** 623 * Dismiss (hide) the 'Cloud Media Available' banner for the current user. 624 */ 625 @UiThread onUserDismissedCloudMediaAvailableBanner()626 public void onUserDismissedCloudMediaAvailableBanner() { 627 mBannerManager.onUserDismissedCloudMediaAvailableBanner(); 628 } 629 630 /** 631 * Dismiss (hide) the 'Account Updated' banner for the current user. 632 */ 633 @UiThread onUserDismissedAccountUpdatedBanner()634 public void onUserDismissedAccountUpdatedBanner() { 635 mBannerManager.onUserDismissedAccountUpdatedBanner(); 636 } 637 638 /** 639 * Dismiss (hide) the 'Choose Account' banner for the current user. 640 */ 641 @UiThread onUserDismissedChooseAccountBanner()642 public void onUserDismissedChooseAccountBanner() { 643 mBannerManager.onUserDismissedChooseAccountBanner(); 644 } 645 } 646