diff options
Diffstat (limited to 'src/com/android/settings/accounts/AccountSettings.java')
-rw-r--r-- | src/com/android/settings/accounts/AccountSettings.java | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/src/com/android/settings/accounts/AccountSettings.java b/src/com/android/settings/accounts/AccountSettings.java new file mode 100644 index 0000000..8a183da --- /dev/null +++ b/src/com/android/settings/accounts/AccountSettings.java @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accounts; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.Process; +import android.util.Log; +import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceGroup; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static android.content.Intent.EXTRA_USER; +import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS; +import static android.provider.Settings.EXTRA_AUTHORITIES; + +/** + * Settings screen for the account types on the device. + * This shows all account types available for personal and work profiles. + * + * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for + * which the action needs to be performed is different to the one the Settings App will run in. + */ +public class AccountSettings extends SettingsPreferenceFragment + implements AuthenticatorHelper.OnAccountsUpdateListener, + OnPreferenceClickListener { + public static final String TAG = "AccountSettings"; + + private static final String KEY_ACCOUNT = "account"; + + private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS"; + private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange"; + + private static final int ORDER_LAST = 1001; + private static final int ORDER_NEXT_TO_LAST = 1000; + + private UserManager mUm; + private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>(); + private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver + = new ManagedProfileBroadcastReceiver(); + private Preference mProfileNotAvailablePreference; + private String[] mAuthorities; + private int mAuthoritiesCount = 0; + + /** + * Holds data related to the accounts belonging to one profile. + */ + private static class ProfileData { + /** + * The preference that displays the accounts. + */ + public PreferenceGroup preferenceGroup; + /** + * The preference that displays the add account button. + */ + public Preference addAccountPreference; + /** + * The preference that displays the button to remove the managed profile + */ + public Preference removeWorkProfilePreference; + /** + * The {@link AuthenticatorHelper} that holds accounts data for this profile. + */ + public AuthenticatorHelper authenticatorHelper; + /** + * The {@link UserInfo} of the profile. + */ + public UserInfo userInfo; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mUm = (UserManager) getSystemService(Context.USER_SERVICE); + mProfileNotAvailablePreference = new Preference(getActivity()); + mAuthorities = getActivity().getIntent().getStringArrayExtra(EXTRA_AUTHORITIES); + if (mAuthorities != null) { + mAuthoritiesCount = mAuthorities.length; + } + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.account_settings, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + final UserHandle currentProfile = Process.myUserHandle(); + if (mProfiles.size() == 1) { + menu.findItem(R.id.account_settings_menu_auto_sync) + .setVisible(true) + .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile)) + .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser( + currentProfile.getIdentifier())); + menu.findItem(R.id.account_settings_menu_auto_sync_personal).setVisible(false); + menu.findItem(R.id.account_settings_menu_auto_sync_work).setVisible(false); + } else if (mProfiles.size() > 1) { + // We assume there's only one managed profile, otherwise UI needs to change + final UserHandle managedProfile = mProfiles.valueAt(1).userInfo.getUserHandle(); + + menu.findItem(R.id.account_settings_menu_auto_sync_personal) + .setVisible(true) + .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile)) + .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser( + currentProfile.getIdentifier())); + menu.findItem(R.id.account_settings_menu_auto_sync_work) + .setVisible(true) + .setOnMenuItemClickListener(new MasterSyncStateClickListener(managedProfile)) + .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser( + managedProfile.getIdentifier())); + menu.findItem(R.id.account_settings_menu_auto_sync).setVisible(false); + } else { + Log.w(TAG, "Method onPrepareOptionsMenu called before mProfiles was initialized"); + } + } + + @Override + public void onResume() { + super.onResume(); + updateUi(); + mManagedProfileBroadcastReceiver.register(getActivity()); + listenToAccountUpdates(); + } + + @Override + public void onPause() { + super.onPause(); + stopListeningToAccountUpdates(); + mManagedProfileBroadcastReceiver.unregister(getActivity()); + cleanUpPreferences(); + } + + @Override + public void onAccountsUpdate(UserHandle userHandle) { + final ProfileData profileData = mProfiles.get(userHandle.getIdentifier()); + if (profileData != null) { + updateAccountTypes(profileData); + } else { + Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier()); + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + // Check the preference + final int count = mProfiles.size(); + for (int i = 0; i < count; i++) { + ProfileData profileData = mProfiles.valueAt(i); + if (preference == profileData.addAccountPreference) { + Intent intent = new Intent(ADD_ACCOUNT_ACTION); + intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle()); + intent.putExtra(EXTRA_AUTHORITIES, mAuthorities); + startActivity(intent); + return true; + } + if (preference == profileData.removeWorkProfilePreference) { + final int userId = profileData.userInfo.id; + Utils.createRemoveConfirmationDialog(getActivity(), userId, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mUm.removeUser(userId); + } + } + ).show(); + return true; + } + } + return false; + } + + void updateUi() { + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.account_settings); + + if (Utils.isManagedProfile(mUm)) { + // This should not happen + Log.e(TAG, "We should not be showing settings for a managed profile"); + finish(); + return; + } + + final PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference(KEY_ACCOUNT); + if(mUm.isLinkedUser()) { + // Restricted user or similar + UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId()); + updateProfileUi(userInfo, false /* no category needed */, preferenceScreen); + } else { + List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId()); + final int profilesCount = profiles.size(); + final boolean addCategory = profilesCount > 1; + for (int i = 0; i < profilesCount; i++) { + updateProfileUi(profiles.get(i), addCategory, preferenceScreen); + } + } + + // Add all preferences, starting with one for the primary profile. + // Note that we're relying on the ordering given by the SparseArray keys, and on the + // value of UserHandle.USER_OWNER being smaller than all the rest. + final int profilesCount = mProfiles.size(); + for (int i = 0; i < profilesCount; i++) { + ProfileData profileData = mProfiles.valueAt(i); + if (!profileData.preferenceGroup.equals(preferenceScreen)) { + preferenceScreen.addPreference(profileData.preferenceGroup); + } + updateAccountTypes(profileData); + } + } + + private void updateProfileUi(final UserInfo userInfo, boolean addCategory, + PreferenceScreen parent) { + final Context context = getActivity(); + final ProfileData profileData = new ProfileData(); + profileData.userInfo = userInfo; + if (addCategory) { + profileData.preferenceGroup = new PreferenceCategory(context); + profileData.preferenceGroup.setTitle(userInfo.isManagedProfile() + ? R.string.category_work : R.string.category_personal); + parent.addPreference(profileData.preferenceGroup); + } else { + profileData.preferenceGroup = parent; + } + if (userInfo.isEnabled()) { + profileData.authenticatorHelper = new AuthenticatorHelper(context, + userInfo.getUserHandle(), mUm, this); + if (!mUm.hasUserRestriction(DISALLOW_MODIFY_ACCOUNTS, userInfo.getUserHandle())) { + profileData.addAccountPreference = newAddAccountPreference(context); + } + } + if (userInfo.isManagedProfile()) { + profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context); + } + mProfiles.put(userInfo.id, profileData); + } + + private Preference newAddAccountPreference(Context context) { + Preference preference = new Preference(context); + preference.setTitle(R.string.add_account_label); + preference.setIcon(R.drawable.ic_menu_add_dark); + preference.setOnPreferenceClickListener(this); + preference.setOrder(ORDER_NEXT_TO_LAST); + return preference; + } + + private Preference newRemoveWorkProfilePreference(Context context) { + Preference preference = new Preference(context); + preference.setTitle(R.string.remove_managed_profile_label); + preference.setIcon(R.drawable.ic_menu_delete); + preference.setOnPreferenceClickListener(this); + preference.setOrder(ORDER_LAST); + return preference; + } + + private void cleanUpPreferences() { + PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + preferenceScreen.removeAll(); + } + mProfiles.clear(); + } + + private void listenToAccountUpdates() { + final int count = mProfiles.size(); + for (int i = 0; i < count; i++) { + AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper; + if (authenticatorHelper != null) { + authenticatorHelper.listenToAccountUpdates(); + } + } + } + + private void stopListeningToAccountUpdates() { + final int count = mProfiles.size(); + for (int i = 0; i < count; i++) { + AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper; + if (authenticatorHelper != null) { + authenticatorHelper.stopListeningToAccountUpdates(); + } + } + } + + private void updateAccountTypes(ProfileData profileData) { + profileData.preferenceGroup.removeAll(); + if (profileData.userInfo.isEnabled()) { + final ArrayList<AccountPreference> preferences = getAccountTypePreferences( + profileData.authenticatorHelper, profileData.userInfo.getUserHandle()); + final int count = preferences.size(); + for (int i = 0; i < count; i++) { + profileData.preferenceGroup.addPreference(preferences.get(i)); + } + if (profileData.addAccountPreference != null) { + profileData.preferenceGroup.addPreference(profileData.addAccountPreference); + } + } else { + // Put a label instead of the accounts list + mProfileNotAvailablePreference.setEnabled(false); + mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon); + mProfileNotAvailablePreference.setTitle(null); + mProfileNotAvailablePreference.setSummary( + R.string.managed_profile_not_available_label); + profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference); + } + if (profileData.removeWorkProfilePreference != null) { + profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference); + } + } + + private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper, + UserHandle userHandle) { + final String[] accountTypes = helper.getEnabledAccountTypes(); + final ArrayList<AccountPreference> accountTypePreferences = + new ArrayList<AccountPreference>(accountTypes.length); + + for (int i = 0; i < accountTypes.length; i++) { + final String accountType = accountTypes[i]; + // Skip showing any account that does not have any of the requested authorities + if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) { + continue; + } + final CharSequence label = helper.getLabelForType(getActivity(), accountType); + if (label == null) { + continue; + } + + final Account[] accounts = AccountManager.get(getActivity()) + .getAccountsByTypeAsUser(accountType, userHandle); + final boolean skipToAccount = accounts.length == 1 + && !helper.hasAccountPreferences(accountType); + + if (skipToAccount) { + final Bundle fragmentArguments = new Bundle(); + fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY, + accounts[0]); + fragmentArguments.putParcelable(EXTRA_USER, userHandle); + + accountTypePreferences.add(new AccountPreference(getActivity(), label, + AccountSyncSettings.class.getName(), fragmentArguments, + helper.getDrawableForType(getActivity(), accountType))); + } else { + final Bundle fragmentArguments = new Bundle(); + fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType); + fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL, + label.toString()); + fragmentArguments.putParcelable(EXTRA_USER, userHandle); + + accountTypePreferences.add(new AccountPreference(getActivity(), label, + ManageAccountsSettings.class.getName(), fragmentArguments, + helper.getDrawableForType(getActivity(), accountType))); + } + helper.preloadDrawableForType(getActivity(), accountType); + } + // Sort by label + Collections.sort(accountTypePreferences, new Comparator<AccountPreference>() { + @Override + public int compare(AccountPreference t1, AccountPreference t2) { + return t1.mTitle.toString().compareTo(t2.mTitle.toString()); + } + }); + return accountTypePreferences; + } + + private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper, + String accountType) { + if (mAuthoritiesCount == 0) { + // No authorities required + return true; + } + final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType( + accountType); + if (authoritiesForType == null) { + Log.d(TAG, "No sync authorities for account type: " + accountType); + return false; + } + for (int j = 0; j < mAuthoritiesCount; j++) { + if (authoritiesForType.contains(mAuthorities[j])) { + return true; + } + } + return false; + } + + private class AccountPreference extends Preference implements OnPreferenceClickListener { + /** + * Title of the tile that is shown to the user. + * @attr ref android.R.styleable#PreferenceHeader_title + */ + private final CharSequence mTitle; + + /** + * Full class name of the fragment to display when this tile is + * selected. + * @attr ref android.R.styleable#PreferenceHeader_fragment + */ + private final String mFragment; + + /** + * Optional arguments to supply to the fragment when it is + * instantiated. + */ + private final Bundle mFragmentArguments; + + public AccountPreference(Context context, CharSequence title, String fragment, + Bundle fragmentArguments, Drawable icon) { + super(context); + mTitle = title; + mFragment = fragment; + mFragmentArguments = fragmentArguments; + setWidgetLayoutResource(R.layout.account_type_preference); + + setTitle(title); + setIcon(icon); + + setOnPreferenceClickListener(this); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (mFragment != null) { + Utils.startWithFragment( + getContext(), mFragment, mFragmentArguments, null, 0, 0, mTitle); + return true; + } + return false; + } + } + + private class ManagedProfileBroadcastReceiver extends BroadcastReceiver { + private boolean listeningToManagedProfileEvents; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_REMOVED) + || intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) { + Log.v(TAG, "Received broadcast: " + intent.getAction()); + // Clean old state + stopListeningToAccountUpdates(); + cleanUpPreferences(); + // Build new state + updateUi(); + listenToAccountUpdates(); + // Force the menu to update. Note that #onPrepareOptionsMenu uses data built by + // #updateUi so we must call this later + getActivity().invalidateOptionsMenu(); + return; + } + Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction()); + } + + public void register(Context context) { + if (!listeningToManagedProfileEvents) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); + intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + context.registerReceiver(this, intentFilter); + listeningToManagedProfileEvents = true; + } + } + + public void unregister(Context context) { + if (listeningToManagedProfileEvents) { + context.unregisterReceiver(this); + listeningToManagedProfileEvents = false; + } + } + } + + private class MasterSyncStateClickListener implements MenuItem.OnMenuItemClickListener { + private final UserHandle mUserHandle; + + public MasterSyncStateClickListener(UserHandle userHandle) { + mUserHandle = userHandle; + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + if (ActivityManager.isUserAMonkey()) { + Log.d(TAG, "ignoring monkey's attempt to flip sync state"); + } else { + ConfirmAutoSyncChangeFragment.show(AccountSettings.this, !item.isChecked(), + mUserHandle); + } + return true; + } + } + + /** + * Dialog to inform user about changing auto-sync setting + */ + public static class ConfirmAutoSyncChangeFragment extends DialogFragment { + private static final String SAVE_ENABLING = "enabling"; + private boolean mEnabling; + private UserHandle mUserHandle; + + public static void show(AccountSettings parent, boolean enabling, UserHandle userHandle) { + if (!parent.isAdded()) return; + + final ConfirmAutoSyncChangeFragment dialog = new ConfirmAutoSyncChangeFragment(); + dialog.mEnabling = enabling; + dialog.mUserHandle = userHandle; + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_AUTO_SYNC_CHANGE); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + if (savedInstanceState != null) { + mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING); + } + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + if (!mEnabling) { + builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title); + builder.setMessage(R.string.data_usage_auto_sync_off_dialog); + } else { + builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title); + builder.setMessage(R.string.data_usage_auto_sync_on_dialog); + } + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ContentResolver.setMasterSyncAutomaticallyAsUser(mEnabling, + mUserHandle.getIdentifier()); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + return builder.create(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(SAVE_ENABLING, mEnabling); + } + } + // TODO Implement a {@link SearchIndexProvider} to allow Indexing and Search of account types + // See http://b/15403806 +} |