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