OSDN Git Service

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