Home | History | Annotate | Download | only in accounts
      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.OnAccountsUpdateListener;
     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.PackageManager;
     30 import android.content.pm.ResolveInfo;
     31 import android.graphics.drawable.Drawable;
     32 import android.os.Bundle;
     33 import android.preference.Preference;
     34 import android.preference.PreferenceActivity;
     35 import android.preference.PreferenceScreen;
     36 import android.util.Log;
     37 import android.view.LayoutInflater;
     38 import android.view.Menu;
     39 import android.view.MenuInflater;
     40 import android.view.MenuItem;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.widget.ListView;
     44 import android.widget.TextView;
     45 
     46 import com.android.settings.AccountPreference;
     47 import com.android.settings.R;
     48 import com.android.settings.Utils;
     49 import com.android.settings.location.LocationSettings;
     50 
     51 import java.util.ArrayList;
     52 import java.util.Date;
     53 import java.util.HashSet;
     54 
     55 /** Manages settings for Google Account. */
     56 public class ManageAccountsSettings extends AccountPreferenceBase
     57         implements OnAccountsUpdateListener {
     58 
     59     private static final String ACCOUNT_KEY = "account"; // to pass to auth settings
     60     public static final String KEY_ACCOUNT_TYPE = "account_type";
     61     public static final String KEY_ACCOUNT_LABEL = "account_label";
     62 
     63     private static final int MENU_SYNC_NOW_ID = Menu.FIRST;
     64     private static final int MENU_SYNC_CANCEL_ID    = Menu.FIRST + 1;
     65 
     66     private static final int REQUEST_SHOW_SYNC_SETTINGS = 1;
     67 
     68     private String[] mAuthorities;
     69     private TextView mErrorInfoView;
     70 
     71     // If an account type is set, then show only accounts of that type
     72     private String mAccountType;
     73     // Temporary hack, to deal with backward compatibility
     74     private Account mFirstAccount;
     75 
     76     @Override
     77     public void onCreate(Bundle icicle) {
     78         super.onCreate(icicle);
     79 
     80         Bundle args = getArguments();
     81         if (args != null && args.containsKey(KEY_ACCOUNT_TYPE)) {
     82             mAccountType = args.getString(KEY_ACCOUNT_TYPE);
     83         }
     84         addPreferencesFromResource(R.xml.manage_accounts_settings);
     85         setHasOptionsMenu(true);
     86     }
     87 
     88     @Override
     89     public void onStart() {
     90         super.onStart();
     91         Activity activity = getActivity();
     92         AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true);
     93     }
     94 
     95     @Override
     96     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     97             Bundle savedInstanceState) {
     98         final View view = inflater.inflate(R.layout.manage_accounts_screen, container, false);
     99         final ListView list = (ListView) view.findViewById(android.R.id.list);
    100         Utils.prepareCustomPreferencesList(container, view, list, false);
    101         return view;
    102     }
    103 
    104     @Override
    105     public void onActivityCreated(Bundle savedInstanceState) {
    106         super.onActivityCreated(savedInstanceState);
    107 
    108         final Activity activity = getActivity();
    109         final View view = getView();
    110 
    111         mErrorInfoView = (TextView)view.findViewById(R.id.sync_settings_error_info);
    112         mErrorInfoView.setVisibility(View.GONE);
    113 
    114         mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY);
    115 
    116         Bundle args = getArguments();
    117         if (args != null && args.containsKey(KEY_ACCOUNT_LABEL)) {
    118             getActivity().setTitle(args.getString(KEY_ACCOUNT_LABEL));
    119         }
    120         updateAuthDescriptions();
    121     }
    122 
    123     @Override
    124     public void onStop() {
    125         super.onStop();
    126         final Activity activity = getActivity();
    127         AccountManager.get(activity).removeOnAccountsUpdatedListener(this);
    128         activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM);
    129         activity.getActionBar().setCustomView(null);
    130     }
    131 
    132     @Override
    133     public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
    134         if (preference instanceof AccountPreference) {
    135             startAccountSettings((AccountPreference) preference);
    136         } else {
    137             return false;
    138         }
    139         return true;
    140     }
    141 
    142     private void startAccountSettings(AccountPreference acctPref) {
    143         Bundle args = new Bundle();
    144         args.putParcelable(AccountSyncSettings.ACCOUNT_KEY, acctPref.getAccount());
    145         ((PreferenceActivity) getActivity()).startPreferencePanel(
    146                 AccountSyncSettings.class.getCanonicalName(), args,
    147                 R.string.account_sync_settings_title, acctPref.getAccount().name,
    148                 this, REQUEST_SHOW_SYNC_SETTINGS);
    149     }
    150 
    151     @Override
    152     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    153         MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
    154                 getString(R.string.sync_menu_sync_now))
    155                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
    156         MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
    157                 getString(R.string.sync_menu_sync_cancel))
    158                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
    159         super.onCreateOptionsMenu(menu, inflater);
    160     }
    161 
    162     @Override
    163     public void onPrepareOptionsMenu(Menu menu) {
    164         super.onPrepareOptionsMenu(menu);
    165         boolean syncActive = ContentResolver.getCurrentSync() != null;
    166         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive && mFirstAccount != null);
    167         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive && mFirstAccount != null);
    168     }
    169 
    170     @Override
    171     public boolean onOptionsItemSelected(MenuItem item) {
    172         switch (item.getItemId()) {
    173         case MENU_SYNC_NOW_ID:
    174             requestOrCancelSyncForAccounts(true);
    175             return true;
    176         case MENU_SYNC_CANCEL_ID:
    177             requestOrCancelSyncForAccounts(false);
    178             return true;
    179         }
    180         return super.onOptionsItemSelected(item);
    181     }
    182 
    183     private void requestOrCancelSyncForAccounts(boolean sync) {
    184         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
    185         Bundle extras = new Bundle();
    186         extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    187         int count = getPreferenceScreen().getPreferenceCount();
    188         // For each account
    189         for (int i = 0; i < count; i++) {
    190             Preference pref = getPreferenceScreen().getPreference(i);
    191             if (pref instanceof AccountPreference) {
    192                 Account account = ((AccountPreference) pref).getAccount();
    193                 // For all available sync authorities, sync those that are enabled for the account
    194                 for (int j = 0; j < syncAdapters.length; j++) {
    195                     SyncAdapterType sa = syncAdapters[j];
    196                     if (syncAdapters[j].accountType.equals(mAccountType)
    197                             && ContentResolver.getSyncAutomatically(account, sa.authority)) {
    198                         if (sync) {
    199                             ContentResolver.requestSync(account, sa.authority, extras);
    200                         } else {
    201                             ContentResolver.cancelSync(account, sa.authority);
    202                         }
    203                     }
    204                 }
    205             }
    206         }
    207     }
    208 
    209     @Override
    210     protected void onSyncStateUpdated() {
    211         // Catch any delayed delivery of update messages
    212         if (getActivity() == null) return;
    213 
    214         // iterate over all the preferences, setting the state properly for each
    215         SyncInfo currentSync = ContentResolver.getCurrentSync();
    216 
    217         boolean anySyncFailed = false; // true if sync on any account failed
    218         Date date = new Date();
    219 
    220         // only track userfacing sync adapters when deciding if account is synced or not
    221         final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
    222         HashSet<String> userFacing = new HashSet<String>();
    223         for (int k = 0, n = syncAdapters.length; k < n; k++) {
    224             final SyncAdapterType sa = syncAdapters[k];
    225             if (sa.isUserVisible()) {
    226                 userFacing.add(sa.authority);
    227             }
    228         }
    229         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
    230             Preference pref = getPreferenceScreen().getPreference(i);
    231             if (! (pref instanceof AccountPreference)) {
    232                 continue;
    233             }
    234 
    235             AccountPreference accountPref = (AccountPreference) pref;
    236             Account account = accountPref.getAccount();
    237             int syncCount = 0;
    238             long lastSuccessTime = 0;
    239             boolean syncIsFailing = false;
    240             final ArrayList<String> authorities = accountPref.getAuthorities();
    241             boolean syncingNow = false;
    242             if (authorities != null) {
    243                 for (String authority : authorities) {
    244                     SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
    245                     boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority)
    246                             && ContentResolver.getMasterSyncAutomatically()
    247                             && (ContentResolver.getIsSyncable(account, authority) > 0);
    248                     boolean authorityIsPending = ContentResolver.isSyncPending(account, authority);
    249                     boolean activelySyncing = currentSync != null
    250                             && currentSync.authority.equals(authority)
    251                             && new Account(currentSync.account.name, currentSync.account.type)
    252                                     .equals(account);
    253                     boolean lastSyncFailed = status != null
    254                             && syncEnabled
    255                             && status.lastFailureTime != 0
    256                             && status.getLastFailureMesgAsInt(0)
    257                                != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
    258                     if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
    259                         syncIsFailing = true;
    260                         anySyncFailed = true;
    261                     }
    262                     syncingNow |= activelySyncing;
    263                     if (status != null && lastSuccessTime < status.lastSuccessTime) {
    264                         lastSuccessTime = status.lastSuccessTime;
    265                     }
    266                     syncCount += syncEnabled && userFacing.contains(authority) ? 1 : 0;
    267                 }
    268             } else {
    269                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    270                     Log.v(TAG, "no syncadapters found for " + account);
    271                 }
    272             }
    273             if (syncIsFailing) {
    274                 accountPref.setSyncStatus(AccountPreference.SYNC_ERROR, true);
    275             } else if (syncCount == 0) {
    276                 accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true);
    277             } else if (syncCount > 0) {
    278                 if (syncingNow) {
    279                     accountPref.setSyncStatus(AccountPreference.SYNC_IN_PROGRESS, true);
    280                 } else {
    281                     accountPref.setSyncStatus(AccountPreference.SYNC_ENABLED, true);
    282                     if (lastSuccessTime > 0) {
    283                         accountPref.setSyncStatus(AccountPreference.SYNC_ENABLED, false);
    284                         date.setTime(lastSuccessTime);
    285                         final String timeString = formatSyncDate(date);
    286                         accountPref.setSummary(getResources().getString(
    287                                 R.string.last_synced, timeString));
    288                     }
    289                 }
    290             } else {
    291                 accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true);
    292             }
    293         }
    294 
    295         mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE);
    296     }
    297 
    298     @Override
    299     public void onAccountsUpdated(Account[] accounts) {
    300         if (getActivity() == null) return;
    301         getPreferenceScreen().removeAll();
    302         mFirstAccount = null;
    303         addPreferencesFromResource(R.xml.manage_accounts_settings);
    304         for (int i = 0, n = accounts.length; i < n; i++) {
    305             final Account account = accounts[i];
    306             // If an account type is specified for this screen, skip other types
    307             if (mAccountType != null && !account.type.equals(mAccountType)) continue;
    308             final ArrayList<String> auths = getAuthoritiesForAccountType(account.type);
    309 
    310             boolean showAccount = true;
    311             if (mAuthorities != null && auths != null) {
    312                 showAccount = false;
    313                 for (String requestedAuthority : mAuthorities) {
    314                     if (auths.contains(requestedAuthority)) {
    315                         showAccount = true;
    316                         break;
    317                     }
    318                 }
    319             }
    320 
    321             if (showAccount) {
    322                 final Drawable icon = getDrawableForType(account.type);
    323                 final AccountPreference preference =
    324                         new AccountPreference(getActivity(), account, icon, auths, false);
    325                 getPreferenceScreen().addPreference(preference);
    326                 if (mFirstAccount == null) {
    327                     mFirstAccount = account;
    328                     getActivity().invalidateOptionsMenu();
    329                 }
    330             }
    331         }
    332         if (mAccountType != null && mFirstAccount != null) {
    333             addAuthenticatorSettings();
    334         } else {
    335             // There's no account, reset to top-level of settings
    336             Intent settingsTop = new Intent(android.provider.Settings.ACTION_SETTINGS);
    337             settingsTop.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    338             getActivity().startActivity(settingsTop);
    339         }
    340         onSyncStateUpdated();
    341     }
    342 
    343     private void addAuthenticatorSettings() {
    344         PreferenceScreen prefs = addPreferencesForType(mAccountType, getPreferenceScreen());
    345         if (prefs != null) {
    346             updatePreferenceIntents(prefs);
    347         }
    348     }
    349 
    350     /** Listens to a preference click event and starts a fragment */
    351     private class FragmentStarter
    352             implements Preference.OnPreferenceClickListener {
    353         private final String mClass;
    354         private final int mTitleRes;
    355 
    356         /**
    357          * @param className the class name of the fragment to be started.
    358          * @param title the title resource id of the started preference panel.
    359          */
    360         public FragmentStarter(String className, int title) {
    361             mClass = className;
    362             mTitleRes = title;
    363         }
    364 
    365         @Override
    366         public boolean onPreferenceClick(Preference preference) {
    367             ((PreferenceActivity) getActivity()).startPreferencePanel(
    368                     mClass, null, mTitleRes, null, null, 0);
    369             return true;
    370         }
    371     }
    372 
    373     /**
    374      * Filters through the preference list provided by GoogleLoginService.
    375      *
    376      * This method removes all the invalid intent from the list, adds account name as extra into the
    377      * intent, and hack the location settings to start it as a fragment.
    378      */
    379     private void updatePreferenceIntents(PreferenceScreen prefs) {
    380         PackageManager pm = getActivity().getPackageManager();
    381         for (int i = 0; i < prefs.getPreferenceCount();) {
    382             Preference pref = prefs.getPreference(i);
    383             Intent intent = pref.getIntent();
    384             if (intent != null) {
    385                 // Hack. Launch "Location" as fragment instead of as activity.
    386                 //
    387                 // When "Location" is launched as activity via Intent, there's no "Up" button at the
    388                 // top left, and if there's another running instance of "Location" activity, the
    389                 // back stack would usually point to some other place so the user won't be able to
    390                 // go back to the previous page by "back" key. Using fragment is a much easier
    391                 // solution to those problems.
    392                 //
    393                 // If we set Intent to null and assign a fragment to the PreferenceScreen item here,
    394                 // in order to make it work as expected, we still need to modify the container
    395                 // PreferenceActivity, override onPreferenceStartFragment() and call
    396                 // startPreferencePanel() there. In order to inject the title string there, more
    397                 // dirty further hack is still needed. It's much easier and cleaner to listen to
    398                 // preference click event here directly.
    399                 if (intent.getAction().equals(
    400                         android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)) {
    401                     // The OnPreferenceClickListener overrides the click event completely. No intent
    402                     // will get fired.
    403                     pref.setOnPreferenceClickListener(new FragmentStarter(
    404                             LocationSettings.class.getName(),
    405                             R.string.location_settings_title));
    406                 } else {
    407                     ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    408                     if (ri == null) {
    409                         prefs.removePreference(pref);
    410                         continue;
    411                     } else {
    412                         intent.putExtra(ACCOUNT_KEY, mFirstAccount);
    413                         intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
    414                     }
    415                 }
    416             }
    417             i++;
    418         }
    419     }
    420 
    421     @Override
    422     protected void onAuthDescriptionsUpdated() {
    423         // Update account icons for all account preference items
    424         for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
    425             Preference pref = getPreferenceScreen().getPreference(i);
    426             if (pref instanceof AccountPreference) {
    427                 AccountPreference accPref = (AccountPreference) pref;
    428                 accPref.setSummary(getLabelForType(accPref.getAccount().type));
    429             }
    430         }
    431     }
    432 }
    433