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