Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2014 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.settings.accounts;
     18 
     19 
     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;
     48 
     49 import com.android.settings.R;
     50 import com.android.settings.SettingsPreferenceFragment;
     51 import com.android.settings.Utils;
     52 
     53 import java.util.ArrayList;
     54 import java.util.Collections;
     55 import java.util.Comparator;
     56 import java.util.List;
     57 
     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;
     61 
     62 /**
     63  * Settings screen for the account types on the device.
     64  * This shows all account types available for personal and work profiles.
     65  *
     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.
     68  */
     69 public class AccountSettings extends SettingsPreferenceFragment
     70         implements AuthenticatorHelper.OnAccountsUpdateListener,
     71         OnPreferenceClickListener {
     72     public static final String TAG = "AccountSettings";
     73 
     74     private static final String KEY_ACCOUNT = "account";
     75 
     76     private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS";
     77     private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange";
     78 
     79     private static final int ORDER_LAST = 1001;
     80     private static final int ORDER_NEXT_TO_LAST = 1000;
     81 
     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;
     89 
     90     /**
     91      * Holds data related to the accounts belonging to one profile.
     92      */
     93     private static class ProfileData {
     94         /**
     95          * The preference that displays the accounts.
     96          */
     97         public PreferenceGroup preferenceGroup;
     98         /**
     99          * The preference that displays the add account button.
    100          */
    101         public Preference addAccountPreference;
    102         /**
    103          * The preference that displays the button to remove the managed profile
    104          */
    105         public Preference removeWorkProfilePreference;
    106         /**
    107          * The {@link AuthenticatorHelper} that holds accounts data for this profile.
    108          */
    109         public AuthenticatorHelper authenticatorHelper;
    110         /**
    111          * The {@link UserInfo} of the profile.
    112          */
    113         public UserInfo userInfo;
    114     }
    115 
    116     @Override
    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;
    124         }
    125         setHasOptionsMenu(true);
    126     }
    127 
    128     @Override
    129     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    130         inflater.inflate(R.menu.account_settings, menu);
    131         super.onCreateOptionsMenu(menu, inflater);
    132     }
    133 
    134     @Override
    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)
    139                     .setVisible(true)
    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();
    148 
    149             menu.findItem(R.id.account_settings_menu_auto_sync_personal)
    150                     .setVisible(true)
    151                     .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
    152                     .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
    153                             currentProfile.getIdentifier()));
    154             menu.findItem(R.id.account_settings_menu_auto_sync_work)
    155                     .setVisible(true)
    156                     .setOnMenuItemClickListener(new MasterSyncStateClickListener(managedProfile))
    157                     .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
    158                             managedProfile.getIdentifier()));
    159             menu.findItem(R.id.account_settings_menu_auto_sync).setVisible(false);
    160          } else {
    161              Log.w(TAG, "Method onPrepareOptionsMenu called before mProfiles was initialized");
    162          }
    163     }
    164 
    165     @Override
    166     public void onResume() {
    167         super.onResume();
    168         updateUi();
    169         mManagedProfileBroadcastReceiver.register(getActivity());
    170         listenToAccountUpdates();
    171     }
    172 
    173     @Override
    174     public void onPause() {
    175         super.onPause();
    176         stopListeningToAccountUpdates();
    177         mManagedProfileBroadcastReceiver.unregister(getActivity());
    178         cleanUpPreferences();
    179     }
    180 
    181     @Override
    182     public void onAccountsUpdate(UserHandle userHandle) {
    183         final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
    184         if (profileData != null) {
    185             updateAccountTypes(profileData);
    186         } else {
    187             Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
    188         }
    189     }
    190 
    191     @Override
    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);
    202                 return true;
    203             }
    204             if (preference == profileData.removeWorkProfilePreference) {
    205                 final int userId = profileData.userInfo.id;
    206                 Utils.createRemoveConfirmationDialog(getActivity(), userId,
    207                         new DialogInterface.OnClickListener() {
    208                             @Override
    209                             public void onClick(DialogInterface dialog, int which) {
    210                                 mUm.removeUser(userId);
    211                             }
    212                         }
    213                 ).show();
    214                 return true;
    215             }
    216         }
    217         return false;
    218     }
    219 
    220     void updateUi() {
    221         // Load the preferences from an XML resource
    222         addPreferencesFromResource(R.xml.account_settings);
    223 
    224         if (Utils.isManagedProfile(mUm)) {
    225             // This should not happen
    226             Log.e(TAG, "We should not be showing settings for a managed profile");
    227             finish();
    228             return;
    229         }
    230 
    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);
    236         } else {
    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);
    242             }
    243         }
    244 
    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);
    253             }
    254             updateAccountTypes(profileData);
    255         }
    256     }
    257 
    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;
    263         if (addCategory) {
    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);
    268         } else {
    269             profileData.preferenceGroup = parent;
    270         }
    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);
    276             }
    277         }
    278         if (userInfo.isManagedProfile()) {
    279             profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context);
    280         }
    281         mProfiles.put(userInfo.id, profileData);
    282     }
    283 
    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);
    290         return preference;
    291     }
    292 
    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);
    299         return preference;
    300     }
    301 
    302     private void cleanUpPreferences() {
    303         PreferenceScreen preferenceScreen = getPreferenceScreen();
    304         if (preferenceScreen != null) {
    305             preferenceScreen.removeAll();
    306         }
    307         mProfiles.clear();
    308     }
    309 
    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();
    316             }
    317         }
    318     }
    319 
    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();
    326             }
    327         }
    328     }
    329 
    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));
    338             }
    339             if (profileData.addAccountPreference != null) {
    340                 profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
    341             }
    342         } else {
    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);
    350         }
    351         if (profileData.removeWorkProfilePreference != null) {
    352             profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
    353         }
    354     }
    355 
    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);
    361 
    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)) {
    366                 continue;
    367             }
    368             final CharSequence label = helper.getLabelForType(getActivity(), accountType);
    369             if (label == null) {
    370                 continue;
    371             }
    372 
    373             final Account[] accounts = AccountManager.get(getActivity())
    374                     .getAccountsByTypeAsUser(accountType, userHandle);
    375             final boolean skipToAccount = accounts.length == 1
    376                     && !helper.hasAccountPreferences(accountType);
    377 
    378             if (skipToAccount) {
    379                 final Bundle fragmentArguments = new Bundle();
    380                 fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
    381                         accounts[0]);
    382                 fragmentArguments.putParcelable(EXTRA_USER, userHandle);
    383 
    384                 accountTypePreferences.add(new AccountPreference(getActivity(), label,
    385                         AccountSyncSettings.class.getName(), fragmentArguments,
    386                         helper.getDrawableForType(getActivity(), accountType)));
    387             } else {
    388                 final Bundle fragmentArguments = new Bundle();
    389                 fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
    390                 fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
    391                         label.toString());
    392                 fragmentArguments.putParcelable(EXTRA_USER, userHandle);
    393 
    394                 accountTypePreferences.add(new AccountPreference(getActivity(), label,
    395                         ManageAccountsSettings.class.getName(), fragmentArguments,
    396                         helper.getDrawableForType(getActivity(), accountType)));
    397             }
    398             helper.preloadDrawableForType(getActivity(), accountType);
    399         }
    400         // Sort by label
    401         Collections.sort(accountTypePreferences, new Comparator<AccountPreference>() {
    402             @Override
    403             public int compare(AccountPreference t1, AccountPreference t2) {
    404                 return t1.mTitle.toString().compareTo(t2.mTitle.toString());
    405             }
    406         });
    407         return accountTypePreferences;
    408     }
    409 
    410     private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
    411             String accountType) {
    412         if (mAuthoritiesCount == 0) {
    413             // No authorities required
    414             return true;
    415         }
    416         final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
    417                 accountType);
    418         if (authoritiesForType == null) {
    419             Log.d(TAG, "No sync authorities for account type: " + accountType);
    420             return false;
    421         }
    422         for (int j = 0; j < mAuthoritiesCount; j++) {
    423             if (authoritiesForType.contains(mAuthorities[j])) {
    424                 return true;
    425             }
    426         }
    427         return false;
    428     }
    429 
    430     private class AccountPreference extends Preference implements OnPreferenceClickListener {
    431         /**
    432          * Title of the tile that is shown to the user.
    433          * @attr ref android.R.styleable#PreferenceHeader_title
    434          */
    435         private final CharSequence mTitle;
    436 
    437         /**
    438          * Full class name of the fragment to display when this tile is
    439          * selected.
    440          * @attr ref android.R.styleable#PreferenceHeader_fragment
    441          */
    442         private final String mFragment;
    443 
    444         /**
    445          * Optional arguments to supply to the fragment when it is
    446          * instantiated.
    447          */
    448         private final Bundle mFragmentArguments;
    449 
    450         public AccountPreference(Context context, CharSequence title, String fragment,
    451                 Bundle fragmentArguments, Drawable icon) {
    452             super(context);
    453             mTitle = title;
    454             mFragment = fragment;
    455             mFragmentArguments = fragmentArguments;
    456             setWidgetLayoutResource(R.layout.account_type_preference);
    457 
    458             setTitle(title);
    459             setIcon(icon);
    460 
    461             setOnPreferenceClickListener(this);
    462         }
    463 
    464         @Override
    465         public boolean onPreferenceClick(Preference preference) {
    466             if (mFragment != null) {
    467                 Utils.startWithFragment(
    468                         getContext(), mFragment, mFragmentArguments, null, 0, 0, mTitle);
    469                 return true;
    470             }
    471             return false;
    472         }
    473     }
    474 
    475     private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
    476         private boolean listeningToManagedProfileEvents;
    477 
    478         @Override
    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());
    483                 // Clean old state
    484                 stopListeningToAccountUpdates();
    485                 cleanUpPreferences();
    486                 // Build new state
    487                 updateUi();
    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();
    492                 return;
    493             }
    494             Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
    495         }
    496 
    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;
    504             }
    505         }
    506 
    507         public void unregister(Context context) {
    508             if (listeningToManagedProfileEvents) {
    509                 context.unregisterReceiver(this);
    510                 listeningToManagedProfileEvents = false;
    511             }
    512         }
    513     }
    514 
    515     private class MasterSyncStateClickListener implements MenuItem.OnMenuItemClickListener {
    516         private final UserHandle mUserHandle;
    517 
    518         public MasterSyncStateClickListener(UserHandle userHandle) {
    519             mUserHandle = userHandle;
    520         }
    521 
    522         @Override
    523         public boolean onMenuItemClick(MenuItem item) {
    524             if (ActivityManager.isUserAMonkey()) {
    525                 Log.d(TAG, "ignoring monkey's attempt to flip sync state");
    526             } else {
    527                 ConfirmAutoSyncChangeFragment.show(AccountSettings.this, !item.isChecked(),
    528                         mUserHandle);
    529             }
    530             return true;
    531         }
    532     }
    533 
    534     /**
    535      * Dialog to inform user about changing auto-sync setting
    536      */
    537     public static class ConfirmAutoSyncChangeFragment extends DialogFragment {
    538         private static final String SAVE_ENABLING = "enabling";
    539         private boolean mEnabling;
    540         private UserHandle mUserHandle;
    541 
    542         public static void show(AccountSettings parent, boolean enabling, UserHandle userHandle) {
    543             if (!parent.isAdded()) return;
    544 
    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);
    550         }
    551 
    552         @Override
    553         public Dialog onCreateDialog(Bundle savedInstanceState) {
    554             final Context context = getActivity();
    555             if (savedInstanceState != null) {
    556                 mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING);
    557             }
    558 
    559             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
    560             if (!mEnabling) {
    561                 builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title);
    562                 builder.setMessage(R.string.data_usage_auto_sync_off_dialog);
    563             } else {
    564                 builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title);
    565                 builder.setMessage(R.string.data_usage_auto_sync_on_dialog);
    566             }
    567 
    568             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
    569                 @Override
    570                 public void onClick(DialogInterface dialog, int which) {
    571                     ContentResolver.setMasterSyncAutomaticallyAsUser(mEnabling,
    572                             mUserHandle.getIdentifier());
    573                 }
    574             });
    575             builder.setNegativeButton(android.R.string.cancel, null);
    576 
    577             return builder.create();
    578         }
    579 
    580         @Override
    581         public void onSaveInstanceState(Bundle outState) {
    582             super.onSaveInstanceState(outState);
    583             outState.putBoolean(SAVE_ENABLING, mEnabling);
    584         }
    585     }
    586     // TODO Implement a {@link SearchIndexProvider} to allow Indexing and Search of account types
    587     // See http://b/15403806
    588 }
    589