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