1 /* 2 * Copyright (C) 2018 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.car.settings.accounts; 18 19 import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS; 20 21 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm; 22 23 import android.accounts.Account; 24 import android.accounts.AccountManager; 25 import android.car.drivingstate.CarUxRestrictions; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.UserInfo; 31 import android.graphics.drawable.Drawable; 32 import android.os.UserHandle; 33 34 import androidx.collection.ArrayMap; 35 import androidx.preference.Preference; 36 import androidx.preference.PreferenceCategory; 37 38 import com.android.car.settings.R; 39 import com.android.car.settings.common.FragmentController; 40 import com.android.car.settings.common.PreferenceController; 41 import com.android.car.settings.profiles.ProfileHelper; 42 import com.android.car.settings.profiles.ProfileUtils; 43 import com.android.car.ui.preference.CarUiPreference; 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.settingslib.accounts.AuthenticatorHelper; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.Comparator; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Set; 54 55 /** 56 * Controller for listing accounts. 57 * 58 * <p>Largely derived from {@link com.android.settings.accounts.AccountPreferenceController} 59 */ 60 public class AccountListPreferenceController extends 61 PreferenceController<PreferenceCategory> implements 62 AuthenticatorHelper.OnAccountsUpdateListener { 63 private static final String NO_ACCOUNT_PREF_KEY = "no_accounts_added"; 64 65 private final ArrayMap<String, Preference> mPreferences = new ArrayMap<>(); 66 67 private UserInfo mUserInfo; 68 private AuthenticatorHelper mAuthenticatorHelper; 69 private String[] mAuthorities; 70 private boolean mListenerRegistered = false; 71 private boolean mNeedToRefreshUserInfo = false; 72 73 private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() { 74 @Override 75 public void onReceive(Context context, Intent intent) { 76 onUsersUpdate(); 77 } 78 }; 79 AccountListPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)80 public AccountListPreferenceController(Context context, String preferenceKey, 81 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 82 super(context, preferenceKey, fragmentController, uxRestrictions); 83 mUserInfo = ProfileHelper.getInstance(context).getCurrentProcessUserInfo(); 84 mAuthenticatorHelper = createAuthenticatorHelper(); 85 } 86 87 /** Sets the account authorities that are available. */ setAuthorities(String[] authorities)88 public void setAuthorities(String[] authorities) { 89 mAuthorities = authorities; 90 } 91 92 @Override getPreferenceType()93 protected Class<PreferenceCategory> getPreferenceType() { 94 return PreferenceCategory.class; 95 } 96 97 @Override updateState(PreferenceCategory preference)98 protected void updateState(PreferenceCategory preference) { 99 forceUpdateAccountsCategory(); 100 } 101 102 @Override onCreateInternal()103 protected void onCreateInternal() { 104 super.onCreateInternal(); 105 setClickableWhileDisabled(getPreference(), /* clickable= */ true, p -> getProfileHelper() 106 .runClickableWhileDisabled(getContext(), getFragmentController())); 107 } 108 109 @Override getDefaultAvailabilityStatus()110 protected int getDefaultAvailabilityStatus() { 111 ProfileHelper profileHelper = getProfileHelper(); 112 boolean canModifyAccounts = profileHelper.canCurrentProcessModifyAccounts(); 113 114 if (canModifyAccounts) { 115 return AVAILABLE; 116 } 117 118 if (profileHelper.isDemoOrGuest() 119 || hasUserRestrictionByUm(getContext(), DISALLOW_MODIFY_ACCOUNTS)) { 120 return DISABLED_FOR_PROFILE; 121 } 122 123 return AVAILABLE_FOR_VIEWING; 124 } 125 126 /** 127 * Registers the account update and user update callbacks. 128 */ 129 @Override onStartInternal()130 protected void onStartInternal() { 131 mAuthenticatorHelper.listenToAccountUpdates(); 132 registerForUserEvents(); 133 mListenerRegistered = true; 134 135 /* refresh UserInfo only when restarting */ 136 if (mNeedToRefreshUserInfo) { 137 onUsersUpdate(); 138 } 139 } 140 141 /** 142 * Unregisters the account update and user update callbacks. 143 */ 144 @Override onStopInternal()145 protected void onStopInternal() { 146 mAuthenticatorHelper.stopListeningToAccountUpdates(); 147 unregisterForUserEvents(); 148 mListenerRegistered = false; 149 mNeedToRefreshUserInfo = true; 150 } 151 152 @Override onAccountsUpdate(UserHandle userHandle)153 public void onAccountsUpdate(UserHandle userHandle) { 154 if (userHandle.equals(mUserInfo.getUserHandle())) { 155 forceUpdateAccountsCategory(); 156 } 157 } 158 159 @VisibleForTesting onUsersUpdate()160 void onUsersUpdate() { 161 mUserInfo = ProfileUtils.getUserInfo(getContext(), mUserInfo.id); 162 forceUpdateAccountsCategory(); 163 } 164 165 @VisibleForTesting createAuthenticatorHelper()166 AuthenticatorHelper createAuthenticatorHelper() { 167 return new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(), this); 168 } 169 onAccountPreferenceClicked(AccountPreference preference)170 private boolean onAccountPreferenceClicked(AccountPreference preference) { 171 // Show the account's details when an account is clicked on. 172 getFragmentController().launchFragment(AccountDetailsFragment.newInstance( 173 preference.getAccount(), preference.getLabel(), mUserInfo)); 174 return true; 175 } 176 177 /** Forces a refresh of the account preferences. */ forceUpdateAccountsCategory()178 private void forceUpdateAccountsCategory() { 179 // Set the category title and include the user's name 180 getPreference().setTitle( 181 getContext().getString(R.string.account_list_title, mUserInfo.name)); 182 183 // Recreate the authentication helper to refresh the list of enabled accounts 184 mAuthenticatorHelper.stopListeningToAccountUpdates(); 185 mAuthenticatorHelper = createAuthenticatorHelper(); 186 if (mListenerRegistered) { 187 mAuthenticatorHelper.listenToAccountUpdates(); 188 } 189 190 Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet()); 191 List<? extends Preference> preferences = getAccountPreferences(preferencesToRemove); 192 // Add all preferences that aren't already shown. Manually set the order so that existing 193 // preferences are reordered correctly. 194 for (int i = 0; i < preferences.size(); i++) { 195 Preference pref = preferences.get(i); 196 pref.setOrder(i); 197 mPreferences.put(pref.getKey(), pref); 198 getPreference().addPreference(pref); 199 } 200 201 for (String key : preferencesToRemove) { 202 getPreference().removePreference(mPreferences.get(key)); 203 mPreferences.remove(key); 204 } 205 } 206 207 /** 208 * Returns a list of preferences corresponding to the accounts for the current user. 209 * 210 * <p> Derived from 211 * {@link com.android.settings.accounts.AccountPreferenceController#getAccountTypePreferences} 212 * 213 * @param preferencesToRemove the current preferences shown; only preferences to be removed will 214 * remain after method execution 215 */ getAccountPreferences( Set<String> preferencesToRemove)216 private List<? extends Preference> getAccountPreferences( 217 Set<String> preferencesToRemove) { 218 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes(); 219 ArrayList<AccountPreference> accountPreferences = 220 new ArrayList<>(accountTypes.length); 221 222 for (int i = 0; i < accountTypes.length; i++) { 223 String accountType = accountTypes[i]; 224 // Skip showing any account that does not have any of the requested authorities 225 if (!accountTypeHasAnyRequestedAuthorities(accountType)) { 226 continue; 227 } 228 CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType); 229 if (label == null) { 230 continue; 231 } 232 233 Account[] accounts = AccountManager.get(getContext()) 234 .getAccountsByTypeAsUser(accountType, mUserInfo.getUserHandle()); 235 Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType); 236 237 // Add a preference row for each individual account 238 for (Account account : accounts) { 239 String key = AccountPreference.buildKey(account); 240 AccountPreference preference = (AccountPreference) mPreferences.getOrDefault(key, 241 new AccountPreference(getContext(), account, label, icon)); 242 preference.setOnPreferenceClickListener( 243 (Preference pref) -> onAccountPreferenceClicked((AccountPreference) pref)); 244 245 accountPreferences.add(preference); 246 preferencesToRemove.remove(key); 247 } 248 mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType); 249 } 250 251 // If there are no accounts, return the "no account added" preference. 252 if (accountPreferences.isEmpty()) { 253 preferencesToRemove.remove(NO_ACCOUNT_PREF_KEY); 254 return Arrays.asList(mPreferences.getOrDefault(NO_ACCOUNT_PREF_KEY, 255 createNoAccountsAddedPreference())); 256 } 257 258 Collections.sort(accountPreferences, Comparator.comparing( 259 (AccountPreference a) -> a.getSummary().toString()) 260 .thenComparing((AccountPreference a) -> a.getTitle().toString())); 261 262 return accountPreferences; 263 } 264 createNoAccountsAddedPreference()265 private Preference createNoAccountsAddedPreference() { 266 CarUiPreference emptyPreference = new CarUiPreference(getContext()); 267 emptyPreference.setTitle(R.string.no_accounts_added); 268 emptyPreference.setKey(NO_ACCOUNT_PREF_KEY); 269 emptyPreference.setSelectable(false); 270 271 return emptyPreference; 272 } 273 registerForUserEvents()274 private void registerForUserEvents() { 275 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_INFO_CHANGED); 276 getContext().registerReceiver(mUserUpdateReceiver, filter); 277 } 278 unregisterForUserEvents()279 private void unregisterForUserEvents() { 280 getContext().unregisterReceiver(mUserUpdateReceiver); 281 } 282 283 284 /** 285 * Returns whether the account type has any of the authorities requested by the caller. 286 * 287 * <p> Derived from {@link AccountPreferenceController#accountTypeHasAnyRequestedAuthorities} 288 */ accountTypeHasAnyRequestedAuthorities(String accountType)289 private boolean accountTypeHasAnyRequestedAuthorities(String accountType) { 290 if (mAuthorities == null || mAuthorities.length == 0) { 291 // No authorities required 292 return true; 293 } 294 ArrayList<String> authoritiesForType = 295 mAuthenticatorHelper.getAuthoritiesForAccountType(accountType); 296 if (authoritiesForType == null) { 297 return false; 298 } 299 for (int j = 0; j < mAuthorities.length; j++) { 300 if (authoritiesForType.contains(mAuthorities[j])) { 301 return true; 302 } 303 } 304 return false; 305 } 306 307 @VisibleForTesting getProfileHelper()308 ProfileHelper getProfileHelper() { 309 return ProfileHelper.getInstance(getContext()); 310 } 311 312 private static class AccountPreference extends CarUiPreference { 313 /** Account that this Preference represents. */ 314 private final Account mAccount; 315 private final CharSequence mLabel; 316 AccountPreference(Context context, Account account, CharSequence label, Drawable icon)317 private AccountPreference(Context context, Account account, CharSequence label, 318 Drawable icon) { 319 super(context); 320 mAccount = account; 321 mLabel = label; 322 323 setKey(buildKey(account)); 324 setTitle(account.name); 325 setSummary(label); 326 setIcon(icon); 327 setShowChevron(false); 328 } 329 330 /** 331 * Build a unique preference key based on the account. 332 */ buildKey(Account account)333 public static String buildKey(Account account) { 334 return String.valueOf(account.hashCode()); 335 } 336 getAccount()337 public Account getAccount() { 338 return mAccount; 339 } 340 getLabel()341 public CharSequence getLabel() { 342 return mLabel; 343 } 344 } 345 } 346