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