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; 18 19 import static com.android.providers.media.photopicker.data.PickerResult.getPickerResponseIntent; 20 import static com.android.providers.media.photopicker.util.LayoutModeUtils.MODE_PHOTOS_TAB; 21 22 import android.app.Activity; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.res.Configuration; 28 import android.content.res.TypedArray; 29 import android.graphics.Color; 30 import android.graphics.Outline; 31 import android.graphics.Rect; 32 import android.graphics.drawable.ColorDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.os.Bundle; 35 import android.os.UserHandle; 36 import android.util.Log; 37 import android.view.MotionEvent; 38 import android.view.View; 39 import android.view.ViewOutlineProvider; 40 import android.view.WindowInsetsController; 41 import android.view.WindowManager; 42 import android.view.accessibility.AccessibilityManager; 43 44 import androidx.annotation.ColorInt; 45 import androidx.annotation.NonNull; 46 import androidx.annotation.VisibleForTesting; 47 import androidx.appcompat.app.AppCompatActivity; 48 import androidx.appcompat.widget.Toolbar; 49 import androidx.fragment.app.FragmentManager; 50 import androidx.lifecycle.ViewModelProvider; 51 52 import com.android.internal.logging.InstanceId; 53 import com.android.internal.logging.InstanceIdSequence; 54 import com.android.providers.media.R; 55 import com.android.providers.media.photopicker.data.Selection; 56 import com.android.providers.media.photopicker.data.UserIdManager; 57 import com.android.providers.media.photopicker.data.model.UserId; 58 import com.android.providers.media.photopicker.ui.TabContainerFragment; 59 import com.android.providers.media.photopicker.util.LayoutModeUtils; 60 import com.android.providers.media.photopicker.viewmodel.PickerViewModel; 61 62 import com.google.android.material.bottomsheet.BottomSheetBehavior; 63 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback; 64 import com.google.android.material.tabs.TabLayout; 65 import com.google.common.collect.Lists; 66 67 import java.util.List; 68 69 /** 70 * Photo Picker allows users to choose one or more photos and/or videos to share with an app. The 71 * app does not get access to all photos/videos. 72 */ 73 public class PhotoPickerActivity extends AppCompatActivity { 74 private static final String TAG = "PhotoPickerActivity"; 75 private static final float BOTTOM_SHEET_PEEK_HEIGHT_PERCENTAGE = 0.60f; 76 private static final float HIDE_PROFILE_BUTTON_THRESHOLD = -0.5f; 77 private static final String LOGGER_INSTANCE_ID_ARG = "loggerInstanceIdArg"; 78 79 private PickerViewModel mPickerViewModel; 80 private Selection mSelection; 81 private BottomSheetBehavior mBottomSheetBehavior; 82 private View mBottomBar; 83 private View mBottomSheetView; 84 private View mFragmentContainerView; 85 private View mDragBar; 86 private View mPrivacyText; 87 private View mProfileButton; 88 private TabLayout mTabLayout; 89 private Toolbar mToolbar; 90 private CrossProfileListeners mCrossProfileListeners; 91 92 @ColorInt 93 private int mDefaultBackgroundColor; 94 95 @ColorInt 96 private int mToolBarIconColor; 97 98 private int mToolbarHeight = 0; 99 private boolean mIsAccessibilityEnabled; 100 101 @Override onCreate(Bundle savedInstanceState)102 public void onCreate(Bundle savedInstanceState) { 103 // We use the device default theme as the base theme. Apply the material them for the 104 // material components. We use force "false" here, only values that are not already defined 105 // in the base theme will be copied. 106 getTheme().applyStyle(R.style.PickerMaterialTheme, /* force */ false); 107 108 super.onCreate(savedInstanceState); 109 110 setContentView(R.layout.activity_photo_picker); 111 112 mToolbar = findViewById(R.id.toolbar); 113 setSupportActionBar(mToolbar); 114 getSupportActionBar().setDisplayHomeAsUpEnabled(true); 115 116 final int[] attrs = new int[]{R.attr.actionBarSize, R.attr.pickerTextColor}; 117 final TypedArray ta = obtainStyledAttributes(attrs); 118 // Save toolbar height so that we can use it as padding for FragmentContainerView 119 mToolbarHeight = ta.getDimensionPixelSize(/* index */ 0, /* defValue */ -1); 120 mToolBarIconColor = ta.getColor(/* index */ 1,/* defValue */ -1); 121 ta.recycle(); 122 123 mDefaultBackgroundColor = getColor(R.color.picker_background_color); 124 mPickerViewModel = createViewModel(); 125 mSelection = mPickerViewModel.getSelection(); 126 127 try { 128 mPickerViewModel.parseValuesFromIntent(getIntent()); 129 } catch (IllegalArgumentException e) { 130 Log.e(TAG, "Finished activity due to an exception while parsing extras", e); 131 setCancelledResultAndFinishSelf(); 132 } 133 134 mDragBar = findViewById(R.id.drag_bar); 135 mPrivacyText = findViewById(R.id.privacy_text); 136 mBottomBar = findViewById(R.id.picker_bottom_bar); 137 mProfileButton = findViewById(R.id.profile_button); 138 139 mTabLayout = findViewById(R.id.tab_layout); 140 141 AccessibilityManager accessibilityManager = getSystemService(AccessibilityManager.class); 142 mIsAccessibilityEnabled = accessibilityManager.isEnabled(); 143 accessibilityManager.addAccessibilityStateChangeListener( 144 enabled -> mIsAccessibilityEnabled = enabled); 145 146 initBottomSheetBehavior(); 147 restoreState(savedInstanceState); 148 149 // Call this after state is restored, to use the correct LOGGER_INSTANCE_ID_ARG 150 mPickerViewModel.logPickerOpened(getCallingPackage()); 151 152 // Save the fragment container layout so that we can adjust the padding based on preview or 153 // non-preview mode. 154 mFragmentContainerView = findViewById(R.id.fragment_container); 155 156 mCrossProfileListeners = new CrossProfileListeners(); 157 } 158 159 @Override onDestroy()160 public void onDestroy() { 161 super.onDestroy(); 162 // This is required to unregister any broadcast receivers. 163 mCrossProfileListeners.onDestroy(); 164 } 165 166 /** 167 * Warning: This method is needed for tests, we are not customizing anything here. 168 * Allowing ourselves to control ViewModel creation helps us mock the ViewModel for test. 169 */ 170 @VisibleForTesting 171 @NonNull createViewModel()172 protected PickerViewModel createViewModel() { 173 return new ViewModelProvider(this).get(PickerViewModel.class); 174 } 175 176 @Override dispatchTouchEvent(MotionEvent event)177 public boolean dispatchTouchEvent(MotionEvent event){ 178 if (event.getAction() == MotionEvent.ACTION_DOWN) { 179 if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) { 180 181 Rect outRect = new Rect(); 182 mBottomSheetView.getGlobalVisibleRect(outRect); 183 184 if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) { 185 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); 186 } 187 } 188 } 189 return super.dispatchTouchEvent(event); 190 } 191 192 @Override onSupportNavigateUp()193 public boolean onSupportNavigateUp() { 194 onBackPressed(); 195 return true; 196 } 197 198 @Override setTitle(CharSequence title)199 public void setTitle(CharSequence title) { 200 super.setTitle(title); 201 getSupportActionBar().setTitle(title); 202 } 203 204 /** 205 * Called when owning activity is saving state to be used to restore state during creation. 206 * 207 * @param state Bundle to save state 208 */ 209 @Override onSaveInstanceState(Bundle state)210 public void onSaveInstanceState(Bundle state) { 211 super.onSaveInstanceState(state); 212 saveBottomSheetState(); 213 state.putParcelable(LOGGER_INSTANCE_ID_ARG, mPickerViewModel.getInstanceId()); 214 } 215 restoreState(Bundle savedInstanceState)216 private void restoreState(Bundle savedInstanceState) { 217 if (savedInstanceState != null) { 218 restoreBottomSheetState(); 219 mPickerViewModel.setInstanceId( 220 savedInstanceState.getParcelable(LOGGER_INSTANCE_ID_ARG)); 221 } else { 222 setupInitialLaunchState(); 223 } 224 } 225 226 /** 227 * Sets up states for the initial launch. This includes updating common layouts, selecting 228 * Photos tab item and saving the current bottom sheet state for later. 229 */ setupInitialLaunchState()230 private void setupInitialLaunchState() { 231 updateCommonLayouts(MODE_PHOTOS_TAB, /* title */ ""); 232 TabContainerFragment.show(getSupportFragmentManager()); 233 saveBottomSheetState(); 234 } 235 initBottomSheetBehavior()236 private void initBottomSheetBehavior() { 237 mBottomSheetView = findViewById(R.id.bottom_sheet); 238 mBottomSheetBehavior = BottomSheetBehavior.from(mBottomSheetView); 239 initStateForBottomSheet(); 240 241 mBottomSheetBehavior.addBottomSheetCallback(createBottomSheetCallBack()); 242 setRoundedCornersForBottomSheet(); 243 } 244 createBottomSheetCallBack()245 private BottomSheetCallback createBottomSheetCallBack() { 246 return new BottomSheetCallback() { 247 private boolean mIsHiddenDueToBottomSheetClosing = false; 248 @Override 249 public void onStateChanged(@NonNull View bottomSheet, int newState) { 250 if (newState == BottomSheetBehavior.STATE_HIDDEN) { 251 finish(); 252 } 253 saveBottomSheetState(); 254 } 255 256 @Override 257 public void onSlide(@NonNull View bottomSheet, float slideOffset) { 258 // slideOffset = -1 is when bottomsheet is completely hidden 259 // slideOffset = 0 is when bottomsheet is in collapsed mode 260 // slideOffset = 1 is when bottomsheet is in expanded mode 261 // We hide the Profile button if the bottomsheet is 50% in between collapsed state 262 // and hidden state. 263 if (slideOffset < HIDE_PROFILE_BUTTON_THRESHOLD && 264 mProfileButton.getVisibility() == View.VISIBLE) { 265 mProfileButton.setVisibility(View.GONE); 266 mIsHiddenDueToBottomSheetClosing = true; 267 return; 268 } 269 270 // We need to handle this state if the user is swiping till the bottom of the 271 // screen but then swipes up bottom sheet suddenly 272 if (slideOffset > HIDE_PROFILE_BUTTON_THRESHOLD && 273 mIsHiddenDueToBottomSheetClosing) { 274 mProfileButton.setVisibility(View.VISIBLE); 275 mIsHiddenDueToBottomSheetClosing = false; 276 } 277 } 278 }; 279 } 280 setRoundedCornersForBottomSheet()281 private void setRoundedCornersForBottomSheet() { 282 final float cornerRadius = 283 getResources().getDimensionPixelSize(R.dimen.picker_top_corner_radius); 284 final ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() { 285 @Override 286 public void getOutline(final View view, final Outline outline) { 287 outline.setRoundRect(0, 0, view.getWidth(), 288 (int)(view.getHeight() + cornerRadius), cornerRadius); 289 } 290 }; 291 mBottomSheetView.setOutlineProvider(viewOutlineProvider); 292 } 293 initStateForBottomSheet()294 private void initStateForBottomSheet() { 295 if (!mIsAccessibilityEnabled && !mSelection.canSelectMultiple() 296 && !isOrientationLandscape()) { 297 final int peekHeight = getBottomSheetPeekHeight(this); 298 mBottomSheetBehavior.setPeekHeight(peekHeight); 299 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); 300 } else { 301 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 302 mBottomSheetBehavior.setSkipCollapsed(true); 303 } 304 } 305 getBottomSheetPeekHeight(Context context)306 private static int getBottomSheetPeekHeight(Context context) { 307 final WindowManager windowManager = context.getSystemService(WindowManager.class); 308 final Rect displayBounds = windowManager.getCurrentWindowMetrics().getBounds(); 309 return (int) (displayBounds.height() * BOTTOM_SHEET_PEEK_HEIGHT_PERCENTAGE); 310 } 311 restoreBottomSheetState()312 private void restoreBottomSheetState() { 313 // BottomSheet is always EXPANDED for landscape 314 if (isOrientationLandscape()) { 315 return; 316 } 317 final int savedState = mPickerViewModel.getBottomSheetState(); 318 if (isValidBottomSheetState(savedState)) { 319 mBottomSheetBehavior.setState(savedState); 320 } 321 } 322 saveBottomSheetState()323 private void saveBottomSheetState() { 324 // Do not save state for landscape or preview mode. This is because they are always in 325 // STATE_EXPANDED state. 326 if (isOrientationLandscape() || !mBottomSheetView.getClipToOutline()) { 327 return; 328 } 329 mPickerViewModel.setBottomSheetState(mBottomSheetBehavior.getState()); 330 } 331 isValidBottomSheetState(int state)332 private boolean isValidBottomSheetState(int state) { 333 return state == BottomSheetBehavior.STATE_COLLAPSED || 334 state == BottomSheetBehavior.STATE_EXPANDED; 335 } 336 isOrientationLandscape()337 private boolean isOrientationLandscape() { 338 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; 339 } 340 setResultAndFinishSelf()341 public void setResultAndFinishSelf() { 342 setResult(Activity.RESULT_OK, getPickerResponseIntent(mSelection.canSelectMultiple(), 343 mSelection.getSelectedItems())); 344 finish(); 345 } 346 setCancelledResultAndFinishSelf()347 private void setCancelledResultAndFinishSelf() { 348 setResult(Activity.RESULT_CANCELED); 349 finish(); 350 } 351 352 /** 353 * Updates the common views such as Title, Toolbar, Navigation bar, status bar and bottom sheet 354 * behavior 355 * 356 * @param mode {@link LayoutModeUtils.Mode} which describes the layout mode to update. 357 * @param title the title to set for the Activity 358 */ updateCommonLayouts(LayoutModeUtils.Mode mode, String title)359 public void updateCommonLayouts(LayoutModeUtils.Mode mode, String title) { 360 updateTitle(title); 361 updateToolbar(mode); 362 updateStatusBarAndNavigationBar(mode); 363 updateBottomSheetBehavior(mode); 364 updateFragmentContainerViewPadding(mode); 365 updateDragBarVisibility(mode); 366 updatePrivacyTextVisibility(mode); 367 // The bottom bar and profile button are not shown on preview, hide them in preview. We 368 // handle the visibility of them in TabFragment. We don't need to make them shown in 369 // non-preview page here. 370 if (mode.isPreview) { 371 mBottomBar.setVisibility(View.GONE); 372 mProfileButton.setVisibility(View.GONE); 373 } 374 } 375 updateTitle(String title)376 private void updateTitle(String title) { 377 setTitle(title); 378 } 379 380 /** 381 * Updates the icons and show/hide the tab layout with {@code mode}. 382 * 383 * @param mode {@link LayoutModeUtils.Mode} which describes the layout mode to update. 384 */ updateToolbar(@onNull LayoutModeUtils.Mode mode)385 private void updateToolbar(@NonNull LayoutModeUtils.Mode mode) { 386 final boolean isPreview = mode.isPreview; 387 final boolean shouldShowTabLayout = mode.isPhotosTabOrAlbumsTab; 388 // 1. Set the tabLayout visibility 389 mTabLayout.setVisibility(shouldShowTabLayout ? View.VISIBLE : View.GONE); 390 391 // 2. Set the toolbar color 392 final ColorDrawable toolbarColor; 393 if (isPreview && !shouldShowTabLayout) { 394 if (isOrientationLandscape()) { 395 // Toolbar in Preview will have transparent color in Landscape mode. 396 toolbarColor = new ColorDrawable(getColor(android.R.color.transparent)); 397 } else { 398 // Toolbar in Preview will have a solid color with 90% opacity in Portrait mode. 399 toolbarColor = new ColorDrawable(getColor(R.color.preview_scrim_solid_color)); 400 } 401 } else { 402 toolbarColor = new ColorDrawable(mDefaultBackgroundColor); 403 } 404 getSupportActionBar().setBackgroundDrawable(toolbarColor); 405 406 // 3. Set the toolbar icon. 407 final Drawable icon; 408 if (shouldShowTabLayout) { 409 icon = getDrawable(R.drawable.ic_close); 410 } else { 411 icon = getDrawable(R.drawable.ic_arrow_back); 412 // Preview mode has dark background, hence icons will be WHITE in color 413 icon.setTint(isPreview ? Color.WHITE : mToolBarIconColor); 414 } 415 getSupportActionBar().setHomeAsUpIndicator(icon); 416 getSupportActionBar().setHomeActionContentDescription( 417 shouldShowTabLayout ? android.R.string.cancel 418 : R.string.abc_action_bar_up_description); 419 } 420 421 /** 422 * Updates status bar and navigation bar 423 * 424 * @param mode {@link LayoutModeUtils.Mode} which describes the layout mode to update. 425 */ updateStatusBarAndNavigationBar(@onNull LayoutModeUtils.Mode mode)426 private void updateStatusBarAndNavigationBar(@NonNull LayoutModeUtils.Mode mode) { 427 final boolean isPreview = mode.isPreview; 428 final int navigationBarColor = isPreview ? getColor(R.color.preview_background_color) : 429 mDefaultBackgroundColor; 430 getWindow().setNavigationBarColor(navigationBarColor); 431 432 final int statusBarColor = isPreview ? getColor(R.color.preview_background_color) : 433 getColor(android.R.color.transparent); 434 getWindow().setStatusBarColor(statusBarColor); 435 436 // Update the system bar appearance 437 final int mask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; 438 int appearance = 0; 439 if (!isPreview) { 440 final int uiModeNight = 441 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; 442 443 if (uiModeNight == Configuration.UI_MODE_NIGHT_NO) { 444 // If the system is not in Dark theme, set the system bars to light mode. 445 appearance = mask; 446 } 447 } 448 getWindow().getInsetsController().setSystemBarsAppearance(appearance, mask); 449 } 450 451 /** 452 * Updates the bottom sheet behavior 453 * 454 * @param mode {@link LayoutModeUtils.Mode} which describes the layout mode to update. 455 */ updateBottomSheetBehavior(@onNull LayoutModeUtils.Mode mode)456 private void updateBottomSheetBehavior(@NonNull LayoutModeUtils.Mode mode) { 457 final boolean isPreview = mode.isPreview; 458 if (mBottomSheetView != null) { 459 mBottomSheetView.setClipToOutline(!isPreview); 460 // TODO(b/197241815): Add animation downward swipe for preview should go back to 461 // the photo in photos grid 462 mBottomSheetBehavior.setDraggable(!isPreview); 463 } 464 if (isPreview) { 465 if (mBottomSheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) { 466 // Sets bottom sheet behavior state to STATE_EXPANDED if it's not already expanded. 467 // This is useful when user goes to Preview mode which is always Full screen. 468 // TODO(b/197241815): Add animation preview to full screen and back transition to 469 // partial screen. This is similar to long press animation. 470 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 471 } 472 } else { 473 restoreBottomSheetState(); 474 } 475 } 476 477 /** 478 * Updates the FragmentContainerView padding. 479 * <p> 480 * For Preview mode, toolbar overlaps the Fragment content, hence the padding will be set to 0. 481 * For Non-Preview mode, toolbar doesn't overlap the contents of the fragment, hence we set the 482 * padding as the height of the toolbar. 483 */ updateFragmentContainerViewPadding(@onNull LayoutModeUtils.Mode mode)484 private void updateFragmentContainerViewPadding(@NonNull LayoutModeUtils.Mode mode) { 485 if (mFragmentContainerView == null) return; 486 487 final int topPadding; 488 if (mode.isPreview) { 489 topPadding = 0; 490 } else { 491 topPadding = mToolbarHeight; 492 } 493 494 mFragmentContainerView.setPadding(mFragmentContainerView.getPaddingLeft(), 495 topPadding, mFragmentContainerView.getPaddingRight(), 496 mFragmentContainerView.getPaddingBottom()); 497 } 498 updateDragBarVisibility(@onNull LayoutModeUtils.Mode mode)499 private void updateDragBarVisibility(@NonNull LayoutModeUtils.Mode mode) { 500 final boolean shouldShowDragBar = !mode.isPreview; 501 mDragBar.setVisibility(shouldShowDragBar ? View.VISIBLE : View.GONE); 502 } 503 updatePrivacyTextVisibility(@onNull LayoutModeUtils.Mode mode)504 private void updatePrivacyTextVisibility(@NonNull LayoutModeUtils.Mode mode) { 505 // The privacy text is only shown on the Photos tab and Albums tab 506 final boolean shouldShowPrivacyMessage = mode.isPhotosTabOrAlbumsTab; 507 mPrivacyText.setVisibility(shouldShowPrivacyMessage ? View.VISIBLE : View.GONE); 508 } 509 510 private class CrossProfileListeners { 511 512 private final List<String> MANAGED_PROFILE_FILTER_ACTIONS = Lists.newArrayList( 513 Intent.ACTION_MANAGED_PROFILE_ADDED, // add profile button switch 514 Intent.ACTION_MANAGED_PROFILE_REMOVED, // remove profile button switch 515 Intent.ACTION_MANAGED_PROFILE_UNLOCKED, // activate profile button switch 516 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE // disable profile button switch 517 ); 518 519 private final UserIdManager mUserIdManager; 520 CrossProfileListeners()521 public CrossProfileListeners() { 522 mUserIdManager = mPickerViewModel.getUserIdManager(); 523 524 registerBroadcastReceivers(); 525 } 526 onDestroy()527 public void onDestroy() { 528 unregisterReceiver(mReceiver); 529 } 530 531 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 532 @Override 533 public void onReceive(Context context, Intent intent) { 534 final String action = intent.getAction(); 535 536 final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); 537 final UserId userId = UserId.of(userHandle); 538 539 // We only need to refresh the layout when the received profile user is the 540 // managed user corresponding to the current profile or a new work profile is added 541 // for the current user. 542 if (!userId.equals(mUserIdManager.getManagedUserId()) && 543 !action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) { 544 return; 545 } 546 547 switch (action) { 548 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: 549 handleWorkProfileOff(); 550 break; 551 case Intent.ACTION_MANAGED_PROFILE_REMOVED: 552 handleWorkProfileRemoved(); 553 break; 554 case Intent.ACTION_MANAGED_PROFILE_UNLOCKED: 555 handleWorkProfileOn(); 556 break; 557 case Intent.ACTION_MANAGED_PROFILE_ADDED: 558 handleWorkProfileAdded(); 559 break; 560 default: 561 // do nothing 562 } 563 } 564 }; 565 registerBroadcastReceivers()566 private void registerBroadcastReceivers() { 567 final IntentFilter managedProfileFilter = new IntentFilter(); 568 for (String managedProfileAction : MANAGED_PROFILE_FILTER_ACTIONS) { 569 managedProfileFilter.addAction(managedProfileAction); 570 } 571 registerReceiver(mReceiver, managedProfileFilter); 572 } 573 handleWorkProfileOff()574 private void handleWorkProfileOff() { 575 if (mUserIdManager.isManagedUserSelected()) { 576 switchToPersonalProfileInitialLaunchState(); 577 } 578 mUserIdManager.updateWorkProfileOffValue(); 579 } 580 handleWorkProfileRemoved()581 private void handleWorkProfileRemoved() { 582 if (mUserIdManager.isManagedUserSelected()) { 583 switchToPersonalProfileInitialLaunchState(); 584 } 585 mUserIdManager.resetUserIds(); 586 } 587 handleWorkProfileAdded()588 private void handleWorkProfileAdded() { 589 mUserIdManager.resetUserIds(); 590 } 591 handleWorkProfileOn()592 private void handleWorkProfileOn() { 593 // Update UI for switch to profile button 594 // When the managed profile becomes available, the provider may not be available 595 // immediately, we need to check if it is ready before we reload the content. 596 mUserIdManager.waitForMediaProviderToBeAvailable(); 597 } 598 switchToPersonalProfileInitialLaunchState()599 private void switchToPersonalProfileInitialLaunchState() { 600 final FragmentManager fragmentManager = getSupportFragmentManager(); 601 // Clear all back stacks in FragmentManager 602 fragmentManager.popBackStackImmediate(/* name */ null, 603 FragmentManager.POP_BACK_STACK_INCLUSIVE); 604 605 // We reset the state of the PhotoPicker as we do not want to make any 606 // assumptions on the state of the PhotoPicker when it was in Work Profile mode. 607 resetToPersonalProfile(); 608 } 609 610 /** 611 * Reset to Photo Picker initial launch state (Photos grid tab) in personal profile mode. 612 */ resetToPersonalProfile()613 private void resetToPersonalProfile() { 614 mPickerViewModel.resetToPersonalProfile(); 615 setupInitialLaunchState(); 616 } 617 } 618 } 619