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.AccountManagerCallback;
     22 import android.accounts.AccountManagerFuture;
     23 import android.accounts.AuthenticatorException;
     24 import android.accounts.OperationCanceledException;
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.app.Dialog;
     28 import android.content.ContentResolver;
     29 import android.content.Context;
     30 import android.content.DialogInterface;
     31 import android.content.SyncAdapterType;
     32 import android.content.SyncInfo;
     33 import android.content.SyncStatusInfo;
     34 import android.content.pm.ProviderInfo;
     35 import android.net.ConnectivityManager;
     36 import android.os.Bundle;
     37 import android.os.UserManager;
     38 import android.preference.Preference;
     39 import android.preference.PreferenceScreen;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.view.LayoutInflater;
     43 import android.view.Menu;
     44 import android.view.MenuInflater;
     45 import android.view.MenuItem;
     46 import android.view.View;
     47 import android.view.ViewGroup;
     48 import android.widget.ImageView;
     49 import android.widget.ListView;
     50 import android.widget.TextView;
     51 
     52 import com.android.settings.R;
     53 import com.android.settings.Utils;
     54 import com.google.android.collect.Lists;
     55 import com.google.android.collect.Maps;
     56 
     57 import java.io.IOException;
     58 import java.util.ArrayList;
     59 import java.util.Collections;
     60 import java.util.Date;
     61 import java.util.HashMap;
     62 import java.util.List;
     63 
     64 public class AccountSyncSettings extends AccountPreferenceBase {
     65 
     66     public static final String ACCOUNT_KEY = "account";
     67     private static final int MENU_SYNC_NOW_ID       = Menu.FIRST;
     68     private static final int MENU_SYNC_CANCEL_ID    = Menu.FIRST + 1;
     69     private static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST + 2;
     70     private static final int REALLY_REMOVE_DIALOG = 100;
     71     private static final int FAILED_REMOVAL_DIALOG = 101;
     72     private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
     73     private TextView mUserId;
     74     private TextView mProviderId;
     75     private ImageView mProviderIcon;
     76     private TextView mErrorInfoView;
     77     private Account mAccount;
     78     // List of all accounts, updated when accounts are added/removed
     79     // We need to re-scan the accounts on sync events, in case sync state changes.
     80     private Account[] mAccounts;
     81     private ArrayList<SyncStateCheckBoxPreference> mCheckBoxes =
     82                 new ArrayList<SyncStateCheckBoxPreference>();
     83     private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
     84 
     85     @Override
     86     public Dialog onCreateDialog(final int id) {
     87         Dialog dialog = null;
     88         if (id == REALLY_REMOVE_DIALOG) {
     89             dialog = new AlertDialog.Builder(getActivity())
     90                 .setTitle(R.string.really_remove_account_title)
     91                 .setMessage(R.string.really_remove_account_message)
     92                 .setNegativeButton(android.R.string.cancel, null)
     93                 .setPositiveButton(R.string.remove_account_label,
     94                         new DialogInterface.OnClickListener() {
     95                     @Override
     96                     public void onClick(DialogInterface dialog, int which) {
     97                         AccountManager.get(AccountSyncSettings.this.getActivity())
     98                                 .removeAccount(mAccount,
     99                                 new AccountManagerCallback<Boolean>() {
    100                             @Override
    101                             public void run(AccountManagerFuture<Boolean> future) {
    102                                 // If already out of this screen, don't proceed.
    103                                 if (!AccountSyncSettings.this.isResumed()) {
    104                                     return;
    105                                 }
    106                                 boolean failed = true;
    107                                 try {
    108                                     if (future.getResult() == true) {
    109                                         failed = false;
    110                                     }
    111                                 } catch (OperationCanceledException e) {
    112                                     // handled below
    113                                 } catch (IOException e) {
    114                                     // handled below
    115                                 } catch (AuthenticatorException e) {
    116                                     // handled below
    117                                 }
    118                                 if (failed && getActivity() != null &&
    119                                         !getActivity().isFinishing()) {
    120                                     showDialog(FAILED_REMOVAL_DIALOG);
    121                                 } else {
    122                                     finish();
    123                                 }
    124                             }
    125                         }, null);
    126                     }
    127                 })
    128                 .create();
    129         } else if (id == FAILED_REMOVAL_DIALOG) {
    130             dialog = new AlertDialog.Builder(getActivity())
    131                 .setTitle(R.string.really_remove_account_title)
    132                 .setPositiveButton(android.R.string.ok, null)
    133                 .setMessage(R.string.remove_account_failed)
    134                 .create();
    135         } else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
    136             dialog = new AlertDialog.Builder(getActivity())
    137                 .setTitle(R.string.cant_sync_dialog_title)
    138                 .setMessage(R.string.cant_sync_dialog_message)
    139                 .setPositiveButton(android.R.string.ok, null)
    140                 .create();
    141         }
    142         return dialog;
    143     }
    144 
    145     @Override
    146     public void onCreate(Bundle icicle) {
    147         super.onCreate(icicle);
    148 
    149         setHasOptionsMenu(true);
    150     }
    151 
    152     @Override
    153     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    154             Bundle savedInstanceState) {
    155         final View view = inflater.inflate(R.layout.account_sync_screen, container, false);
    156 
    157         final ListView list = (ListView) view.findViewById(android.R.id.list);
    158         Utils.prepareCustomPreferencesList(container, view, list, false);
    159 
    160         initializeUi(view);
    161 
    162         return view;
    163     }
    164 
    165     protected void initializeUi(final View rootView) {
    166         addPreferencesFromResource(R.xml.account_sync_settings);
    167 
    168         mErrorInfoView = (TextView) rootView.findViewById(R.id.sync_settings_error_info);
    169         mErrorInfoView.setVisibility(View.GONE);
    170 
    171         mUserId = (TextView) rootView.findViewById(R.id.user_id);
    172         mProviderId = (TextView) rootView.findViewById(R.id.provider_id);
    173         mProviderIcon = (ImageView) rootView.findViewById(R.id.provider_icon);
    174     }
    175 
    176     @Override
    177     public void onActivityCreated(Bundle savedInstanceState) {
    178         super.onActivityCreated(savedInstanceState);
    179 
    180         Bundle arguments = getArguments();
    181         if (arguments == null) {
    182             Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
    183             return;
    184         }
    185 
    186         mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
    187         if (mAccount != null) {
    188             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Got account: " + mAccount);
    189             mUserId.setText(mAccount.name);
    190             mProviderId.setText(mAccount.type);
    191         }
    192     }
    193 
    194     @Override
    195     public void onResume() {
    196         final Activity activity = getActivity();
    197         AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, false);
    198         updateAuthDescriptions();
    199         onAccountsUpdated(AccountManager.get(activity).getAccounts());
    200 
    201         super.onResume();
    202     }
    203 
    204     @Override
    205     public void onPause() {
    206         super.onPause();
    207         AccountManager.get(getActivity()).removeOnAccountsUpdatedListener(this);
    208     }
    209 
    210     private void addSyncStateCheckBox(Account account, String authority) {
    211         SyncStateCheckBoxPreference item =
    212                 new SyncStateCheckBoxPreference(getActivity(), account, authority);
    213         item.setPersistent(false);
    214         final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0);
    215         if (providerInfo == null) {
    216             return;
    217         }
    218         CharSequence providerLabel = providerInfo.loadLabel(getPackageManager());
    219         if (TextUtils.isEmpty(providerLabel)) {
    220             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
    221             return;
    222         }
    223         String title = getString(R.string.sync_item_title, providerLabel);
    224         item.setTitle(title);
    225         item.setKey(authority);
    226         mCheckBoxes.add(item);
    227     }
    228 
    229     @Override
    230     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    231 
    232         MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
    233                 getString(R.string.sync_menu_sync_now))
    234                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
    235         MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
    236                 getString(R.string.sync_menu_sync_cancel))
    237                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
    238 
    239         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
    240         if (!um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
    241             MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
    242                     getString(R.string.remove_account_label))
    243                     .setIcon(R.drawable.ic_menu_delete_holo_dark);
    244             removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    245                     MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    246         }
    247         syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    248                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    249         syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
    250                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    251 
    252         super.onCreateOptionsMenu(menu, inflater);
    253     }
    254 
    255     @Override
    256     public void onPrepareOptionsMenu(Menu menu) {
    257         super.onPrepareOptionsMenu(menu);
    258         boolean syncActive = ContentResolver.getCurrentSync() != null;
    259         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
    260         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
    261     }
    262 
    263     @Override
    264     public boolean onOptionsItemSelected(MenuItem item) {
    265         switch (item.getItemId()) {
    266             case MENU_SYNC_NOW_ID:
    267                 startSyncForEnabledProviders();
    268                 return true;
    269             case MENU_SYNC_CANCEL_ID:
    270                 cancelSyncForEnabledProviders();
    271                 return true;
    272             case MENU_REMOVE_ACCOUNT_ID:
    273                 showDialog(REALLY_REMOVE_DIALOG);
    274                 return true;
    275         }
    276         return super.onOptionsItemSelected(item);
    277     }
    278 
    279     @Override
    280     public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
    281         if (preference instanceof SyncStateCheckBoxPreference) {
    282             SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) preference;
    283             String authority = syncPref.getAuthority();
    284             Account account = syncPref.getAccount();
    285             boolean syncAutomatically = ContentResolver.getSyncAutomatically(account, authority);
    286             if (syncPref.isOneTimeSyncMode()) {
    287                 requestOrCancelSync(account, authority, true);
    288             } else {
    289                 boolean syncOn = syncPref.isChecked();
    290                 boolean oldSyncState = syncAutomatically;
    291                 if (syncOn != oldSyncState) {
    292                     // if we're enabling sync, this will request a sync as well
    293                     ContentResolver.setSyncAutomatically(account, authority, syncOn);
    294                     // if the master sync switch is off, the request above will
    295                     // get dropped.  when the user clicks on this toggle,
    296                     // we want to force the sync, however.
    297                     if (!ContentResolver.getMasterSyncAutomatically() || !syncOn) {
    298                         requestOrCancelSync(account, authority, syncOn);
    299                     }
    300                 }
    301             }
    302             return true;
    303         } else {
    304             return super.onPreferenceTreeClick(preferences, preference);
    305         }
    306     }
    307 
    308     private void startSyncForEnabledProviders() {
    309         requestOrCancelSyncForEnabledProviders(true /* start them */);
    310         getActivity().invalidateOptionsMenu();
    311     }
    312 
    313     private void cancelSyncForEnabledProviders() {
    314         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    315         getActivity().invalidateOptionsMenu();
    316     }
    317 
    318     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
    319         // sync everything that the user has enabled
    320         int count = getPreferenceScreen().getPreferenceCount();
    321         for (int i = 0; i < count; i++) {
    322             Preference pref = getPreferenceScreen().getPreference(i);
    323             if (! (pref instanceof SyncStateCheckBoxPreference)) {
    324                 continue;
    325             }
    326             SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) pref;
    327             if (!syncPref.isChecked()) {
    328                 continue;
    329             }
    330             requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
    331         }
    332         // plus whatever the system needs to sync, e.g., invisible sync adapters
    333         if (mAccount != null) {
    334             for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
    335                 // invisible sync adapters' account type should be same as current account type
    336                 if (syncAdapter.accountType.equals(mAccount.type)) {
    337                     requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
    338                 }
    339             }
    340         }
    341     }
    342 
    343     private void requestOrCancelSync(Account account, String authority, boolean flag) {
    344         if (flag) {
    345             Bundle extras = new Bundle();
    346             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    347             ContentResolver.requestSync(account, authority, extras);
    348         } else {
    349             ContentResolver.cancelSync(account, authority);
    350         }
    351     }
    352 
    353     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
    354         for (SyncInfo syncInfo : currentSyncs) {
    355             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
    356                 return true;
    357             }
    358         }
    359         return false;
    360     }
    361 
    362     @Override
    363     protected void onSyncStateUpdated() {
    364         if (!isResumed()) return;
    365         setFeedsState();
    366     }
    367 
    368     private void setFeedsState() {
    369         // iterate over all the preferences, setting the state properly for each
    370         Date date = new Date();
    371         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncs();
    372         boolean syncIsFailing = false;
    373 
    374         // Refresh the sync status checkboxes - some syncs may have become active.
    375         updateAccountCheckboxes(mAccounts);
    376 
    377         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
    378             Preference pref = getPreferenceScreen().getPreference(i);
    379             if (! (pref instanceof SyncStateCheckBoxPreference)) {
    380                 continue;
    381             }
    382             SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) pref;
    383 
    384             String authority = syncPref.getAuthority();
    385             Account account = syncPref.getAccount();
    386 
    387             SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
    388             boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
    389             boolean authorityIsPending = status == null ? false : status.pending;
    390             boolean initialSync = status == null ? false : status.initialize;
    391 
    392             boolean activelySyncing = isSyncing(currentSyncs, account, authority);
    393             boolean lastSyncFailed = status != null
    394                     && status.lastFailureTime != 0
    395                     && status.getLastFailureMesgAsInt(0)
    396                        != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
    397             if (!syncEnabled) lastSyncFailed = false;
    398             if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
    399                 syncIsFailing = true;
    400             }
    401             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    402                 Log.d(TAG, "Update sync status: " + account + " " + authority +
    403                         " active = " + activelySyncing + " pend =" +  authorityIsPending);
    404             }
    405 
    406             final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
    407             if (!syncEnabled) {
    408                 syncPref.setSummary(R.string.sync_disabled);
    409             } else if (activelySyncing) {
    410                 syncPref.setSummary(R.string.sync_in_progress);
    411             } else if (successEndTime != 0) {
    412                 date.setTime(successEndTime);
    413                 final String timeString = formatSyncDate(date);
    414                 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString));
    415             } else {
    416                 syncPref.setSummary("");
    417             }
    418             int syncState = ContentResolver.getIsSyncable(account, authority);
    419 
    420             syncPref.setActive(activelySyncing && (syncState >= 0) &&
    421                     !initialSync);
    422             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
    423                     !initialSync);
    424 
    425             syncPref.setFailed(lastSyncFailed);
    426             ConnectivityManager connManager =
    427                 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    428             final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
    429             final boolean backgroundDataEnabled = connManager.getBackgroundDataSetting();
    430             final boolean oneTimeSyncMode = !masterSyncAutomatically || !backgroundDataEnabled;
    431             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
    432             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
    433         }
    434         mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
    435         getActivity().invalidateOptionsMenu();
    436     }
    437 
    438     @Override
    439     public void onAccountsUpdated(Account[] accounts) {
    440         super.onAccountsUpdated(accounts);
    441         mAccounts = accounts;
    442         updateAccountCheckboxes(accounts);
    443         onSyncStateUpdated();
    444     }
    445 
    446     private void updateAccountCheckboxes(Account[] accounts) {
    447         mInvisibleAdapters.clear();
    448 
    449         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
    450         HashMap<String, ArrayList<String>> accountTypeToAuthorities =
    451             Maps.newHashMap();
    452         for (int i = 0, n = syncAdapters.length; i < n; i++) {
    453             final SyncAdapterType sa = syncAdapters[i];
    454             if (sa.isUserVisible()) {
    455                 ArrayList<String> authorities = accountTypeToAuthorities.get(sa.accountType);
    456                 if (authorities == null) {
    457                     authorities = new ArrayList<String>();
    458                     accountTypeToAuthorities.put(sa.accountType, authorities);
    459                 }
    460                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    461                     Log.d(TAG, "onAccountUpdated: added authority " + sa.authority
    462                             + " to accountType " + sa.accountType);
    463                 }
    464                 authorities.add(sa.authority);
    465             } else {
    466                 // keep track of invisible sync adapters, so sync now forces
    467                 // them to sync as well.
    468                 mInvisibleAdapters.add(sa);
    469             }
    470         }
    471 
    472         for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
    473             getPreferenceScreen().removePreference(mCheckBoxes.get(i));
    474         }
    475         mCheckBoxes.clear();
    476 
    477         for (int i = 0, n = accounts.length; i < n; i++) {
    478             final Account account = accounts[i];
    479             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    480                 Log.d(TAG, "looking for sync adapters that match account " + account);
    481             }
    482             final ArrayList<String> authorities = accountTypeToAuthorities.get(account.type);
    483             if (authorities != null && (mAccount == null || mAccount.equals(account))) {
    484                 for (int j = 0, m = authorities.size(); j < m; j++) {
    485                     final String authority = authorities.get(j);
    486                     // We could check services here....
    487                     int syncState = ContentResolver.getIsSyncable(account, authority);
    488                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    489                         Log.d(TAG, "  found authority " + authority + " " + syncState);
    490                     }
    491                     if (syncState > 0) {
    492                         addSyncStateCheckBox(account, authority);
    493                     }
    494                 }
    495             }
    496         }
    497 
    498         Collections.sort(mCheckBoxes);
    499         for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
    500             getPreferenceScreen().addPreference(mCheckBoxes.get(i));
    501         }
    502     }
    503 
    504     /**
    505      * Updates the titlebar with an icon for the provider type.
    506      */
    507     @Override
    508     protected void onAuthDescriptionsUpdated() {
    509         super.onAuthDescriptionsUpdated();
    510         getPreferenceScreen().removeAll();
    511         if (mAccount != null) {
    512             mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
    513             mProviderId.setText(getLabelForType(mAccount.type));
    514         }
    515         addPreferencesFromResource(R.xml.account_sync_settings);
    516     }
    517 
    518     @Override
    519     protected int getHelpResource() {
    520         return R.string.help_url_accounts;
    521     }
    522 }
    523