OSDN Git Service

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