OSDN Git Service

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