blob: 99955b200f42afd22e0eb6518d8f732afc31e3f1 [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;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070035import com.android.settingslib.accounts.AuthenticatorHelper;
36
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.Collections;
40import java.util.Comparator;
Roshan Agrawala33c6122018-12-11 17:56:16 -080041import java.util.HashSet;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070042import java.util.List;
Roshan Agrawala33c6122018-12-11 17:56:16 -080043import java.util.Set;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070044
45/**
46 * Controller for listing accounts.
47 *
48 * <p>Largely derived from {@link com.android.settings.accounts.AccountPreferenceController}
49 */
Roshan Agrawala33c6122018-12-11 17:56:16 -080050public class AccountListPreferenceController extends
51 PreferenceController<PreferenceCategory> implements
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070052 AuthenticatorHelper.OnAccountsUpdateListener,
Roshan Agrawala33c6122018-12-11 17:56:16 -080053 CarUserManagerHelper.OnUsersUpdateListener {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070054 private static final String NO_ACCOUNT_PREF_KEY = "no_accounts_added";
55
56 private final UserInfo mUserInfo;
57 private final CarUserManagerHelper mCarUserManagerHelper;
58 private final ArrayMap<String, Preference> mPreferences = new ArrayMap<>();
59 private AuthenticatorHelper mAuthenticatorHelper;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070060 private String[] mAuthorities;
Heemin Seog77ffa522019-08-26 15:40:48 -070061 private boolean mListenerRegistered = false;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070062
63 public AccountListPreferenceController(Context context, String preferenceKey,
Roshan Agrawala33c6122018-12-11 17:56:16 -080064 FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
65 super(context, preferenceKey, fragmentController, uxRestrictions);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070066 mCarUserManagerHelper = new CarUserManagerHelper(context);
67 mUserInfo = mCarUserManagerHelper.getCurrentProcessUserInfo();
68 mAuthenticatorHelper = new AuthenticatorHelper(context,
69 mUserInfo.getUserHandle(), /* listener= */ this);
70 }
71
72 /** Sets the account authorities that are available. */
73 public void setAuthorities(String[] authorities) {
74 mAuthorities = authorities;
75 }
76
77 @Override
Roshan Agrawala33c6122018-12-11 17:56:16 -080078 protected Class<PreferenceCategory> getPreferenceType() {
79 return PreferenceCategory.class;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070080 }
81
82 @Override
Roshan Agrawala33c6122018-12-11 17:56:16 -080083 protected void updateState(PreferenceCategory preference) {
84 forceUpdateAccountsCategory();
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070085 }
86
87 @Override
Roshan Agrawala33c6122018-12-11 17:56:16 -080088 protected int getAvailabilityStatus() {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070089 return mCarUserManagerHelper.canCurrentProcessModifyAccounts() ? AVAILABLE
90 : DISABLED_FOR_USER;
91 }
92
93 /**
94 * Registers the account update and user update callbacks.
95 */
Roshan Agrawala33c6122018-12-11 17:56:16 -080096 @Override
97 protected void onStartInternal() {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -070098 mAuthenticatorHelper.listenToAccountUpdates();
99 mCarUserManagerHelper.registerOnUsersUpdateListener(this);
Heemin Seog77ffa522019-08-26 15:40:48 -0700100 mListenerRegistered = true;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700101 }
102
103 /**
104 * Unregisters the account update and user update callbacks.
105 */
Roshan Agrawala33c6122018-12-11 17:56:16 -0800106 @Override
107 protected void onStopInternal() {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700108 mAuthenticatorHelper.stopListeningToAccountUpdates();
109 mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
Heemin Seog77ffa522019-08-26 15:40:48 -0700110 mListenerRegistered = false;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700111 }
112
113 @Override
114 public void onAccountsUpdate(UserHandle userHandle) {
115 if (userHandle.equals(mUserInfo.getUserHandle())) {
116 forceUpdateAccountsCategory();
117 }
118 }
119
120 @Override
121 public void onUsersUpdate() {
122 forceUpdateAccountsCategory();
123 }
124
Roshan Agrawala33c6122018-12-11 17:56:16 -0800125 private boolean onAccountPreferenceClicked(AccountPreference preference) {
126 // Show the account's details when an account is clicked on.
127 getFragmentController().launchFragment(AccountDetailsFragment.newInstance(
128 preference.getAccount(), preference.getLabel(), mUserInfo));
129 return true;
130 }
131
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700132 /** Forces a refresh of the account preferences. */
133 private void forceUpdateAccountsCategory() {
134 // Set the category title and include the user's name
Roshan Agrawala33c6122018-12-11 17:56:16 -0800135 getPreference().setTitle(
136 getContext().getString(R.string.account_list_title, mUserInfo.name));
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700137
138 // Recreate the authentication helper to refresh the list of enabled accounts
Heemin Seog77ffa522019-08-26 15:40:48 -0700139 mAuthenticatorHelper.stopListeningToAccountUpdates();
Roshan Agrawala33c6122018-12-11 17:56:16 -0800140 mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(),
141 this);
Heemin Seog77ffa522019-08-26 15:40:48 -0700142 if (mListenerRegistered) {
143 mAuthenticatorHelper.listenToAccountUpdates();
144 }
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700145
Roshan Agrawala33c6122018-12-11 17:56:16 -0800146 Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700147 List<? extends Preference> preferences = getAccountPreferences(preferencesToRemove);
148 // Add all preferences that aren't already shown. Manually set the order so that existing
149 // preferences are reordered correctly.
150 for (int i = 0; i < preferences.size(); i++) {
Roshan Agrawala33c6122018-12-11 17:56:16 -0800151 Preference pref = preferences.get(i);
152 pref.setOrder(i);
153 mPreferences.put(pref.getKey(), pref);
154 getPreference().addPreference(pref);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700155 }
156
Roshan Agrawala33c6122018-12-11 17:56:16 -0800157 for (String key : preferencesToRemove) {
158 getPreference().removePreference(mPreferences.get(key));
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700159 mPreferences.remove(key);
160 }
161 }
162
163 /**
164 * Returns a list of preferences corresponding to the accounts for the current user.
165 *
166 * <p> Derived from
167 * {@link com.android.settings.accounts.AccountPreferenceController#getAccountTypePreferences}
168 *
169 * @param preferencesToRemove the current preferences shown; only preferences to be removed will
170 * remain after method execution
171 */
172 private List<? extends Preference> getAccountPreferences(
Roshan Agrawala33c6122018-12-11 17:56:16 -0800173 Set<String> preferencesToRemove) {
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700174 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
175 ArrayList<AccountPreference> accountPreferences =
176 new ArrayList<>(accountTypes.length);
177
178 for (int i = 0; i < accountTypes.length; i++) {
179 String accountType = accountTypes[i];
180 // Skip showing any account that does not have any of the requested authorities
181 if (!accountTypeHasAnyRequestedAuthorities(accountType)) {
182 continue;
183 }
Roshan Agrawala33c6122018-12-11 17:56:16 -0800184 CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700185 if (label == null) {
186 continue;
187 }
188
Roshan Agrawala33c6122018-12-11 17:56:16 -0800189 Account[] accounts = AccountManager.get(getContext())
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700190 .getAccountsByTypeAsUser(accountType, mUserInfo.getUserHandle());
Roshan Agrawala33c6122018-12-11 17:56:16 -0800191 Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700192
193 // Add a preference row for each individual account
194 for (Account account : accounts) {
Roshan Agrawala33c6122018-12-11 17:56:16 -0800195 String key = AccountPreference.buildKey(account);
196 AccountPreference preference = (AccountPreference) mPreferences.getOrDefault(key,
197 new AccountPreference(getContext(), account, label, icon));
198 preference.setOnPreferenceClickListener(
199 (Preference pref) -> onAccountPreferenceClicked((AccountPreference) pref));
200
201 accountPreferences.add(preference);
202 preferencesToRemove.remove(key);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700203 }
Roshan Agrawala33c6122018-12-11 17:56:16 -0800204 mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType);
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700205 }
206
207 // If there are no accounts, return the "no account added" preference.
208 if (accountPreferences.isEmpty()) {
209 preferencesToRemove.remove(NO_ACCOUNT_PREF_KEY);
Roshan Agrawala33c6122018-12-11 17:56:16 -0800210 return Arrays.asList(mPreferences.getOrDefault(NO_ACCOUNT_PREF_KEY,
211 createNoAccountsAddedPreference()));
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700212 }
213
214 Collections.sort(accountPreferences, Comparator.comparing(
215 (AccountPreference a) -> a.getSummary().toString())
216 .thenComparing((AccountPreference a) -> a.getTitle().toString()));
217
218 return accountPreferences;
219 }
220
221 private Preference createNoAccountsAddedPreference() {
Roshan Agrawala33c6122018-12-11 17:56:16 -0800222 Preference emptyPreference = new Preference(getContext());
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700223 emptyPreference.setTitle(R.string.no_accounts_added);
224 emptyPreference.setKey(NO_ACCOUNT_PREF_KEY);
225 emptyPreference.setSelectable(false);
226
227 return emptyPreference;
228 }
229
230 /**
231 * Returns whether the account type has any of the authorities requested by the caller.
232 *
233 * <p> Derived from {@link AccountPreferenceController#accountTypeHasAnyRequestedAuthorities}
234 */
235 private boolean accountTypeHasAnyRequestedAuthorities(String accountType) {
236 if (mAuthorities == null || mAuthorities.length == 0) {
237 // No authorities required
238 return true;
239 }
240 ArrayList<String> authoritiesForType =
241 mAuthenticatorHelper.getAuthoritiesForAccountType(accountType);
242 if (authoritiesForType == null) {
243 return false;
244 }
245 for (int j = 0; j < mAuthorities.length; j++) {
246 if (authoritiesForType.contains(mAuthorities[j])) {
247 return true;
248 }
249 }
250 return false;
251 }
252
253 private static class AccountPreference extends Preference {
254 /** Account that this Preference represents. */
255 private final Account mAccount;
Roshan Agrawala2df3f12018-11-06 13:52:37 -0800256 private final CharSequence mLabel;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700257
258 private AccountPreference(Context context, Account account, CharSequence label,
259 Drawable icon) {
260 super(context);
261 mAccount = account;
Roshan Agrawala2df3f12018-11-06 13:52:37 -0800262 mLabel = label;
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700263
264 setKey(buildKey(account));
265 setTitle(account.name);
266 setSummary(label);
267 setIcon(icon);
268 }
269
270 /**
271 * Build a unique preference key based on the account.
272 */
273 public static String buildKey(Account account) {
274 return String.valueOf(account.hashCode());
275 }
276
277 public Account getAccount() {
278 return mAccount;
279 }
Roshan Agrawala2df3f12018-11-06 13:52:37 -0800280
281 public CharSequence getLabel() {
282 return mLabel;
283 }
Roshan Agrawal16bf44c2018-10-23 17:17:03 -0700284 }
285}