• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
20 
21 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm;
22 
23 import android.accounts.Account;
24 import android.accounts.AccountManager;
25 import android.car.drivingstate.CarUxRestrictions;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.UserInfo;
31 import android.graphics.drawable.Drawable;
32 import android.os.UserHandle;
33 
34 import androidx.collection.ArrayMap;
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceCategory;
37 
38 import com.android.car.settings.R;
39 import com.android.car.settings.common.FragmentController;
40 import com.android.car.settings.common.PreferenceController;
41 import com.android.car.settings.profiles.ProfileHelper;
42 import com.android.car.settings.profiles.ProfileUtils;
43 import com.android.car.ui.preference.CarUiPreference;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.settingslib.accounts.AuthenticatorHelper;
46 
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Set;
54 
55 /**
56  * Controller for listing accounts.
57  *
58  * <p>Largely derived from {@link com.android.settings.accounts.AccountPreferenceController}
59  */
60 public class AccountListPreferenceController extends
61         PreferenceController<PreferenceCategory> implements
62         AuthenticatorHelper.OnAccountsUpdateListener {
63     private static final String NO_ACCOUNT_PREF_KEY = "no_accounts_added";
64 
65     private final ArrayMap<String, Preference> mPreferences = new ArrayMap<>();
66 
67     private UserInfo mUserInfo;
68     private AuthenticatorHelper mAuthenticatorHelper;
69     private String[] mAuthorities;
70     private boolean mListenerRegistered = false;
71     private boolean mNeedToRefreshUserInfo = false;
72 
73     private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() {
74         @Override
75         public void onReceive(Context context, Intent intent) {
76             onUsersUpdate();
77         }
78     };
79 
AccountListPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)80     public AccountListPreferenceController(Context context, String preferenceKey,
81             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
82         super(context, preferenceKey, fragmentController, uxRestrictions);
83         mUserInfo = ProfileHelper.getInstance(context).getCurrentProcessUserInfo();
84         mAuthenticatorHelper = createAuthenticatorHelper();
85     }
86 
87     /** Sets the account authorities that are available. */
setAuthorities(String[] authorities)88     public void setAuthorities(String[] authorities) {
89         mAuthorities = authorities;
90     }
91 
92     @Override
getPreferenceType()93     protected Class<PreferenceCategory> getPreferenceType() {
94         return PreferenceCategory.class;
95     }
96 
97     @Override
updateState(PreferenceCategory preference)98     protected void updateState(PreferenceCategory preference) {
99         forceUpdateAccountsCategory();
100     }
101 
102     @Override
onCreateInternal()103     protected void onCreateInternal() {
104         super.onCreateInternal();
105         setClickableWhileDisabled(getPreference(), /* clickable= */ true, p -> getProfileHelper()
106                 .runClickableWhileDisabled(getContext(), getFragmentController()));
107     }
108 
109     @Override
getDefaultAvailabilityStatus()110     protected int getDefaultAvailabilityStatus() {
111         ProfileHelper profileHelper = getProfileHelper();
112         boolean canModifyAccounts = profileHelper.canCurrentProcessModifyAccounts();
113 
114         if (canModifyAccounts) {
115             return AVAILABLE;
116         }
117 
118         if (profileHelper.isDemoOrGuest()
119                 || hasUserRestrictionByUm(getContext(), DISALLOW_MODIFY_ACCOUNTS)) {
120             return DISABLED_FOR_PROFILE;
121         }
122 
123         return AVAILABLE_FOR_VIEWING;
124     }
125 
126     /**
127      * Registers the account update and user update callbacks.
128      */
129     @Override
onStartInternal()130     protected void onStartInternal() {
131         mAuthenticatorHelper.listenToAccountUpdates();
132         registerForUserEvents();
133         mListenerRegistered = true;
134 
135         /* refresh UserInfo only when restarting */
136         if (mNeedToRefreshUserInfo) {
137             onUsersUpdate();
138         }
139     }
140 
141     /**
142      * Unregisters the account update and user update callbacks.
143      */
144     @Override
onStopInternal()145     protected void onStopInternal() {
146         mAuthenticatorHelper.stopListeningToAccountUpdates();
147         unregisterForUserEvents();
148         mListenerRegistered = false;
149         mNeedToRefreshUserInfo = true;
150     }
151 
152     @Override
onAccountsUpdate(UserHandle userHandle)153     public void onAccountsUpdate(UserHandle userHandle) {
154         if (userHandle.equals(mUserInfo.getUserHandle())) {
155             forceUpdateAccountsCategory();
156         }
157     }
158 
159     @VisibleForTesting
onUsersUpdate()160     void onUsersUpdate() {
161         mUserInfo = ProfileUtils.getUserInfo(getContext(), mUserInfo.id);
162         forceUpdateAccountsCategory();
163     }
164 
165     @VisibleForTesting
createAuthenticatorHelper()166     AuthenticatorHelper createAuthenticatorHelper() {
167         return new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(), this);
168     }
169 
onAccountPreferenceClicked(AccountPreference preference)170     private boolean onAccountPreferenceClicked(AccountPreference preference) {
171         // Show the account's details when an account is clicked on.
172         getFragmentController().launchFragment(AccountDetailsFragment.newInstance(
173                 preference.getAccount(), preference.getLabel(), mUserInfo));
174         return true;
175     }
176 
177     /** Forces a refresh of the account preferences. */
forceUpdateAccountsCategory()178     private void forceUpdateAccountsCategory() {
179         // Set the category title and include the user's name
180         getPreference().setTitle(
181                 getContext().getString(R.string.account_list_title, mUserInfo.name));
182 
183         // Recreate the authentication helper to refresh the list of enabled accounts
184         mAuthenticatorHelper.stopListeningToAccountUpdates();
185         mAuthenticatorHelper = createAuthenticatorHelper();
186         if (mListenerRegistered) {
187             mAuthenticatorHelper.listenToAccountUpdates();
188         }
189 
190         Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
191         List<? extends Preference> preferences = getAccountPreferences(preferencesToRemove);
192         // Add all preferences that aren't already shown. Manually set the order so that existing
193         // preferences are reordered correctly.
194         for (int i = 0; i < preferences.size(); i++) {
195             Preference pref = preferences.get(i);
196             pref.setOrder(i);
197             mPreferences.put(pref.getKey(), pref);
198             getPreference().addPreference(pref);
199         }
200 
201         for (String key : preferencesToRemove) {
202             getPreference().removePreference(mPreferences.get(key));
203             mPreferences.remove(key);
204         }
205     }
206 
207     /**
208      * Returns a list of preferences corresponding to the accounts for the current user.
209      *
210      * <p> Derived from
211      * {@link com.android.settings.accounts.AccountPreferenceController#getAccountTypePreferences}
212      *
213      * @param preferencesToRemove the current preferences shown; only preferences to be removed will
214      *                            remain after method execution
215      */
getAccountPreferences( Set<String> preferencesToRemove)216     private List<? extends Preference> getAccountPreferences(
217             Set<String> preferencesToRemove) {
218         String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
219         ArrayList<AccountPreference> accountPreferences =
220                 new ArrayList<>(accountTypes.length);
221 
222         for (int i = 0; i < accountTypes.length; i++) {
223             String accountType = accountTypes[i];
224             // Skip showing any account that does not have any of the requested authorities
225             if (!accountTypeHasAnyRequestedAuthorities(accountType)) {
226                 continue;
227             }
228             CharSequence label = mAuthenticatorHelper.getLabelForType(getContext(), accountType);
229             if (label == null) {
230                 continue;
231             }
232 
233             Account[] accounts = AccountManager.get(getContext())
234                     .getAccountsByTypeAsUser(accountType, mUserInfo.getUserHandle());
235             Drawable icon = mAuthenticatorHelper.getDrawableForType(getContext(), accountType);
236 
237             // Add a preference row for each individual account
238             for (Account account : accounts) {
239                 String key = AccountPreference.buildKey(account);
240                 AccountPreference preference = (AccountPreference) mPreferences.getOrDefault(key,
241                         new AccountPreference(getContext(), account, label, icon));
242                 preference.setOnPreferenceClickListener(
243                         (Preference pref) -> onAccountPreferenceClicked((AccountPreference) pref));
244 
245                 accountPreferences.add(preference);
246                 preferencesToRemove.remove(key);
247             }
248             mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType);
249         }
250 
251         // If there are no accounts, return the "no account added" preference.
252         if (accountPreferences.isEmpty()) {
253             preferencesToRemove.remove(NO_ACCOUNT_PREF_KEY);
254             return Arrays.asList(mPreferences.getOrDefault(NO_ACCOUNT_PREF_KEY,
255                     createNoAccountsAddedPreference()));
256         }
257 
258         Collections.sort(accountPreferences, Comparator.comparing(
259                 (AccountPreference a) -> a.getSummary().toString())
260                 .thenComparing((AccountPreference a) -> a.getTitle().toString()));
261 
262         return accountPreferences;
263     }
264 
createNoAccountsAddedPreference()265     private Preference createNoAccountsAddedPreference() {
266         CarUiPreference emptyPreference = new CarUiPreference(getContext());
267         emptyPreference.setTitle(R.string.no_accounts_added);
268         emptyPreference.setKey(NO_ACCOUNT_PREF_KEY);
269         emptyPreference.setSelectable(false);
270 
271         return emptyPreference;
272     }
273 
registerForUserEvents()274     private void registerForUserEvents() {
275         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_INFO_CHANGED);
276         getContext().registerReceiver(mUserUpdateReceiver, filter);
277     }
278 
unregisterForUserEvents()279     private void unregisterForUserEvents() {
280         getContext().unregisterReceiver(mUserUpdateReceiver);
281     }
282 
283 
284     /**
285      * Returns whether the account type has any of the authorities requested by the caller.
286      *
287      * <p> Derived from {@link AccountPreferenceController#accountTypeHasAnyRequestedAuthorities}
288      */
accountTypeHasAnyRequestedAuthorities(String accountType)289     private boolean accountTypeHasAnyRequestedAuthorities(String accountType) {
290         if (mAuthorities == null || mAuthorities.length == 0) {
291             // No authorities required
292             return true;
293         }
294         ArrayList<String> authoritiesForType =
295                 mAuthenticatorHelper.getAuthoritiesForAccountType(accountType);
296         if (authoritiesForType == null) {
297             return false;
298         }
299         for (int j = 0; j < mAuthorities.length; j++) {
300             if (authoritiesForType.contains(mAuthorities[j])) {
301                 return true;
302             }
303         }
304         return false;
305     }
306 
307     @VisibleForTesting
getProfileHelper()308     ProfileHelper getProfileHelper() {
309         return ProfileHelper.getInstance(getContext());
310     }
311 
312     private static class AccountPreference extends CarUiPreference {
313         /** Account that this Preference represents. */
314         private final Account mAccount;
315         private final CharSequence mLabel;
316 
AccountPreference(Context context, Account account, CharSequence label, Drawable icon)317         private AccountPreference(Context context, Account account, CharSequence label,
318                 Drawable icon) {
319             super(context);
320             mAccount = account;
321             mLabel = label;
322 
323             setKey(buildKey(account));
324             setTitle(account.name);
325             setSummary(label);
326             setIcon(icon);
327             setShowChevron(false);
328         }
329 
330         /**
331          * Build a unique preference key based on the account.
332          */
buildKey(Account account)333         public static String buildKey(Account account) {
334             return String.valueOf(account.hashCode());
335         }
336 
getAccount()337         public Account getAccount() {
338             return mAccount;
339         }
340 
getLabel()341         public CharSequence getLabel() {
342             return mLabel;
343         }
344     }
345 }
346