2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.accounts;
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.UserInfo;
28 import android.content.res.Resources;
29 import android.graphics.drawable.Drawable;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.support.v7.preference.Preference;
34 import android.support.v7.preference.Preference.OnPreferenceClickListener;
35 import android.support.v7.preference.PreferenceGroup;
36 import android.support.v7.preference.PreferenceScreen;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 import android.util.SparseArray;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.settings.AccessiblePreferenceCategory;
43 import com.android.settings.DimmableIconPreference;
44 import com.android.settings.R;
45 import com.android.settings.SettingsActivity;
46 import com.android.settings.SettingsPreferenceFragment;
47 import com.android.settings.Utils;
48 import com.android.settings.core.PreferenceController;
49 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
50 import com.android.settings.overlay.FeatureFactory;
51 import com.android.settings.search.SearchIndexableRaw;
52 import com.android.settings.search2.SearchFeatureProviderImpl;
53 import com.android.settingslib.RestrictedPreference;
54 import com.android.settingslib.accounts.AuthenticatorHelper;
55 import com.android.settingslib.core.lifecycle.LifecycleObserver;
56 import com.android.settingslib.core.lifecycle.events.OnPause;
57 import com.android.settingslib.core.lifecycle.events.OnResume;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.Comparator;
62 import java.util.List;
64 import static android.content.Intent.EXTRA_USER;
65 import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
66 import static android.os.UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
67 import static android.provider.Settings.EXTRA_AUTHORITIES;
69 public class AccountPreferenceController extends PreferenceController
70 implements AuthenticatorHelper.OnAccountsUpdateListener,
71 OnPreferenceClickListener, LifecycleObserver, OnPause, OnResume {
73 private static final String TAG = "AccountPrefController";
74 private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS";
76 private static final int ORDER_ACCOUNT_PROFILES = 1;
77 private static final int ORDER_LAST = 1002;
78 private static final int ORDER_NEXT_TO_LAST = 1001;
79 private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
81 private UserManager mUm;
82 private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
83 private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver
84 = new ManagedProfileBroadcastReceiver();
85 private Preference mProfileNotAvailablePreference;
86 private String[] mAuthorities;
87 private int mAuthoritiesCount = 0;
88 private SettingsPreferenceFragment mParent;
89 private int mAccountProfileOrder = ORDER_ACCOUNT_PROFILES;
90 private AccountRestrictionHelper mHelper;
91 private MetricsFeatureProvider mMetricsFeatureProvider;
94 * Holds data related to the accounts belonging to one profile.
96 public static class ProfileData {
98 * The preference that displays the accounts.
100 public PreferenceGroup preferenceGroup;
102 * The preference that displays the add account button.
104 public DimmableIconPreference addAccountPreference;
106 * The preference that displays the button to remove the managed profile
108 public RestrictedPreference removeWorkProfilePreference;
110 * The preference that displays managed profile settings.
112 public Preference managedProfilePreference;
114 * The {@link AuthenticatorHelper} that holds accounts data for this profile.
116 public AuthenticatorHelper authenticatorHelper;
118 * The {@link UserInfo} of the profile.
120 public UserInfo userInfo;
122 * The {@link UserInfo} of the profile.
124 public boolean pendingRemoval;
126 * The map from account key to account preference
128 public ArrayMap<String, AccountTypePreference> accountPreferences = new ArrayMap<>();
131 public AccountPreferenceController(Context context, SettingsPreferenceFragment parent,
132 String[] authorities) {
133 this(context, parent, authorities, new AccountRestrictionHelper(context));
137 AccountPreferenceController(Context context, SettingsPreferenceFragment parent,
138 String[] authorities, AccountRestrictionHelper helper) {
140 mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
141 mAuthorities = authorities;
143 if (mAuthorities != null) {
144 mAuthoritiesCount = mAuthorities.length;
146 final FeatureFactory featureFactory = FeatureFactory.getFactory(mContext);
147 mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
152 public boolean isAvailable() {
153 return !mUm.isManagedProfile();
157 public String getPreferenceKey() {
162 public void displayPreference(PreferenceScreen screen) {
163 super.displayPreference(screen);
168 public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
169 if (!isAvailable()) {
172 final Resources res = mContext.getResources();
173 final String screenTitle = res.getString(R.string.account_settings_title);
175 List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
176 final int profilesCount = profiles.size();
177 for (int i = 0; i < profilesCount; i++) {
178 UserInfo userInfo = profiles.get(i);
179 if (userInfo.isEnabled()) {
180 if (!mHelper.hasBaseUserRestriction(DISALLOW_MODIFY_ACCOUNTS, userInfo.id)) {
181 SearchIndexableRaw data = new SearchIndexableRaw(mContext);
182 data.title = res.getString(R.string.add_account_label);
183 data.screenTitle = screenTitle;
186 if (userInfo.isManagedProfile()) {
187 if (!mHelper.hasBaseUserRestriction(DISALLOW_REMOVE_MANAGED_PROFILE,
188 UserHandle.myUserId())) {
189 SearchIndexableRaw data = new SearchIndexableRaw(mContext);
190 data.title = res.getString(R.string.remove_managed_profile_label);
191 data.screenTitle = screenTitle;
195 SearchIndexableRaw data = new SearchIndexableRaw(mContext);
196 data.title = res.getString(R.string.managed_profile_settings_title);
197 data.screenTitle = screenTitle;
206 public void onResume() {
208 mManagedProfileBroadcastReceiver.register(mContext);
209 listenToAccountUpdates();
213 public void onPause() {
214 stopListeningToAccountUpdates();
215 mManagedProfileBroadcastReceiver.unregister(mContext);
219 public void onAccountsUpdate(UserHandle userHandle) {
220 final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
221 if (profileData != null) {
222 updateAccountTypes(profileData);
224 Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
229 public boolean onPreferenceClick(Preference preference) {
230 // Check the preference
231 final int count = mProfiles.size();
232 for (int i = 0; i < count; i++) {
233 ProfileData profileData = mProfiles.valueAt(i);
234 if (preference == profileData.addAccountPreference) {
235 Intent intent = new Intent(ADD_ACCOUNT_ACTION);
236 intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
237 intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
238 mContext.startActivity(intent);
241 if (preference == profileData.removeWorkProfilePreference) {
242 final int userId = profileData.userInfo.id;
243 RemoveUserFragment.newInstance(userId).show(mParent.getFragmentManager(),
247 if (preference == profileData.managedProfilePreference) {
248 Bundle arguments = new Bundle();
249 arguments.putParcelable(Intent.EXTRA_USER, profileData.userInfo.getUserHandle());
250 ((SettingsActivity) mParent.getActivity()).startPreferencePanel(mParent,
251 ManagedProfileSettings.class.getName(), arguments,
252 R.string.managed_profile_settings_title, null, null, 0);
259 SparseArray<ProfileData> getProfileData() {
263 private void updateUi() {
264 if (!isAvailable()) {
265 // This should not happen
266 Log.e(TAG, "We should not be showing settings for a managed profile");
270 for (int i = 0, size = mProfiles.size(); i < size; i++) {
271 mProfiles.valueAt(i).pendingRemoval = true;
273 if (mUm.isLinkedUser()) {
274 // Restricted user or similar
275 UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
276 updateProfileUi(userInfo);
278 List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
279 final int profilesCount = profiles.size();
280 for (int i = 0; i < profilesCount; i++) {
281 updateProfileUi(profiles.get(i));
284 cleanUpPreferences();
286 // Add all preferences, starting with one for the primary profile.
287 // Note that we're relying on the ordering given by the SparseArray keys, and on the
288 // value of UserHandle.USER_OWNER being smaller than all the rest.
289 final int profilesCount = mProfiles.size();
290 for (int i = 0; i < profilesCount; i++) {
291 updateAccountTypes(mProfiles.valueAt(i));
295 private void updateProfileUi(final UserInfo userInfo) {
296 if (mParent.getPreferenceManager() == null) {
299 final ProfileData data = mProfiles.get(userInfo.id);
301 data.pendingRemoval = false;
304 final Context context = mContext;
305 final ProfileData profileData = new ProfileData();
306 profileData.userInfo = userInfo;
307 AccessiblePreferenceCategory preferenceGroup =
308 mHelper.createAccessiblePreferenceCategory(mParent.getPreferenceManager().getContext());
309 preferenceGroup.setOrder(mAccountProfileOrder++);
310 if (isSingleProfile()) {
311 preferenceGroup.setTitle(context.getString(R.string.account_for_section_header,
313 preferenceGroup.setContentDescription(
314 mContext.getString(R.string.account_settings));
315 } else if (userInfo.isManagedProfile()) {
316 preferenceGroup.setTitle(R.string.category_work);
317 String workGroupSummary = getWorkGroupSummary(context, userInfo);
318 preferenceGroup.setSummary(workGroupSummary);
319 preferenceGroup.setContentDescription(
320 mContext.getString(R.string.accessibility_category_work, workGroupSummary));
321 profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context);
322 mHelper.enforceRestrictionOnPreference(profileData.removeWorkProfilePreference,
323 DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.myUserId());
324 profileData.managedProfilePreference = newManagedProfileSettings();
326 preferenceGroup.setTitle(R.string.category_personal);
327 preferenceGroup.setContentDescription(
328 mContext.getString(R.string.accessibility_category_personal));
330 final PreferenceScreen screen = mParent.getPreferenceScreen();
331 if (screen != null) {
332 screen.addPreference(preferenceGroup);
334 profileData.preferenceGroup = preferenceGroup;
335 if (userInfo.isEnabled()) {
336 profileData.authenticatorHelper = new AuthenticatorHelper(context,
337 userInfo.getUserHandle(), this);
338 profileData.addAccountPreference = newAddAccountPreference(context);
339 mHelper.enforceRestrictionOnPreference(profileData.addAccountPreference,
340 DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
342 mProfiles.put(userInfo.id, profileData);
343 new SearchFeatureProviderImpl().getIndexingManager(mContext).updateFromClassNameResource(
344 UserAndAccountDashboardFragment.class.getName(), true /* includeInSearchResults */);
347 private DimmableIconPreference newAddAccountPreference(Context context) {
348 DimmableIconPreference preference =
349 new DimmableIconPreference(mParent.getPreferenceManager().getContext());
350 preference.setTitle(R.string.add_account_label);
351 preference.setIcon(R.drawable.ic_menu_add);
352 preference.setOnPreferenceClickListener(this);
353 preference.setOrder(ORDER_NEXT_TO_NEXT_TO_LAST);
357 private RestrictedPreference newRemoveWorkProfilePreference(Context context) {
358 RestrictedPreference preference = new RestrictedPreference(
359 mParent.getPreferenceManager().getContext());
360 preference.setTitle(R.string.remove_managed_profile_label);
361 preference.setIcon(R.drawable.ic_menu_delete);
362 preference.setOnPreferenceClickListener(this);
363 preference.setOrder(ORDER_LAST);
368 private Preference newManagedProfileSettings() {
369 Preference preference = new Preference(mParent.getPreferenceManager().getContext());
370 preference.setTitle(R.string.managed_profile_settings_title);
371 preference.setIcon(R.drawable.ic_settings);
372 preference.setOnPreferenceClickListener(this);
373 preference.setOrder(ORDER_NEXT_TO_LAST);
377 private String getWorkGroupSummary(Context context, UserInfo userInfo) {
378 PackageManager packageManager = context.getPackageManager();
379 ApplicationInfo adminApplicationInfo = Utils.getAdminApplicationInfo(context, userInfo.id);
380 if (adminApplicationInfo == null) {
383 CharSequence appLabel = packageManager.getApplicationLabel(adminApplicationInfo);
384 return mContext.getString(R.string.managing_admin, appLabel);
387 void cleanUpPreferences() {
388 PreferenceScreen screen = mParent.getPreferenceScreen();
389 if (screen == null) {
392 final int count = mProfiles.size();
393 for (int i = count-1; i >= 0; i--) {
394 final ProfileData data = mProfiles.valueAt(i);
395 if (data.pendingRemoval) {
396 screen.removePreference(data.preferenceGroup);
397 mProfiles.removeAt(i);
402 private void listenToAccountUpdates() {
403 final int count = mProfiles.size();
404 for (int i = 0; i < count; i++) {
405 AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
406 if (authenticatorHelper != null) {
407 authenticatorHelper.listenToAccountUpdates();
412 private void stopListeningToAccountUpdates() {
413 final int count = mProfiles.size();
414 for (int i = 0; i < count; i++) {
415 AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
416 if (authenticatorHelper != null) {
417 authenticatorHelper.stopListeningToAccountUpdates();
422 private void updateAccountTypes(ProfileData profileData) {
423 if (mParent.getPreferenceManager() == null
424 || profileData.preferenceGroup.getPreferenceManager() == null) {
425 // This could happen if activity is finishing
428 if (profileData.userInfo.isEnabled()) {
429 final ArrayMap<String, AccountTypePreference> preferenceToRemove =
430 new ArrayMap<>(profileData.accountPreferences);
431 final ArrayList<AccountTypePreference> preferences = getAccountTypePreferences(
432 profileData.authenticatorHelper, profileData.userInfo.getUserHandle(),
434 final int count = preferences.size();
435 for (int i = 0; i < count; i++) {
436 final AccountTypePreference preference = preferences.get(i);
437 preference.setOrder(i);
438 final String key = preference.getKey();
439 if (!profileData.accountPreferences.containsKey(key)) {
440 profileData.preferenceGroup.addPreference(preference);
441 profileData.accountPreferences.put(key, preference);
444 if (profileData.addAccountPreference != null) {
445 profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
447 for (String key : preferenceToRemove.keySet()) {
448 profileData.preferenceGroup.removePreference(
449 profileData.accountPreferences.get(key));
450 profileData.accountPreferences.remove(key);
453 profileData.preferenceGroup.removeAll();
454 // Put a label instead of the accounts list
455 if (mProfileNotAvailablePreference == null) {
456 mProfileNotAvailablePreference =
457 new Preference(mParent.getPreferenceManager().getContext());
459 mProfileNotAvailablePreference.setEnabled(false);
460 mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
461 mProfileNotAvailablePreference.setTitle(null);
462 mProfileNotAvailablePreference.setSummary(
463 R.string.managed_profile_not_available_label);
464 profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference);
466 if (profileData.removeWorkProfilePreference != null) {
467 profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
469 if (profileData.managedProfilePreference != null) {
470 profileData.preferenceGroup.addPreference(profileData.managedProfilePreference);
474 private ArrayList<AccountTypePreference> getAccountTypePreferences(AuthenticatorHelper helper,
475 UserHandle userHandle, ArrayMap<String, AccountTypePreference> preferenceToRemove) {
476 final String[] accountTypes = helper.getEnabledAccountTypes();
477 final ArrayList<AccountTypePreference> accountTypePreferences =
478 new ArrayList<>(accountTypes.length);
480 for (int i = 0; i < accountTypes.length; i++) {
481 final String accountType = accountTypes[i];
482 // Skip showing any account that does not have any of the requested authorities
483 if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
486 final CharSequence label = helper.getLabelForType(mContext, accountType);
490 final String titleResPackageName = helper.getPackageForType(accountType);
491 final int titleResId = helper.getLabelIdForType(accountType);
493 final Account[] accounts = AccountManager.get(mContext)
494 .getAccountsByTypeAsUser(accountType, userHandle);
495 final Drawable icon = helper.getDrawableForType(mContext, accountType);
496 final Context prefContext = mParent.getPreferenceManager().getContext();
498 // Add a preference row for each individual account
499 for (Account account : accounts) {
500 final AccountTypePreference preference =
501 preferenceToRemove.remove(AccountTypePreference.buildKey(account));
502 if (preference != null) {
503 accountTypePreferences.add(preference);
506 final ArrayList<String> auths =
507 helper.getAuthoritiesForAccountType(account.type);
508 if (!AccountRestrictionHelper.showAccount(mAuthorities, auths)) {
511 final Bundle fragmentArguments = new Bundle();
512 fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT,
514 fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE,
516 fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE,
518 fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_LABEL,
520 fragmentArguments.putInt(AccountDetailDashboardFragment.KEY_ACCOUNT_TITLE_RES,
522 fragmentArguments.putParcelable(EXTRA_USER, userHandle);
523 accountTypePreferences.add(new AccountTypePreference(
524 prefContext, mMetricsFeatureProvider.getMetricsCategory(mParent),
525 account, titleResPackageName, titleResId, label,
526 AccountDetailDashboardFragment.class.getName(), fragmentArguments, icon));
528 helper.preloadDrawableForType(mContext, accountType);
531 Collections.sort(accountTypePreferences, new Comparator<AccountTypePreference>() {
533 public int compare(AccountTypePreference t1, AccountTypePreference t2) {
534 int result = t1.getSummary().toString().compareTo(t2.getSummary().toString());
536 ? result : t1.getTitle().toString().compareTo(t2.getTitle().toString());
539 return accountTypePreferences;
542 private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
543 String accountType) {
544 if (mAuthoritiesCount == 0) {
545 // No authorities required
548 final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
550 if (authoritiesForType == null) {
551 Log.d(TAG, "No sync authorities for account type: " + accountType);
554 for (int j = 0; j < mAuthoritiesCount; j++) {
555 if (authoritiesForType.contains(mAuthorities[j])) {
562 private boolean isSingleProfile() {
563 return mUm.isLinkedUser() || mUm.getProfiles(UserHandle.myUserId()).size() == 1;
566 private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
567 private boolean mListeningToManagedProfileEvents;
570 public void onReceive(Context context, Intent intent) {
571 final String action = intent.getAction();
572 Log.v(TAG, "Received broadcast: " + action);
573 if (action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
574 || action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
576 stopListeningToAccountUpdates();
579 listenToAccountUpdates();
582 Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
585 public void register(Context context) {
586 if (!mListeningToManagedProfileEvents) {
587 IntentFilter intentFilter = new IntentFilter();
588 intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
589 intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
590 context.registerReceiver(this, intentFilter);
591 mListeningToManagedProfileEvents = true;
595 public void unregister(Context context) {
596 if (mListeningToManagedProfileEvents) {
597 context.unregisterReceiver(this);
598 mListeningToManagedProfileEvents = false;