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