blob: 1a04b4bcd9151db5ee60047122e6462f8b10d818 [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;
21import android.car.userlib.CarUserManagerHelper;
22import android.content.Context;
23import android.content.pm.UserInfo;
24import android.graphics.drawable.Drawable;
25import android.os.UserHandle;
26
27import androidx.collection.ArrayMap;
28import androidx.lifecycle.Lifecycle;
29import androidx.lifecycle.LifecycleObserver;
30import androidx.lifecycle.OnLifecycleEvent;
31import androidx.preference.Preference;
32import androidx.preference.PreferenceCategory;
33import androidx.preference.PreferenceScreen;
34
35import com.android.car.settings.R;
36import com.android.car.settings.common.FragmentController;
37import com.android.car.settings.common.NoSetupPreferenceController;
38import com.android.settingslib.accounts.AuthenticatorHelper;
39
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.Collections;
43import java.util.Comparator;
44import java.util.List;
45import java.util.Map;
46
47/**
48 * Controller for listing accounts.
49 *
50 * <p>Largely derived from {@link com.android.settings.accounts.AccountPreferenceController}
51 */
52public class AccountListPreferenceController extends NoSetupPreferenceController implements
53 AuthenticatorHelper.OnAccountsUpdateListener,
54 CarUserManagerHelper.OnUsersUpdateListener, LifecycleObserver {
55 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;
61 private PreferenceCategory mAccountsCategory;
62 private String[] mAuthorities;
63
64 public AccountListPreferenceController(Context context, String preferenceKey,
65 FragmentController fragmentController) {
66 super(context, preferenceKey, fragmentController);
67 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
79 public void displayPreference(PreferenceScreen screen) {
80 super.displayPreference(screen);
81
82 // Add preferences for each account if the controller should be available
83 if (isAvailable()) {
84 mAccountsCategory = (PreferenceCategory) screen.findPreference(getPreferenceKey());
85 forceUpdateAccountsCategory();
86 }
87 }
88
89 @Override
90 public boolean handlePreferenceTreeClick(Preference preference) {
91 // Show the account's details when an account is clicked on.
92 if (preference instanceof AccountPreference) {
93 getFragmentController().launchFragment(AccountDetailsFragment.newInstance(
94 ((AccountPreference) preference).getAccount(), mUserInfo));
95 return true;
96 }
97 return false;
98 }
99
100 @Override
101 public int getAvailabilityStatus() {
102 return mCarUserManagerHelper.canCurrentProcessModifyAccounts() ? AVAILABLE
103 : DISABLED_FOR_USER;
104 }
105
106 /**
107 * Registers the account update and user update callbacks.
108 */
109 @OnLifecycleEvent(Lifecycle.Event.ON_START)
110 public void onStart() {
111 mAuthenticatorHelper.listenToAccountUpdates();
112 mCarUserManagerHelper.registerOnUsersUpdateListener(this);
113 }
114
115 /**
116 * Unregisters the account update and user update callbacks.
117 */
118 @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
119 public void onStop() {
120 mAuthenticatorHelper.stopListeningToAccountUpdates();
121 mCarUserManagerHelper.unregisterOnUsersUpdateListener(this);
122 }
123
124 @Override
125 public void onAccountsUpdate(UserHandle userHandle) {
126 if (userHandle.equals(mUserInfo.getUserHandle())) {
127 forceUpdateAccountsCategory();
128 }
129 }
130
131 @Override
132 public void onUsersUpdate() {
133 forceUpdateAccountsCategory();
134 }
135
136 /** Forces a refresh of the account preferences. */
137 private void forceUpdateAccountsCategory() {
138 // Set the category title and include the user's name
139 mAccountsCategory.setTitle(mContext.getString(R.string.account_list_title, mUserInfo.name));
140
141 // Recreate the authentication helper to refresh the list of enabled accounts
142 mAuthenticatorHelper = new AuthenticatorHelper(mContext, mUserInfo.getUserHandle(), this);
143
144 Map<String, Preference> preferencesToRemove = new ArrayMap<>(mPreferences);
145 List<? extends Preference> preferences = getAccountPreferences(preferencesToRemove);
146 // Add all preferences that aren't already shown. Manually set the order so that existing
147 // preferences are reordered correctly.
148 for (int i = 0; i < preferences.size(); i++) {
149 Preference preference = preferences.get(i);
150 String key = preference.getKey();
151 if (!mPreferences.containsKey(key)) {
152 preference.setOrder(i);
153 mAccountsCategory.addPreference(preference);
154 mPreferences.put(key, preference);
155 } else {
156 mPreferences.get(key).setOrder(i);
157 }
158 }
159
160 // Remove all preferences that should no longer be shown
161 for (String key : preferencesToRemove.keySet()) {
162 mAccountsCategory.removePreference(mPreferences.get(key));
163 mPreferences.remove(key);
164 }
165 }
166
167 /**
168 * Returns a list of preferences corresponding to the accounts for the current user.
169 *
170 * <p> Derived from
171 * {@link com.android.settings.accounts.AccountPreferenceController#getAccountTypePreferences}
172 *
173 * @param preferencesToRemove the current preferences shown; only preferences to be removed will
174 * remain after method execution
175 */
176 private List<? extends Preference> getAccountPreferences(
177 Map<String, Preference> preferencesToRemove) {
178 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
179 ArrayList<AccountPreference> accountPreferences =
180 new ArrayList<>(accountTypes.length);
181
182 for (int i = 0; i < accountTypes.length; i++) {
183 String accountType = accountTypes[i];
184 // Skip showing any account that does not have any of the requested authorities
185 if (!accountTypeHasAnyRequestedAuthorities(accountType)) {
186 continue;
187 }
188 CharSequence label = mAuthenticatorHelper.getLabelForType(mContext, accountType);
189 if (label == null) {
190 continue;
191 }
192
193 Account[] accounts = AccountManager.get(mContext)
194 .getAccountsByTypeAsUser(accountType, mUserInfo.getUserHandle());
195 Drawable icon = mAuthenticatorHelper.getDrawableForType(mContext, accountType);
196
197 // Add a preference row for each individual account
198 for (Account account : accounts) {
199 AccountPreference preference =
200 (AccountPreference) preferencesToRemove.remove(
201 AccountPreference.buildKey(account));
202 if (preference != null) {
203 accountPreferences.add(preference);
204 continue;
205 }
206 accountPreferences.add(new AccountPreference(
207 mContext, account, label, icon));
208 }
209 mAuthenticatorHelper.preloadDrawableForType(mContext, accountType);
210 }
211
212 // If there are no accounts, return the "no account added" preference.
213 if (accountPreferences.isEmpty()) {
214 preferencesToRemove.remove(NO_ACCOUNT_PREF_KEY);
215 return Arrays.asList(createNoAccountsAddedPreference());
216 }
217
218 Collections.sort(accountPreferences, Comparator.comparing(
219 (AccountPreference a) -> a.getSummary().toString())
220 .thenComparing((AccountPreference a) -> a.getTitle().toString()));
221
222 return accountPreferences;
223 }
224
225 private Preference createNoAccountsAddedPreference() {
226 Preference emptyPreference = new Preference(mContext);
227 emptyPreference.setTitle(R.string.no_accounts_added);
228 emptyPreference.setKey(NO_ACCOUNT_PREF_KEY);
229 emptyPreference.setSelectable(false);
230
231 return emptyPreference;
232 }
233
234 /**
235 * Returns whether the account type has any of the authorities requested by the caller.
236 *
237 * <p> Derived from {@link AccountPreferenceController#accountTypeHasAnyRequestedAuthorities}
238 */
239 private boolean accountTypeHasAnyRequestedAuthorities(String accountType) {
240 if (mAuthorities == null || mAuthorities.length == 0) {
241 // No authorities required
242 return true;
243 }
244 ArrayList<String> authoritiesForType =
245 mAuthenticatorHelper.getAuthoritiesForAccountType(accountType);
246 if (authoritiesForType == null) {
247 return false;
248 }
249 for (int j = 0; j < mAuthorities.length; j++) {
250 if (authoritiesForType.contains(mAuthorities[j])) {
251 return true;
252 }
253 }
254 return false;
255 }
256
257 private static class AccountPreference extends Preference {
258 /** Account that this Preference represents. */
259 private final Account mAccount;
260
261 private AccountPreference(Context context, Account account, CharSequence label,
262 Drawable icon) {
263 super(context);
264 mAccount = account;
265
266 setKey(buildKey(account));
267 setTitle(account.name);
268 setSummary(label);
269 setIcon(icon);
270 }
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 }
282 }
283}