blob: 2007acab41a5f29dd04f1d7b0fcb9ad4a08d4bb3 [file] [log] [blame]
Roshan Agrawal16bf44c2018-10-23 17:17:03 -07001/*
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
17package com.android.car.settings.accounts;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
Roshan Agrawala33c6122018-12-11 17:56:16 -080021import android.car.drivingstate.CarUxRestrictions;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070022import android.car.userlib.CarUserManagerHelper;
23import android.content.Context;
24import android.content.pm.UserInfo;
25import android.graphics.drawable.Drawable;
26import android.os.UserHandle;
27
28import androidx.collection.ArrayMap;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070029import androidx.preference.Preference;
30import androidx.preference.PreferenceCategory;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070031
32import com.android.car.settings.R;
33import com.android.car.settings.common.FragmentController;
Roshan Agrawala33c6122018-12-11 17:56:16 -080034import com.android.car.settings.common.PreferenceController;
Peter Li97efb4a2019-10-15 16:24:34 -070035import com.android.car.ui.preference.CarUiPreference;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070036import com.android.settingslib.accounts.AuthenticatorHelper;
37
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.Collections;
41import java.util.Comparator;
Roshan Agrawala33c6122018-12-11 17:56:16 -080042import java.util.HashSet;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070043import java.util.List;
Roshan Agrawala33c6122018-12-11 17:56:16 -080044import java.util.Set;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070045
46/**
47 * Controller for listing accounts.
48 *
49 * <p>Largely derived from {@link com.android.settings.accounts.AccountPreferenceController}
50 */
Roshan Agrawala33c6122018-12-11 17:56:16 -080051public class AccountListPreferenceController extends
52 PreferenceController<PreferenceCategory> implements
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070053 AuthenticatorHelper.OnAccountsUpdateListener,
Roshan Agrawala33c6122018-12-11 17:56:16 -080054 CarUserManagerHelper.OnUsersUpdateListener {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070055 private static final String NO_ACCOUNT_PREF_KEY = "no_accounts_added";
56
57 private final UserInfo mUserInfo;
58 private final CarUserManagerHelper mCarUserManagerHelper;
59 private final ArrayMap<String, Preference> mPreferences = new ArrayMap<>();
60 private AuthenticatorHelper mAuthenticatorHelper;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070061 private String[] mAuthorities;
Heemin Seog77ffa522019-08-26 15:40:48 -070062 private boolean mListenerRegistered = false;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070063
64 public AccountListPreferenceController(Context context, String preferenceKey,
Roshan Agrawala33c6122018-12-11 17:56:16 -080065 FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
66 super(context, preferenceKey, fragmentController, uxRestrictions);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070067 mCarUserManagerHelper = new CarUserManagerHelper(context);
68 mUserInfo = mCarUserManagerHelper.getCurrentProcessUserInfo();
69 mAuthenticatorHelper = new AuthenticatorHelper(context,
70 mUserInfo.getUserHandle(), /* listener= */ this);
71 }
72
73 /** Sets the account authorities that are available. */
74 public void setAuthorities(String[] authorities) {
75 mAuthorities = authorities;
76 }
77
78 @Override
Roshan Agrawala33c6122018-12-11 17:56:16 -080079 protected Class<PreferenceCategory> getPreferenceType() {
80 return PreferenceCategory.class;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070081 }
82
83 @Override
Roshan Agrawala33c6122018-12-11 17:56:16 -080084 protected void updateState(PreferenceCategory preference) {
85 forceUpdateAccountsCategory();
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070086 }
87
88 @Override
Roshan Agrawala33c6122018-12-11 17:56:16 -080089 protected int getAvailabilityStatus() {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070090 return mCarUserManagerHelper.canCurrentProcessModifyAccounts() ? AVAILABLE
91 : DISABLED_FOR_USER;
92 }
93
94 /**
95 * Registers the account update and user update callbacks.
96 */
Roshan Agrawala33c6122018-12-11 17:56:16 -080097 @Override
98 protected void onStartInternal() {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070099 mAuthenticatorHelper.listenToAccountUpdates();
100 mCarUserManagerHelper.registerOnUsersUpdateListener(this);
Heemin Seog77ffa522019-08-26 15:40:48 -0700101 mListenerRegistered = true;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700102 }
103
104 /**
105 * Unregisters the account update and user update callbacks.
106 */
Roshan Agrawala33c6122018-12-11 17:56:16 -0800107 @Override
108 protected void onStopInternal() {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700109 mAuthenticatorHelper.stopListeningToAccountUpdates();
110 mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
Heemin Seog77ffa522019-08-26 15:40:48 -0700111 mListenerRegistered = false;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700112 }
113
114 @Override
115 public void onAccountsUpdate(UserHandle userHandle) {
116 if (userHandle.equals(mUserInfo.getUserHandle())) {
117 forceUpdateAccountsCategory();
118 }
119 }
120
121 @Override
122 public void onUsersUpdate() {
123 forceUpdateAccountsCategory();
124 }
125
Roshan Agrawala33c6122018-12-11 17:56:16 -0800126 private boolean onAccountPreferenceClicked(AccountPreference preference) {
127 // Show the account's details when an account is clicked on.
128 getFragmentController().launchFragment(AccountDetailsFragment.newInstance(
129 preference.getAccount(), preference.getLabel(), mUserInfo));
130 return true;
131 }
132
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700133 /** Forces a refresh of the account preferences. */
134 private void forceUpdateAccountsCategory() {
135 // Set the category title and include the user's name
Roshan Agrawala33c6122018-12-11 17:56:16 -0800136 getPreference().setTitle(
137 getContext().getString(R.string.account_list_title, mUserInfo.name));
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700138
139 // Recreate the authentication helper to refresh the list of enabled accounts
Heemin Seog77ffa522019-08-26 15:40:48 -0700140 mAuthenticatorHelper.stopListeningToAccountUpdates();
Roshan Agrawala33c6122018-12-11 17:56:16 -0800141 mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(),
142 this);
Heemin Seog77ffa522019-08-26 15:40:48 -0700143 if (mListenerRegistered) {
144 mAuthenticatorHelper.listenToAccountUpdates();
145 }
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700146
Roshan Agrawala33c6122018-12-11 17:56:16 -0800147 Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700148 List<? extends Preference> preferences = getAccountPreferences(preferencesToRemove);
149 // Add all preferences that aren't already shown. Manually set the order so that existing
150 // preferences are reordered correctly.
151 for (int i = 0; i < preferences.size(); i++) {
Roshan Agrawala33c6122018-12-11 17:56:16 -0800152 Preference pref = preferences.get(i);
153 pref.setOrder(i);
154 mPreferences.put(pref.getKey(), pref);
155 getPreference().addPreference(pref);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700156 }
157
Roshan Agrawala33c6122018-12-11 17:56:16 -0800158 for (String key : preferencesToRemove) {
159 getPreference().removePreference(mPreferences.get(key));
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700160 mPreferences.remove(key);
161 }
162 }
163
164 /**
165 * Returns a list of preferences corresponding to the accounts for the current user.
166 *
167 * <p> Derived from
168 * {@link com.android.settings.accounts.AccountPreferenceController#getAccountTypePreferences}
169 *
170 * @param preferencesToRemove the current preferences shown; only preferences to be removed will
171 * remain after method execution
172 */
173 private List<? extends Preference> getAccountPreferences(
Roshan Agrawala33c6122018-12-11 17:56:16 -0800174 Set<String> preferencesToRemove) {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700175 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
176 ArrayList<AccountPreference> accountPreferences =
177 new ArrayList<>(accountTypes.length);
178
179 for (int i = 0; i < accountTypes.length; i++) {
180 String accountType = accountTypes[i];
181 // Skip showing any account that does not have any of the requested authorities
182 if (!accountTypeHasAnyRequestedAuthorities(accountType)) {
183 continue;
184 }
Roshan Agrawala33c6122018-12-11 17:56:16 -0800185 CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700186 if (label == null) {
187 continue;
188 }
189
Roshan Agrawala33c6122018-12-11 17:56:16 -0800190 Account[] accounts = AccountManager.get(getContext())
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700191 .getAccountsByTypeAsUser(accountType, mUserInfo.getUserHandle());
Roshan Agrawala33c6122018-12-11 17:56:16 -0800192 Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700193
194 // Add a preference row for each individual account
195 for (Account account : accounts) {
Roshan Agrawala33c6122018-12-11 17:56:16 -0800196 String key = AccountPreference.buildKey(account);
197 AccountPreference preference = (AccountPreference) mPreferences.getOrDefault(key,
198 new AccountPreference(getContext(), account, label, icon));
199 preference.setOnPreferenceClickListener(
200 (Preference pref) -> onAccountPreferenceClicked((AccountPreference) pref));
201
202 accountPreferences.add(preference);
203 preferencesToRemove.remove(key);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700204 }
Roshan Agrawala33c6122018-12-11 17:56:16 -0800205 mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700206 }
207
208 // If there are no accounts, return the "no account added" preference.
209 if (accountPreferences.isEmpty()) {
210 preferencesToRemove.remove(NO_ACCOUNT_PREF_KEY);
Roshan Agrawala33c6122018-12-11 17:56:16 -0800211 return Arrays.asList(mPreferences.getOrDefault(NO_ACCOUNT_PREF_KEY,
212 createNoAccountsAddedPreference()));
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700213 }
214
215 Collections.sort(accountPreferences, Comparator.comparing(
216 (AccountPreference a) -> a.getSummary().toString())
217 .thenComparing((AccountPreference a) -> a.getTitle().toString()));
218
219 return accountPreferences;
220 }
221
222 private Preference createNoAccountsAddedPreference() {
Peter Li97efb4a2019-10-15 16:24:34 -0700223 CarUiPreference emptyPreference = new CarUiPreference(getContext());
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700224 emptyPreference.setTitle(R.string.no_accounts_added);
225 emptyPreference.setKey(NO_ACCOUNT_PREF_KEY);
226 emptyPreference.setSelectable(false);
227
228 return emptyPreference;
229 }
230
231 /**
232 * Returns whether the account type has any of the authorities requested by the caller.
233 *
234 * <p> Derived from {@link AccountPreferenceController#accountTypeHasAnyRequestedAuthorities}
235 */
236 private boolean accountTypeHasAnyRequestedAuthorities(String accountType) {
237 if (mAuthorities == null || mAuthorities.length == 0) {
238 // No authorities required
239 return true;
240 }
241 ArrayList<String> authoritiesForType =
242 mAuthenticatorHelper.getAuthoritiesForAccountType(accountType);
243 if (authoritiesForType == null) {
244 return false;
245 }
246 for (int j = 0; j < mAuthorities.length; j++) {
247 if (authoritiesForType.contains(mAuthorities[j])) {
248 return true;
249 }
250 }
251 return false;
252 }
253
Peter Li6d4565e2019-12-10 16:28:36 -0800254 private static class AccountPreference extends CarUiPreference {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700255 /** Account that this Preference represents. */
256 private final Account mAccount;
Roshan Agrawala2df3f12018-11-06 13:52:37 -0800257 private final CharSequence mLabel;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700258
259 private AccountPreference(Context context, Account account, CharSequence label,
260 Drawable icon) {
261 super(context);
262 mAccount = account;
Roshan Agrawala2df3f12018-11-06 13:52:37 -0800263 mLabel = label;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700264
265 setKey(buildKey(account));
266 setTitle(account.name);
267 setSummary(label);
268 setIcon(icon);
Peter Li6d4565e2019-12-10 16:28:36 -0800269 setShowChevron(false);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700270 }
271
272 /**
273 * Build a unique preference key based on the account.
274 */
275 public static String buildKey(Account account) {
276 return String.valueOf(account.hashCode());
277 }
278
279 public Account getAccount() {
280 return mAccount;
281 }
Roshan Agrawala2df3f12018-11-06 13:52:37 -0800282
283 public CharSequence getLabel() {
284 return mLabel;
285 }
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700286 }
287}