OSDN Git Service

Import translations. DO NOT MERGE
[android-x86/packages-apps-Settings.git] / src / com / android / settings / accounts / AccountSettings.java
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 }