2 * Copyright (C) 2014 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;
20 import android.accounts.Account;
21 import android.accounts.AccountManager;
22 import android.app.ActivityManager;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.app.DialogFragment;
26 import android.content.BroadcastReceiver;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.UserInfo;
33 import android.graphics.drawable.Drawable;
34 import android.os.Bundle;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.os.Process;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.view.Menu;
41 import android.view.MenuInflater;
42 import android.view.MenuItem;
43 import android.preference.Preference;
44 import android.preference.Preference.OnPreferenceClickListener;
45 import android.preference.PreferenceGroup;
46 import android.preference.PreferenceCategory;
47 import android.preference.PreferenceScreen;
49 import com.android.settings.R;
50 import com.android.settings.SettingsPreferenceFragment;
51 import com.android.settings.Utils;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.List;
58 import static android.content.Intent.EXTRA_USER;
59 import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
60 import static android.provider.Settings.EXTRA_AUTHORITIES;
63 * Settings screen for the account types on the device.
64 * This shows all account types available for personal and work profiles.
66 * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
67 * which the action needs to be performed is different to the one the Settings App will run in.
69 public class AccountSettings extends SettingsPreferenceFragment
70 implements AuthenticatorHelper.OnAccountsUpdateListener,
71 OnPreferenceClickListener {
72 public static final String TAG = "AccountSettings";
74 private static final String KEY_ACCOUNT = "account";
76 private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS";
77 private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange";
79 private static final int ORDER_LAST = 1001;
80 private static final int ORDER_NEXT_TO_LAST = 1000;
82 private UserManager mUm;
83 private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
84 private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver
85 = new ManagedProfileBroadcastReceiver();
86 private Preference mProfileNotAvailablePreference;
87 private String[] mAuthorities;
88 private int mAuthoritiesCount = 0;
91 * Holds data related to the accounts belonging to one profile.
93 private static class ProfileData {
95 * The preference that displays the accounts.
97 public PreferenceGroup preferenceGroup;
99 * The preference that displays the add account button.
101 public Preference addAccountPreference;
103 * The preference that displays the button to remove the managed profile
105 public Preference removeWorkProfilePreference;
107 * The {@link AuthenticatorHelper} that holds accounts data for this profile.
109 public AuthenticatorHelper authenticatorHelper;
111 * The {@link UserInfo} of the profile.
113 public UserInfo userInfo;
117 public void onCreate(Bundle savedInstanceState) {
118 super.onCreate(savedInstanceState);
119 mUm = (UserManager) getSystemService(Context.USER_SERVICE);
120 mProfileNotAvailablePreference = new Preference(getActivity());
121 mAuthorities = getActivity().getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
122 if (mAuthorities != null) {
123 mAuthoritiesCount = mAuthorities.length;
125 setHasOptionsMenu(true);
129 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
130 inflater.inflate(R.menu.account_settings, menu);
131 super.onCreateOptionsMenu(menu, inflater);
135 public void onPrepareOptionsMenu(Menu menu) {
136 final UserHandle currentProfile = Process.myUserHandle();
137 if (mProfiles.size() == 1) {
138 menu.findItem(R.id.account_settings_menu_auto_sync)
140 .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
141 .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
142 currentProfile.getIdentifier()));
143 menu.findItem(R.id.account_settings_menu_auto_sync_personal).setVisible(false);
144 menu.findItem(R.id.account_settings_menu_auto_sync_work).setVisible(false);
145 } else if (mProfiles.size() > 1) {
146 // We assume there's only one managed profile, otherwise UI needs to change
147 final UserHandle managedProfile = mProfiles.valueAt(1).userInfo.getUserHandle();
149 menu.findItem(R.id.account_settings_menu_auto_sync_personal)
151 .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
152 .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
153 currentProfile.getIdentifier()));
154 menu.findItem(R.id.account_settings_menu_auto_sync_work)
156 .setOnMenuItemClickListener(new MasterSyncStateClickListener(managedProfile))
157 .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
158 managedProfile.getIdentifier()));
159 menu.findItem(R.id.account_settings_menu_auto_sync).setVisible(false);
161 Log.w(TAG, "Method onPrepareOptionsMenu called before mProfiles was initialized");
166 public void onResume() {
169 mManagedProfileBroadcastReceiver.register(getActivity());
170 listenToAccountUpdates();
174 public void onPause() {
176 stopListeningToAccountUpdates();
177 mManagedProfileBroadcastReceiver.unregister(getActivity());
178 cleanUpPreferences();
182 public void onAccountsUpdate(UserHandle userHandle) {
183 final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
184 if (profileData != null) {
185 updateAccountTypes(profileData);
187 Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
192 public boolean onPreferenceClick(Preference preference) {
193 // Check the preference
194 final int count = mProfiles.size();
195 for (int i = 0; i < count; i++) {
196 ProfileData profileData = mProfiles.valueAt(i);
197 if (preference == profileData.addAccountPreference) {
198 Intent intent = new Intent(ADD_ACCOUNT_ACTION);
199 intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
200 intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
201 startActivity(intent);
204 if (preference == profileData.removeWorkProfilePreference) {
205 final int userId = profileData.userInfo.id;
206 Utils.createRemoveConfirmationDialog(getActivity(), userId,
207 new DialogInterface.OnClickListener() {
209 public void onClick(DialogInterface dialog, int which) {
210 mUm.removeUser(userId);
221 // Load the preferences from an XML resource
222 addPreferencesFromResource(R.xml.account_settings);
224 if (Utils.isManagedProfile(mUm)) {
225 // This should not happen
226 Log.e(TAG, "We should not be showing settings for a managed profile");
231 final PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference(KEY_ACCOUNT);
232 if(mUm.isLinkedUser()) {
233 // Restricted user or similar
234 UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
235 updateProfileUi(userInfo, false /* no category needed */, preferenceScreen);
237 List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
238 final int profilesCount = profiles.size();
239 final boolean addCategory = profilesCount > 1;
240 for (int i = 0; i < profilesCount; i++) {
241 updateProfileUi(profiles.get(i), addCategory, preferenceScreen);
245 // Add all preferences, starting with one for the primary profile.
246 // Note that we're relying on the ordering given by the SparseArray keys, and on the
247 // value of UserHandle.USER_OWNER being smaller than all the rest.
248 final int profilesCount = mProfiles.size();
249 for (int i = 0; i < profilesCount; i++) {
250 ProfileData profileData = mProfiles.valueAt(i);
251 if (!profileData.preferenceGroup.equals(preferenceScreen)) {
252 preferenceScreen.addPreference(profileData.preferenceGroup);
254 updateAccountTypes(profileData);
258 private void updateProfileUi(final UserInfo userInfo, boolean addCategory,
259 PreferenceScreen parent) {
260 final Context context = getActivity();
261 final ProfileData profileData = new ProfileData();
262 profileData.userInfo = userInfo;
264 profileData.preferenceGroup = new PreferenceCategory(context);
265 profileData.preferenceGroup.setTitle(userInfo.isManagedProfile()
266 ? R.string.category_work : R.string.category_personal);
267 parent.addPreference(profileData.preferenceGroup);
269 profileData.preferenceGroup = parent;
271 if (userInfo.isEnabled()) {
272 profileData.authenticatorHelper = new AuthenticatorHelper(context,
273 userInfo.getUserHandle(), mUm, this);
274 if (!mUm.hasUserRestriction(DISALLOW_MODIFY_ACCOUNTS, userInfo.getUserHandle())) {
275 profileData.addAccountPreference = newAddAccountPreference(context);
278 if (userInfo.isManagedProfile()) {
279 profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context);
281 mProfiles.put(userInfo.id, profileData);
284 private Preference newAddAccountPreference(Context context) {
285 Preference preference = new Preference(context);
286 preference.setTitle(R.string.add_account_label);
287 preference.setIcon(R.drawable.ic_menu_add_dark);
288 preference.setOnPreferenceClickListener(this);
289 preference.setOrder(ORDER_NEXT_TO_LAST);
293 private Preference newRemoveWorkProfilePreference(Context context) {
294 Preference preference = new Preference(context);
295 preference.setTitle(R.string.remove_managed_profile_label);
296 preference.setIcon(R.drawable.ic_menu_delete);
297 preference.setOnPreferenceClickListener(this);
298 preference.setOrder(ORDER_LAST);
302 private void cleanUpPreferences() {
303 PreferenceScreen preferenceScreen = getPreferenceScreen();
304 if (preferenceScreen != null) {
305 preferenceScreen.removeAll();
310 private void listenToAccountUpdates() {
311 final int count = mProfiles.size();
312 for (int i = 0; i < count; i++) {
313 AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
314 if (authenticatorHelper != null) {
315 authenticatorHelper.listenToAccountUpdates();
320 private void stopListeningToAccountUpdates() {
321 final int count = mProfiles.size();
322 for (int i = 0; i < count; i++) {
323 AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
324 if (authenticatorHelper != null) {
325 authenticatorHelper.stopListeningToAccountUpdates();
330 private void updateAccountTypes(ProfileData profileData) {
331 profileData.preferenceGroup.removeAll();
332 if (profileData.userInfo.isEnabled()) {
333 final ArrayList<AccountPreference> preferences = getAccountTypePreferences(
334 profileData.authenticatorHelper, profileData.userInfo.getUserHandle());
335 final int count = preferences.size();
336 for (int i = 0; i < count; i++) {
337 profileData.preferenceGroup.addPreference(preferences.get(i));
339 if (profileData.addAccountPreference != null) {
340 profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
343 // Put a label instead of the accounts list
344 mProfileNotAvailablePreference.setEnabled(false);
345 mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
346 mProfileNotAvailablePreference.setTitle(null);
347 mProfileNotAvailablePreference.setSummary(
348 R.string.managed_profile_not_available_label);
349 profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference);
351 if (profileData.removeWorkProfilePreference != null) {
352 profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
356 private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper,
357 UserHandle userHandle) {
358 final String[] accountTypes = helper.getEnabledAccountTypes();
359 final ArrayList<AccountPreference> accountTypePreferences =
360 new ArrayList<AccountPreference>(accountTypes.length);
362 for (int i = 0; i < accountTypes.length; i++) {
363 final String accountType = accountTypes[i];
364 // Skip showing any account that does not have any of the requested authorities
365 if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
368 final CharSequence label = helper.getLabelForType(getActivity(), accountType);
373 final Account[] accounts = AccountManager.get(getActivity())
374 .getAccountsByTypeAsUser(accountType, userHandle);
375 final boolean skipToAccount = accounts.length == 1
376 && !helper.hasAccountPreferences(accountType);
379 final Bundle fragmentArguments = new Bundle();
380 fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
382 fragmentArguments.putParcelable(EXTRA_USER, userHandle);
384 accountTypePreferences.add(new AccountPreference(getActivity(), label,
385 AccountSyncSettings.class.getName(), fragmentArguments,
386 helper.getDrawableForType(getActivity(), accountType)));
388 final Bundle fragmentArguments = new Bundle();
389 fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
390 fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
392 fragmentArguments.putParcelable(EXTRA_USER, userHandle);
394 accountTypePreferences.add(new AccountPreference(getActivity(), label,
395 ManageAccountsSettings.class.getName(), fragmentArguments,
396 helper.getDrawableForType(getActivity(), accountType)));
398 helper.preloadDrawableForType(getActivity(), accountType);
401 Collections.sort(accountTypePreferences, new Comparator<AccountPreference>() {
403 public int compare(AccountPreference t1, AccountPreference t2) {
404 return t1.mTitle.toString().compareTo(t2.mTitle.toString());
407 return accountTypePreferences;
410 private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
411 String accountType) {
412 if (mAuthoritiesCount == 0) {
413 // No authorities required
416 final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
418 if (authoritiesForType == null) {
419 Log.d(TAG, "No sync authorities for account type: " + accountType);
422 for (int j = 0; j < mAuthoritiesCount; j++) {
423 if (authoritiesForType.contains(mAuthorities[j])) {
430 private class AccountPreference extends Preference implements OnPreferenceClickListener {
432 * Title of the tile that is shown to the user.
433 * @attr ref android.R.styleable#PreferenceHeader_title
435 private final CharSequence mTitle;
438 * Full class name of the fragment to display when this tile is
440 * @attr ref android.R.styleable#PreferenceHeader_fragment
442 private final String mFragment;
445 * Optional arguments to supply to the fragment when it is
448 private final Bundle mFragmentArguments;
450 public AccountPreference(Context context, CharSequence title, String fragment,
451 Bundle fragmentArguments, Drawable icon) {
454 mFragment = fragment;
455 mFragmentArguments = fragmentArguments;
456 setWidgetLayoutResource(R.layout.account_type_preference);
461 setOnPreferenceClickListener(this);
465 public boolean onPreferenceClick(Preference preference) {
466 if (mFragment != null) {
467 Utils.startWithFragment(
468 getContext(), mFragment, mFragmentArguments, null, 0, 0, mTitle);
475 private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
476 private boolean listeningToManagedProfileEvents;
479 public void onReceive(Context context, Intent intent) {
480 if (intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
481 || intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
482 Log.v(TAG, "Received broadcast: " + intent.getAction());
484 stopListeningToAccountUpdates();
485 cleanUpPreferences();
488 listenToAccountUpdates();
489 // Force the menu to update. Note that #onPrepareOptionsMenu uses data built by
490 // #updateUi so we must call this later
491 getActivity().invalidateOptionsMenu();
494 Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
497 public void register(Context context) {
498 if (!listeningToManagedProfileEvents) {
499 IntentFilter intentFilter = new IntentFilter();
500 intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
501 intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
502 context.registerReceiver(this, intentFilter);
503 listeningToManagedProfileEvents = true;
507 public void unregister(Context context) {
508 if (listeningToManagedProfileEvents) {
509 context.unregisterReceiver(this);
510 listeningToManagedProfileEvents = false;
515 private class MasterSyncStateClickListener implements MenuItem.OnMenuItemClickListener {
516 private final UserHandle mUserHandle;
518 public MasterSyncStateClickListener(UserHandle userHandle) {
519 mUserHandle = userHandle;
523 public boolean onMenuItemClick(MenuItem item) {
524 if (ActivityManager.isUserAMonkey()) {
525 Log.d(TAG, "ignoring monkey's attempt to flip sync state");
527 ConfirmAutoSyncChangeFragment.show(AccountSettings.this, !item.isChecked(),
535 * Dialog to inform user about changing auto-sync setting
537 public static class ConfirmAutoSyncChangeFragment extends DialogFragment {
538 private static final String SAVE_ENABLING = "enabling";
539 private boolean mEnabling;
540 private UserHandle mUserHandle;
542 public static void show(AccountSettings parent, boolean enabling, UserHandle userHandle) {
543 if (!parent.isAdded()) return;
545 final ConfirmAutoSyncChangeFragment dialog = new ConfirmAutoSyncChangeFragment();
546 dialog.mEnabling = enabling;
547 dialog.mUserHandle = userHandle;
548 dialog.setTargetFragment(parent, 0);
549 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_AUTO_SYNC_CHANGE);
553 public Dialog onCreateDialog(Bundle savedInstanceState) {
554 final Context context = getActivity();
555 if (savedInstanceState != null) {
556 mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING);
559 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
561 builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title);
562 builder.setMessage(R.string.data_usage_auto_sync_off_dialog);
564 builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title);
565 builder.setMessage(R.string.data_usage_auto_sync_on_dialog);
568 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
570 public void onClick(DialogInterface dialog, int which) {
571 ContentResolver.setMasterSyncAutomaticallyAsUser(mEnabling,
572 mUserHandle.getIdentifier());
575 builder.setNegativeButton(android.R.string.cancel, null);
577 return builder.create();
581 public void onSaveInstanceState(Bundle outState) {
582 super.onSaveInstanceState(outState);
583 outState.putBoolean(SAVE_ENABLING, mEnabling);
586 // TODO Implement a {@link SearchIndexProvider} to allow Indexing and Search of account types
587 // See http://b/15403806