1 /*
2  * Copyright (C) 2022 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.settingslib.widget.ProfileSelectFragment.PERSONAL_TAB;
20 import static com.android.settingslib.widget.ProfileSelectFragment.WORK_TAB;
21 
22 import android.annotation.NonNull;
23 import android.annotation.RequiresApi;
24 import android.annotation.UserIdInt;
25 import android.app.ActivityManager;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.UserManager;
33 import android.util.Log;
34 import android.view.MenuItem;
35 
36 import androidx.appcompat.app.ActionBar;
37 import androidx.appcompat.app.AppCompatActivity;
38 import androidx.appcompat.widget.Toolbar;
39 import androidx.fragment.app.Fragment;
40 import androidx.fragment.app.FragmentManager;
41 import androidx.lifecycle.ViewModelProvider;
42 
43 import com.android.modules.utils.build.SdkLevel;
44 import com.android.providers.media.R;
45 import com.android.providers.media.photopicker.data.UserIdManager;
46 import com.android.providers.media.photopicker.data.UserManagerState;
47 import com.android.providers.media.photopicker.data.model.UserId;
48 import com.android.providers.media.photopicker.ui.settings.SettingsProfileSelectFragment;
49 import com.android.providers.media.photopicker.ui.settings.SettingsViewModel;
50 import com.android.providers.media.photopicker.util.RecentsPreviewUtil;
51 
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 
56 /**
57  * Photo Picker settings page where user can view/edit current cloud media provider.
58  */
59 public class PhotoPickerSettingsActivity extends AppCompatActivity {
60     private static final String TAG = "PickerSettings";
61     static final String EXTRA_CURRENT_USER_ID = "user_id";
62     private static final int DEFAULT_EXTRA_USER_ID = -1;
63     private ArrayList<String> mProfileActions;
64     private int mCallingUserId;
65     private static final int DEFAULT_TAB_USER_ID = ActivityManager.getCurrentUser();;
66 
67     @NonNull
68     private SettingsViewModel mSettingsViewModel;
69 
70     private final BroadcastReceiver mProfilesActionsReceiver =
71             new BroadcastReceiver() {
72                 @Override
73                 public void onReceive(Context context, Intent intent) {
74                     if (SdkLevel.isAtLeastV()
75                             && !isFinishing()
76                             && mProfileActions.contains(intent.getAction())) {
77                         mSettingsViewModel.getUserManagerState().resetUserIds();
78                         createAndShowFragment(mCallingUserId, /* allowReplace= */ true);
79                         updateRecentsVisibilitySetting();
80                     }
81                 }
82             };
83 
84     @Override
onCreate(Bundle savedInstanceState)85     protected void onCreate(Bundle savedInstanceState) {
86         // We use the device default theme as the base theme. Apply the material theme for the
87         // material components. We use force "false" here, only values that are not already defined
88         // in the base theme will be copied.
89         getTheme().applyStyle(R.style.PickerMaterialTheme, /* force */ false);
90 
91         // TODO(b/309578419): Make this activity handle insets properly and then remove this.
92         getTheme().applyStyle(R.style.OptOutEdgeToEdgeEnforcement, /* force */ false);
93 
94         super.onCreate(savedInstanceState);
95 
96         mSettingsViewModel =
97                 new ViewModelProvider(this).get(SettingsViewModel.class);
98         final Bundle extras = getIntent().getExtras();
99         if (extras != null) {
100             mCallingUserId = extras.getInt(EXTRA_CURRENT_USER_ID, DEFAULT_EXTRA_USER_ID);
101         } else {
102             mCallingUserId = DEFAULT_EXTRA_USER_ID;
103         }
104 
105         setContentView(R.layout.activity_photo_picker_settings);
106         displayActionBar();
107         createAndShowFragment(mCallingUserId, /* allowReplace= */ false);
108 
109         updateRecentsVisibilitySetting();
110 
111         // TODO: merge with CrossProfile listeners in the main Photo picker activity.
112         if (SdkLevel.isAtLeastV()) {
113             mProfileActions =
114                     new ArrayList<>(
115                             Arrays.asList(
116                                     Intent.ACTION_PROFILE_ADDED, Intent.ACTION_PROFILE_AVAILABLE,
117                                     Intent.ACTION_PROFILE_REMOVED,
118                                             Intent.ACTION_PROFILE_UNAVAILABLE));
119 
120             final IntentFilter profileFilter = new IntentFilter();
121             for (String action : mProfileActions) {
122                 profileFilter.addAction(action);
123             }
124             registerReceiver(mProfilesActionsReceiver, profileFilter);
125         }
126     }
127 
128     @Override
onDestroy()129     protected void onDestroy() {
130         super.onDestroy();
131         if (SdkLevel.isAtLeastV()) {
132             unregisterReceiver(mProfilesActionsReceiver);
133         }
134     }
135 
updateRecentsVisibilitySetting()136     private void updateRecentsVisibilitySetting() {
137         RecentsPreviewUtil.updateRecentsVisibilitySetting(mSettingsViewModel.getConfigStore(),
138                 mSettingsViewModel.getUserManagerState(), this);
139     }
140 
displayActionBar()141     private void displayActionBar() {
142         final Toolbar toolbar = findViewById(R.id.picker_settings_toolbar);
143         setSupportActionBar(toolbar);
144         final ActionBar actionBar = getSupportActionBar();
145         actionBar.setDisplayHomeAsUpEnabled(true);
146         actionBar.setDisplayShowTitleEnabled(false);
147     }
148 
149     @Override
onOptionsItemSelected(@onNull MenuItem item)150     public boolean onOptionsItemSelected(@NonNull MenuItem item) {
151         if (item.getItemId() == android.R.id.home) {
152             // Stop PhotoPickerSettingsActivity when back button is pressed.
153             finish();
154             return true;
155         }
156         return false;
157     }
158 
createAndShowFragment(@serIdInt int callingUserId, boolean allowReplace)159     private void createAndShowFragment(@UserIdInt int callingUserId, boolean allowReplace) {
160         final FragmentManager fragmentManager = getSupportFragmentManager();
161         if (!allowReplace
162                 && fragmentManager.findFragmentById(R.id.settings_fragment_container) != null) {
163             // Fragment already exists and is attached to this Activity.
164             // Nothing further needs to be done.
165             Log.d(TAG, "An instance of target fragment is already attached to the "
166                     + "PhotoPickerSettingsActivity. Not creating a new fragment.");
167             return;
168         }
169 
170         // Create a new fragment and attach it to this Activity. The new fragment could be of type
171         // SettingsProfileSelectFragment or SettingsCloudMediaSelectFragment.
172         final Fragment targetFragment = getTargetFragment(callingUserId);
173         fragmentManager.beginTransaction()
174                 .replace(R.id.settings_fragment_container, targetFragment)
175                 .commitAllowingStateLoss();
176         fragmentManager.executePendingTransactions();
177     }
178 
179     @RequiresApi(Build.VERSION_CODES.S)
getUserIdListToShowProfileTabs(UserManagerState userManagerState)180     private List<Integer> getUserIdListToShowProfileTabs(UserManagerState userManagerState) {
181         List<Integer> userIdList = new ArrayList<>();
182         for (UserId userId : userManagerState.getAllUserProfileIds()) {
183             if (!userManagerState.isProfileOff(userId)) {
184                 userIdList.add(userId.getIdentifier());
185             }
186         }
187         return userIdList;
188     }
189 
190     @NonNull
getTargetFragment(@serIdInt int callingUserId)191     private Fragment getTargetFragment(@UserIdInt int callingUserId) {
192         // Target fragment is SettingsProfileSelectFragment if there exists more than one
193         // UserHandles for profiles associated with the context user, including the user itself.
194         // Else target fragment is SettingsCloudMediaSelectFragment
195         boolean showOtherProfileTabs = false;
196         List<Integer> userIdListToShowProfileTabs = null;
197         if (mSettingsViewModel.getConfigStore().isPrivateSpaceInPhotoPickerEnabled()
198                 && SdkLevel.isAtLeastS()) {
199             final UserManagerState userManagerState = mSettingsViewModel.getUserManagerState();
200             userManagerState.updateProfileOffValues();
201             userIdListToShowProfileTabs = getUserIdListToShowProfileTabs(userManagerState);
202             showOtherProfileTabs = userIdListToShowProfileTabs.size() > 1;
203         } else {
204             final UserIdManager userIdManager = mSettingsViewModel.getUserIdManager();
205             if (userIdManager.isMultiUserProfiles()) {
206                 userIdManager.updateWorkProfileOffValue();
207                 // In case work profile exists and is turned off, do not show the work tab.
208                 if (!userIdManager.isWorkProfileOff()) {
209                     showOtherProfileTabs = true;
210                 }
211             }
212         }
213 
214         if (showOtherProfileTabs) {
215             final int selectedProfileTab = getInitialProfileTab(
216                     callingUserId, userIdListToShowProfileTabs);
217             // 'userIdProvided` represents whether we are working with userIds or tab positions.
218             // If we are working with tab’s user id that is only possible when
219             // `Private space feature flag is enabled && SdkLevel.isAtLeastV()` , this variable will
220             // be true and false otherwise
221             boolean userIdProvided = SdkLevel.isAtLeastV() && mSettingsViewModel
222                     .getConfigStore().isPrivateSpaceInPhotoPickerEnabled();
223             return SettingsProfileSelectFragment.getProfileSelectFragment(
224                     selectedProfileTab, userIdProvided, userIdListToShowProfileTabs);
225         }
226 
227         return getCloudMediaSelectFragment();
228     }
229 
230     @NonNull
getCloudMediaSelectFragment()231     private Fragment getCloudMediaSelectFragment() {
232         return SettingsProfileSelectFragment.getCloudMediaSelectFragment(
233                 UserId.CURRENT_USER.getIdentifier());
234     }
235 
236     /**
237      *  Returns the user id of the initially selected tab when `private space is enabled &&
238      * SdkLevel.isAtLeastV()` and for rest of the case it returns the tab position of the initially
239      * selected tab
240      */
getInitialProfileTab(@serIdInt int callingUserId, List<Integer> userIdList)241     private int getInitialProfileTab(@UserIdInt int callingUserId, List<Integer> userIdList) {
242         final UserManager userManager = getApplicationContext().getSystemService(UserManager.class);
243         int selectedTabId = callingUserId;
244         if (userManager == null || callingUserId == DEFAULT_EXTRA_USER_ID) {
245             selectedTabId = DEFAULT_TAB_USER_ID;
246         }
247 
248         // For all the cases when private space may exist on the device, we will work with tab
249         // user ids of different profiles and for rest of the case we will use tab positions to get
250         // personal and work profile tabs
251         if (SdkLevel.isAtLeastV()
252                 && mSettingsViewModel.getConfigStore().isPrivateSpaceInPhotoPickerEnabled()) {
253             if (!userIdList.contains(callingUserId)) {
254                 selectedTabId = DEFAULT_TAB_USER_ID;
255             }
256             return selectedTabId;
257         }
258 
259         return userManager.isManagedProfile(selectedTabId) ? WORK_TAB : PERSONAL_TAB;
260     }
261 }
262