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.AccountManager; 20 import android.accounts.AuthenticatorDescription; 21 import android.car.drivingstate.CarUxRestrictions; 22 import android.car.userlib.CarUserManagerHelper; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.drawable.Drawable; 26 import android.os.Handler; 27 import android.os.UserHandle; 28 29 import androidx.annotation.Nullable; 30 import androidx.annotation.VisibleForTesting; 31 import androidx.collection.ArrayMap; 32 import androidx.preference.PreferenceGroup; 33 34 import com.android.car.settings.common.FragmentController; 35 import com.android.car.settings.common.PreferenceController; 36 import com.android.car.ui.preference.CarUiPreference; 37 import com.android.settingslib.accounts.AuthenticatorHelper; 38 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.Comparator; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Set; 45 46 /** 47 * Controller for showing the user the list of accounts they can add. 48 * 49 * <p>Largely derived from {@link com.android.settings.accounts.ChooseAccountActivity} 50 */ 51 public class ChooseAccountPreferenceController extends 52 PreferenceController<PreferenceGroup> implements 53 AuthenticatorHelper.OnAccountsUpdateListener { 54 private static final int ADD_ACCOUNT_REQUEST_CODE = 1; 55 56 private final UserHandle mUserHandle; 57 private AuthenticatorHelper mAuthenticatorHelper; 58 private List<String> mAuthorities; 59 private Set<String> mAccountTypesFilter; 60 private Set<String> mAccountTypesExclusionFilter; 61 private ArrayMap<String, AuthenticatorDescriptionPreference> mPreferences = new ArrayMap<>(); 62 private boolean mIsStarted = false; 63 private boolean mHasPendingBack = false; 64 ChooseAccountPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)65 public ChooseAccountPreferenceController(Context context, String preferenceKey, 66 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 67 super(context, preferenceKey, fragmentController, uxRestrictions); 68 mUserHandle = new CarUserManagerHelper(context).getCurrentProcessUserInfo().getUserHandle(); 69 70 HashSet<String> accountTypeExclusionFilter = new HashSet<>(); 71 72 // Hardcoding bluetooth account type 73 accountTypeExclusionFilter.add("com.android.bluetooth.pbapsink"); 74 setAccountTypesExclusionFilter(accountTypeExclusionFilter); 75 } 76 77 /** Sets the authorities that the user has. */ setAuthorities(List<String> authorities)78 public void setAuthorities(List<String> authorities) { 79 mAuthorities = authorities; 80 } 81 82 /** Sets the filter for accounts that should be shown. */ setAccountTypesFilter(Set<String> accountTypesFilter)83 public void setAccountTypesFilter(Set<String> accountTypesFilter) { 84 mAccountTypesFilter = accountTypesFilter; 85 } 86 87 /** Sets the filter for accounts that should NOT be shown. */ setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter)88 protected void setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter) { 89 mAccountTypesExclusionFilter = accountTypesExclusionFilterFilter; 90 } 91 92 @Override getPreferenceType()93 protected Class<PreferenceGroup> getPreferenceType() { 94 return PreferenceGroup.class; 95 } 96 97 @Override updateState(PreferenceGroup preferenceGroup)98 protected void updateState(PreferenceGroup preferenceGroup) { 99 forceUpdateAccountsCategory(); 100 } 101 102 /** Initializes the authenticator helper. */ 103 @Override onCreateInternal()104 protected void onCreateInternal() { 105 mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserHandle, this); 106 } 107 108 /** 109 * Registers the account update callback. 110 */ 111 @Override onStartInternal()112 protected void onStartInternal() { 113 mAuthenticatorHelper.listenToAccountUpdates(); 114 mIsStarted = true; 115 116 if (mHasPendingBack) { 117 mHasPendingBack = false; 118 119 // Post the fragment navigation because FragmentManager may still be executing 120 // transactions during onStart. 121 new Handler().post(() -> getFragmentController().goBack()); 122 } 123 } 124 125 /** 126 * Unregisters the account update callback. 127 */ 128 @Override onStopInternal()129 protected void onStopInternal() { 130 mAuthenticatorHelper.stopListeningToAccountUpdates(); 131 mIsStarted = false; 132 } 133 134 @Override onAccountsUpdate(UserHandle userHandle)135 public void onAccountsUpdate(UserHandle userHandle) { 136 // Only force a refresh if accounts have changed for the current user. 137 if (userHandle.equals(mUserHandle)) { 138 forceUpdateAccountsCategory(); 139 } 140 } 141 142 /** Forces a refresh of the authenticator description preferences. */ forceUpdateAccountsCategory()143 private void forceUpdateAccountsCategory() { 144 Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet()); 145 List<AuthenticatorDescriptionPreference> preferences = 146 getAuthenticatorDescriptionPreferences(preferencesToRemove); 147 // Add all preferences that aren't already shown 148 for (int i = 0; i < preferences.size(); i++) { 149 AuthenticatorDescriptionPreference preference = preferences.get(i); 150 preference.setOrder(i); 151 String key = preference.getKey(); 152 getPreference().addPreference(preference); 153 mPreferences.put(key, preference); 154 } 155 156 // Remove all preferences that should no longer be shown 157 for (String key : preferencesToRemove) { 158 getPreference().removePreference(mPreferences.get(key)); 159 mPreferences.remove(key); 160 } 161 } 162 163 /** 164 * Returns a list of preferences corresponding to the account types the user can add. 165 * 166 * <p> Derived from 167 * {@link com.android.settings.accounts.ChooseAccountActivity#onAuthDescriptionsUpdated} 168 * 169 * @param preferencesToRemove the current preferences shown; will contain the preferences that 170 * need to be removed from the screen after method execution 171 */ getAuthenticatorDescriptionPreferences( Set<String> preferencesToRemove)172 private List<AuthenticatorDescriptionPreference> getAuthenticatorDescriptionPreferences( 173 Set<String> preferencesToRemove) { 174 AuthenticatorDescription[] authenticatorDescriptions = AccountManager.get( 175 getContext()).getAuthenticatorTypesAsUser( 176 mUserHandle.getIdentifier()); 177 178 ArrayList<AuthenticatorDescriptionPreference> authenticatorDescriptionPreferences = 179 new ArrayList<>(); 180 // Create list of account providers to show on page. 181 for (AuthenticatorDescription authenticatorDescription : authenticatorDescriptions) { 182 String accountType = authenticatorDescription.type; 183 CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType); 184 Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType); 185 186 List<String> accountAuthorities = 187 mAuthenticatorHelper.getAuthoritiesForAccountType(accountType); 188 189 // If there are specific authorities required, we need to check whether they are 190 // included in the account type. 191 boolean authorized = 192 (mAuthorities == null || mAuthorities.isEmpty() || accountAuthorities == null 193 || !Collections.disjoint(accountAuthorities, mAuthorities)); 194 195 // If there is an account type filter, make sure this account type is included. 196 authorized = authorized && (mAccountTypesFilter == null 197 || mAccountTypesFilter.contains(accountType)); 198 199 // Check if the account type is in the exclusion list. 200 authorized = authorized && (mAccountTypesExclusionFilter == null 201 || !mAccountTypesExclusionFilter.contains(accountType)); 202 203 // If authorized, add a preference for the provider to the list and remove it from 204 // preferencesToRemove. 205 if (authorized) { 206 AuthenticatorDescriptionPreference preference = mPreferences.getOrDefault( 207 accountType, 208 new AuthenticatorDescriptionPreference(getContext(), accountType, label, 209 icon)); 210 preference.setOnPreferenceClickListener( 211 pref -> onAddAccount(preference.getAccountType())); 212 authenticatorDescriptionPreferences.add(preference); 213 preferencesToRemove.remove(accountType); 214 } 215 } 216 217 Collections.sort(authenticatorDescriptionPreferences, Comparator.comparing( 218 (AuthenticatorDescriptionPreference a) -> a.getTitle().toString())); 219 220 return authenticatorDescriptionPreferences; 221 } 222 223 /** Starts the {@link AddAccountActivity} to add an account for the given account type. */ onAddAccount(String accountType)224 private boolean onAddAccount(String accountType) { 225 Intent intent = new Intent(getContext(), AddAccountActivity.class); 226 intent.putExtra(AddAccountActivity.EXTRA_SELECTED_ACCOUNT, accountType); 227 getFragmentController().startActivityForResult(intent, ADD_ACCOUNT_REQUEST_CODE, 228 this::onAccountAdded); 229 return true; 230 } 231 onAccountAdded(int requestCode, int resultCode, @Nullable Intent data)232 private void onAccountAdded(int requestCode, int resultCode, @Nullable Intent data) { 233 if (requestCode == ADD_ACCOUNT_REQUEST_CODE) { 234 if (mIsStarted) { 235 getFragmentController().goBack(); 236 } else { 237 mHasPendingBack = true; 238 } 239 } 240 } 241 242 /** Used for testing to trigger an account update. */ 243 @VisibleForTesting getAuthenticatorHelper()244 AuthenticatorHelper getAuthenticatorHelper() { 245 return mAuthenticatorHelper; 246 } 247 248 /** Handles adding accounts. */ 249 interface AddAccountListener { 250 /** Handles adding an account. */ addAccount(String accountType)251 void addAccount(String accountType); 252 } 253 254 private static class AuthenticatorDescriptionPreference extends CarUiPreference { 255 private final String mType; 256 AuthenticatorDescriptionPreference(Context context, String accountType, CharSequence label, Drawable icon)257 AuthenticatorDescriptionPreference(Context context, String accountType, CharSequence label, 258 Drawable icon) { 259 super(context); 260 mType = accountType; 261 262 setKey(accountType); 263 setTitle(label); 264 setIcon(icon); 265 setShowChevron(false); 266 } 267 getAccountType()268 private String getAccountType() { 269 return mType; 270 } 271 } 272 } 273