OSDN Git Service

resolve merge conflicts of 3964c51bf2 to nyc-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / accounts / ManageAccountsSettings.java
1 /*
2  * Copyright (C) 2008 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 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.accounts.AuthenticatorDescription;
22 import android.app.ActionBar;
23 import android.app.Activity;
24 import android.content.ContentResolver;
25 import android.content.Intent;
26 import android.content.SyncAdapterType;
27 import android.content.SyncInfo;
28 import android.content.SyncStatusInfo;
29 import android.content.pm.ActivityInfo;
30 import android.content.pm.ApplicationInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.ResolveInfo;
34 import android.graphics.drawable.Drawable;
35 import android.os.Bundle;
36 import android.os.UserHandle;
37 import android.support.v7.preference.Preference;
38 import android.support.v7.preference.Preference.OnPreferenceClickListener;
39 import android.support.v7.preference.PreferenceGroup;
40 import android.support.v7.preference.PreferenceScreen;
41 import android.util.Log;
42 import android.view.LayoutInflater;
43 import android.view.Menu;
44 import android.view.MenuInflater;
45 import android.view.MenuItem;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.widget.TextView;
49
50 import com.android.internal.logging.MetricsProto.MetricsEvent;
51 import com.android.settings.AccountPreference;
52 import com.android.settings.R;
53 import com.android.settings.SettingsActivity;
54 import com.android.settings.Utils;
55 import com.android.settings.location.LocationSettings;
56 import com.android.settingslib.accounts.AuthenticatorHelper;
57
58 import java.util.ArrayList;
59 import java.util.Date;
60 import java.util.HashSet;
61 import java.util.List;
62
63 import static android.content.Intent.EXTRA_USER;
64
65 /** Manages settings for Google Account. */
66 public class ManageAccountsSettings extends AccountPreferenceBase
67         implements AuthenticatorHelper.OnAccountsUpdateListener {
68     private static final String ACCOUNT_KEY = "account"; // to pass to auth settings
69     public static final String KEY_ACCOUNT_TYPE = "account_type";
70     public static final String KEY_ACCOUNT_LABEL = "account_label";
71
72     // Action name for the broadcast intent when the Google account preferences page is launching
73     // the location settings.
74     private static final String LAUNCHING_LOCATION_SETTINGS =
75             "com.android.settings.accounts.LAUNCHING_LOCATION_SETTINGS";
76
77     private static final int MENU_SYNC_NOW_ID = Menu.FIRST;
78     private static final int MENU_SYNC_CANCEL_ID    = Menu.FIRST + 1;
79
80     private static final int REQUEST_SHOW_SYNC_SETTINGS = 1;
81
82     private String[] mAuthorities;
83     private TextView mErrorInfoView;
84
85     // If an account type is set, then show only accounts of that type
86     private String mAccountType;
87     // Temporary hack, to deal with backward compatibility
88     // mFirstAccount is used for the injected preferences
89     private Account mFirstAccount;
90
91     @Override
92     protected int getMetricsCategory() {
93         return MetricsEvent.ACCOUNTS_MANAGE_ACCOUNTS;
94     }
95
96     @Override
97     public void onCreate(Bundle icicle) {
98         super.onCreate(icicle);
99
100         Bundle args = getArguments();
101         if (args != null && args.containsKey(KEY_ACCOUNT_TYPE)) {
102             mAccountType = args.getString(KEY_ACCOUNT_TYPE);
103         }
104         addPreferencesFromResource(R.xml.manage_accounts_settings);
105         setHasOptionsMenu(true);
106     }
107
108     @Override
109     public void onResume() {
110         super.onResume();
111         mAuthenticatorHelper.listenToAccountUpdates();
112         updateAuthDescriptions();
113         showAccountsIfNeeded();
114         showSyncState();
115     }
116
117     @Override
118     public View onCreateView(LayoutInflater inflater, ViewGroup container,
119             Bundle savedInstanceState) {
120         final View view = inflater.inflate(R.layout.manage_accounts_screen, container, false);
121         final ViewGroup prefs_container = (ViewGroup) view.findViewById(R.id.prefs_container);
122         Utils.prepareCustomPreferencesList(container, view, prefs_container, false);
123         View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState);
124         prefs_container.addView(prefs);
125         return view;
126     }
127
128     @Override
129     public void onActivityCreated(Bundle savedInstanceState) {
130         super.onActivityCreated(savedInstanceState);
131
132         final Activity activity = getActivity();
133         final View view = getView();
134
135         mErrorInfoView = (TextView)view.findViewById(R.id.sync_settings_error_info);
136         mErrorInfoView.setVisibility(View.GONE);
137
138         mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY);
139
140         Bundle args = getArguments();
141         if (args != null && args.containsKey(KEY_ACCOUNT_LABEL)) {
142             getActivity().setTitle(args.getString(KEY_ACCOUNT_LABEL));
143         }
144     }
145
146     @Override
147     public void onPause() {
148         super.onPause();
149         mAuthenticatorHelper.stopListeningToAccountUpdates();
150     }
151
152     @Override
153     public void onStop() {
154         super.onStop();
155         final Activity activity = getActivity();
156         activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM);
157         activity.getActionBar().setCustomView(null);
158     }
159
160     @Override
161     public boolean onPreferenceTreeClick(Preference preference) {
162         if (preference instanceof AccountPreference) {
163             startAccountSettings((AccountPreference) preference);
164         } else {
165             return false;
166         }
167         return true;
168     }
169
170     private void startAccountSettings(AccountPreference acctPref) {
171         Bundle args = new Bundle();
172         args.putParcelable(AccountSyncSettings.ACCOUNT_KEY, acctPref.getAccount());
173         args.putParcelable(EXTRA_USER, mUserHandle);
174         ((SettingsActivity) getActivity()).startPreferencePanel(
175                 AccountSyncSettings.class.getCanonicalName(), args,
176                 R.string.account_sync_settings_title, acctPref.getAccount().name,
177                 this, REQUEST_SHOW_SYNC_SETTINGS);
178     }
179
180     @Override
181     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
182         menu.add(0, MENU_SYNC_NOW_ID, 0, getString(R.string.sync_menu_sync_now))
183                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
184         menu.add(0, MENU_SYNC_CANCEL_ID, 0, getString(R.string.sync_menu_sync_cancel))
185                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
186         super.onCreateOptionsMenu(menu, inflater);
187     }
188
189     @Override
190     public void onPrepareOptionsMenu(Menu menu) {
191         super.onPrepareOptionsMenu(menu);
192         boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
193                 mUserHandle.getIdentifier()).isEmpty();
194         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
195         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
196     }
197
198     @Override
199     public boolean onOptionsItemSelected(MenuItem item) {
200         switch (item.getItemId()) {
201         case MENU_SYNC_NOW_ID:
202             requestOrCancelSyncForAccounts(true);
203             return true;
204         case MENU_SYNC_CANCEL_ID:
205             requestOrCancelSyncForAccounts(false);
206             return true;
207         }
208         return super.onOptionsItemSelected(item);
209     }
210
211     private void requestOrCancelSyncForAccounts(boolean sync) {
212         final int userId = mUserHandle.getIdentifier();
213         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
214         Bundle extras = new Bundle();
215         extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
216         int count = getPreferenceScreen().getPreferenceCount();
217         // For each account
218         for (int i = 0; i < count; i++) {
219             Preference pref = getPreferenceScreen().getPreference(i);
220             if (pref instanceof AccountPreference) {
221                 Account account = ((AccountPreference) pref).getAccount();
222                 // For all available sync authorities, sync those that are enabled for the account
223                 for (int j = 0; j < syncAdapters.length; j++) {
224                     SyncAdapterType sa = syncAdapters[j];
225                     if (syncAdapters[j].accountType.equals(mAccountType)
226                             && ContentResolver.getSyncAutomaticallyAsUser(account, sa.authority,
227                                     userId)) {
228                         if (sync) {
229                             ContentResolver.requestSyncAsUser(account, sa.authority, userId,
230                                     extras);
231                         } else {
232                             ContentResolver.cancelSyncAsUser(account, sa.authority, userId);
233                         }
234                     }
235                 }
236             }
237         }
238     }
239
240     @Override
241     protected void onSyncStateUpdated() {
242         showSyncState();
243         // Catch any delayed delivery of update messages
244         final Activity activity = getActivity();
245         if (activity != null) {
246             activity.invalidateOptionsMenu();
247         }
248     }
249
250     /**
251      * Shows the sync state of the accounts. Note: it must be called after the accounts have been
252      * loaded, @see #showAccountsIfNeeded().
253      */
254     private void showSyncState() {
255         // Catch any delayed delivery of update messages
256         if (getActivity() == null || getActivity().isFinishing()) return;
257
258         final int userId = mUserHandle.getIdentifier();
259
260         // iterate over all the preferences, setting the state properly for each
261         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
262
263         boolean anySyncFailed = false; // true if sync on any account failed
264         Date date = new Date();
265
266         // only track userfacing sync adapters when deciding if account is synced or not
267         final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
268         HashSet<String> userFacing = new HashSet<String>();
269         for (int k = 0, n = syncAdapters.length; k < n; k++) {
270             final SyncAdapterType sa = syncAdapters[k];
271             if (sa.isUserVisible()) {
272                 userFacing.add(sa.authority);
273             }
274         }
275         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
276             Preference pref = getPreferenceScreen().getPreference(i);
277             if (! (pref instanceof AccountPreference)) {
278                 continue;
279             }
280
281             AccountPreference accountPref = (AccountPreference) pref;
282             Account account = accountPref.getAccount();
283             int syncCount = 0;
284             long lastSuccessTime = 0;
285             boolean syncIsFailing = false;
286             final ArrayList<String> authorities = accountPref.getAuthorities();
287             boolean syncingNow = false;
288             if (authorities != null) {
289                 for (String authority : authorities) {
290                     SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority,
291                             userId);
292                     boolean syncEnabled = isSyncEnabled(userId, account, authority);
293                     boolean authorityIsPending = ContentResolver.isSyncPending(account, authority);
294                     boolean activelySyncing = isSyncing(currentSyncs, account, authority);
295                     boolean lastSyncFailed = status != null
296                             && syncEnabled
297                             && status.lastFailureTime != 0
298                             && status.getLastFailureMesgAsInt(0)
299                                != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
300                     if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
301                         syncIsFailing = true;
302                         anySyncFailed = true;
303                     }
304                     syncingNow |= activelySyncing;
305                     if (status != null && lastSuccessTime < status.lastSuccessTime) {
306                         lastSuccessTime = status.lastSuccessTime;
307                     }
308                     syncCount += syncEnabled && userFacing.contains(authority) ? 1 : 0;
309                 }
310             } else {
311                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
312                     Log.v(TAG, "no syncadapters found for " + account);
313                 }
314             }
315             if (syncIsFailing) {
316                 accountPref.setSyncStatus(AccountPreference.SYNC_ERROR, true);
317             } else if (syncCount == 0) {
318                 accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true);
319             } else if (syncCount > 0) {
320                 if (syncingNow) {
321                     accountPref.setSyncStatus(AccountPreference.SYNC_IN_PROGRESS, true);
322                 } else {
323                     accountPref.setSyncStatus(AccountPreference.SYNC_ENABLED, true);
324                     if (lastSuccessTime > 0) {
325                         accountPref.setSyncStatus(AccountPreference.SYNC_ENABLED, false);
326                         date.setTime(lastSuccessTime);
327                         final String timeString = formatSyncDate(date);
328                         accountPref.setSummary(getResources().getString(
329                                 R.string.last_synced, timeString));
330                     }
331                 }
332             } else {
333                 accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true);
334             }
335         }
336
337         mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE);
338     }
339
340
341     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
342         final int count = currentSyncs.size();
343         for (int i = 0; i < count;  i++) {
344             SyncInfo syncInfo = currentSyncs.get(i);
345             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
346                 return true;
347             }
348         }
349         return false;
350     }
351
352     private boolean isSyncEnabled(int userId, Account account, String authority) {
353         return ContentResolver.getSyncAutomaticallyAsUser(account, authority, userId)
354                 && ContentResolver.getMasterSyncAutomaticallyAsUser(userId)
355                 && (ContentResolver.getIsSyncableAsUser(account, authority, userId) > 0);
356     }
357
358     @Override
359     public void onAccountsUpdate(UserHandle userHandle) {
360         showAccountsIfNeeded();
361         onSyncStateUpdated();
362     }
363
364     private void showAccountsIfNeeded() {
365         if (getActivity() == null) return;
366         Account[] accounts = AccountManager.get(getActivity()).getAccountsAsUser(
367                 mUserHandle.getIdentifier());
368         getPreferenceScreen().removeAll();
369         mFirstAccount = null;
370         addPreferencesFromResource(R.xml.manage_accounts_settings);
371         for (int i = 0, n = accounts.length; i < n; i++) {
372             final Account account = accounts[i];
373             // If an account type is specified for this screen, skip other types
374             if (mAccountType != null && !account.type.equals(mAccountType)) continue;
375             final ArrayList<String> auths = getAuthoritiesForAccountType(account.type);
376
377             boolean showAccount = true;
378             if (mAuthorities != null && auths != null) {
379                 showAccount = false;
380                 for (String requestedAuthority : mAuthorities) {
381                     if (auths.contains(requestedAuthority)) {
382                         showAccount = true;
383                         break;
384                     }
385                 }
386             }
387
388             if (showAccount) {
389                 final Drawable icon = getDrawableForType(account.type);
390                 final AccountPreference preference =
391                         new AccountPreference(getPrefContext(), account, icon, auths, false);
392                 getPreferenceScreen().addPreference(preference);
393                 if (mFirstAccount == null) {
394                     mFirstAccount = account;
395                 }
396             }
397         }
398         if (mAccountType != null && mFirstAccount != null) {
399             addAuthenticatorSettings();
400         } else {
401             // There's no account, close activity
402             finish();
403         }
404     }
405
406     private void addAuthenticatorSettings() {
407         PreferenceScreen prefs = addPreferencesForType(mAccountType, getPreferenceScreen());
408         if (prefs != null) {
409             updatePreferenceIntents(prefs);
410         }
411     }
412
413     /** Listens to a preference click event and starts a fragment */
414     private class FragmentStarter
415             implements Preference.OnPreferenceClickListener {
416         private final String mClass;
417         private final int mTitleRes;
418
419         /**
420          * @param className the class name of the fragment to be started.
421          * @param title the title resource id of the started preference panel.
422          */
423         public FragmentStarter(String className, int title) {
424             mClass = className;
425             mTitleRes = title;
426         }
427
428         @Override
429         public boolean onPreferenceClick(Preference preference) {
430             ((SettingsActivity) getActivity()).startPreferencePanel(
431                     mClass, null, mTitleRes, null, null, 0);
432             // Hack: announce that the Google account preferences page is launching the location
433             // settings
434             if (mClass.equals(LocationSettings.class.getName())) {
435                 Intent intent = new Intent(LAUNCHING_LOCATION_SETTINGS);
436                 getActivity().sendBroadcast(
437                         intent, android.Manifest.permission.WRITE_SECURE_SETTINGS);
438             }
439             return true;
440         }
441     }
442
443     /**
444      * Recursively filters through the preference list provided by GoogleLoginService.
445      *
446      * This method removes all the invalid intent from the list, adds account name as extra into the
447      * intent, and hack the location settings to start it as a fragment.
448      */
449     private void updatePreferenceIntents(PreferenceGroup prefs) {
450         final PackageManager pm = getActivity().getPackageManager();
451         for (int i = 0; i < prefs.getPreferenceCount();) {
452             Preference pref = prefs.getPreference(i);
453             if (pref instanceof PreferenceGroup) {
454                 updatePreferenceIntents((PreferenceGroup) pref);
455             }
456             Intent intent = pref.getIntent();
457             if (intent != null) {
458                 // Hack. Launch "Location" as fragment instead of as activity.
459                 //
460                 // When "Location" is launched as activity via Intent, there's no "Up" button at the
461                 // top left, and if there's another running instance of "Location" activity, the
462                 // back stack would usually point to some other place so the user won't be able to
463                 // go back to the previous page by "back" key. Using fragment is a much easier
464                 // solution to those problems.
465                 //
466                 // If we set Intent to null and assign a fragment to the PreferenceScreen item here,
467                 // in order to make it work as expected, we still need to modify the container
468                 // PreferenceActivity, override onPreferenceStartFragment() and call
469                 // startPreferencePanel() there. In order to inject the title string there, more
470                 // dirty further hack is still needed. It's much easier and cleaner to listen to
471                 // preference click event here directly.
472                 if (intent.getAction().equals(
473                         android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)) {
474                     // The OnPreferenceClickListener overrides the click event completely. No intent
475                     // will get fired.
476                     pref.setOnPreferenceClickListener(new FragmentStarter(
477                             LocationSettings.class.getName(),
478                             R.string.location_settings_title));
479                 } else {
480                     ResolveInfo ri = pm.resolveActivityAsUser(intent,
481                             PackageManager.MATCH_DEFAULT_ONLY, mUserHandle.getIdentifier());
482                     if (ri == null) {
483                         prefs.removePreference(pref);
484                         continue;
485                     } else {
486                         intent.putExtra(ACCOUNT_KEY, mFirstAccount);
487                         intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
488                         pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
489                             @Override
490                             public boolean onPreferenceClick(Preference preference) {
491                                 Intent prefIntent = preference.getIntent();
492                                 /*
493                                  * Check the intent to see if it resolves to a exported=false
494                                  * activity that doesn't share a uid with the authenticator.
495                                  *
496                                  * Otherwise the intent is considered unsafe in that it will be
497                                  * exploiting the fact that settings has system privileges.
498                                  */
499                                 if (isSafeIntent(pm, prefIntent)) {
500                                     getActivity().startActivityAsUser(prefIntent, mUserHandle);
501                                 } else {
502                                     Log.e(TAG,
503                                             "Refusing to launch authenticator intent because"
504                                                     + " it exploits Settings permissions: "
505                                                     + prefIntent);
506                                 }
507                                 return true;
508                             }
509                         });
510                     }
511                 }
512             }
513             i++;
514         }
515     }
516
517     /**
518      * Determines if the supplied Intent is safe. A safe intent is one that is
519      * will launch a exported=true activity or owned by the same uid as the
520      * authenticator supplying the intent.
521      */
522     private boolean isSafeIntent(PackageManager pm, Intent intent) {
523         AuthenticatorDescription authDesc =
524                 mAuthenticatorHelper.getAccountTypeDescription(mAccountType);
525         ResolveInfo resolveInfo =
526             pm.resolveActivityAsUser(intent, 0, mUserHandle.getIdentifier());
527         if (resolveInfo == null) {
528             return false;
529         }
530         ActivityInfo resolvedActivityInfo = resolveInfo.activityInfo;
531         ApplicationInfo resolvedAppInfo = resolvedActivityInfo.applicationInfo;
532         try {
533             if (resolvedActivityInfo.exported) {
534                 if (resolvedActivityInfo.permission == null) {
535                     return true; // exported activity without permission.
536                 } else if (pm.checkPermission(resolvedActivityInfo.permission,
537                         authDesc.packageName) == PackageManager.PERMISSION_GRANTED) {
538                     return true;
539                 }
540             }
541             ApplicationInfo authenticatorAppInf = pm.getApplicationInfo(authDesc.packageName, 0);
542             return  resolvedAppInfo.uid == authenticatorAppInf.uid;
543         } catch (NameNotFoundException e) {
544             Log.e(TAG, "Intent considered unsafe due to exception.", e);
545             return false;
546         }
547     }
548
549     @Override
550     protected void onAuthDescriptionsUpdated() {
551         // Update account icons for all account preference items
552         for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
553             Preference pref = getPreferenceScreen().getPreference(i);
554             if (pref instanceof AccountPreference) {
555                 AccountPreference accPref = (AccountPreference) pref;
556                 accPref.setSummary(getLabelForType(accPref.getAccount().type));
557             }
558         }
559     }
560 }