Home | History | Annotate | Download | only in settings
      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;
     18 
     19 import com.android.providers.subscribedfeeds.R;
     20 import com.google.android.collect.Maps;
     21 import com.google.android.collect.Lists;
     22 
     23 import android.accounts.AccountManager;
     24 import android.accounts.Account;
     25 import android.accounts.AccountManagerCallback;
     26 import android.accounts.AccountManagerFuture;
     27 import android.accounts.AuthenticatorException;
     28 import android.accounts.OperationCanceledException;
     29 import android.app.AlertDialog;
     30 import android.app.Dialog;
     31 import android.content.SyncInfo;
     32 import android.content.ContentResolver;
     33 import android.content.Context;
     34 import android.content.DialogInterface;
     35 import android.content.Intent;
     36 import android.content.SyncStatusInfo;
     37 import android.content.SyncAdapterType;
     38 import android.content.pm.ProviderInfo;
     39 import android.net.ConnectivityManager;
     40 import android.os.Bundle;
     41 import android.text.TextUtils;
     42 import android.text.format.DateFormat;
     43 import android.preference.Preference;
     44 import android.preference.PreferenceScreen;
     45 import android.view.Menu;
     46 import android.view.MenuItem;
     47 import android.view.View;
     48 import android.view.View.OnClickListener;
     49 import android.widget.Button;
     50 import android.widget.ImageView;
     51 import android.widget.TextView;
     52 import android.util.Log;
     53 
     54 import java.io.IOException;
     55 import java.util.ArrayList;
     56 import java.util.Date;
     57 import java.util.HashMap;
     58 
     59 public class AccountSyncSettings extends AccountPreferenceBase implements OnClickListener {
     60     private static final String ACCOUNT_KEY = "account";
     61     private static final String TAG = "AccountSettings";
     62     private static final String CHANGE_PASSWORD_KEY = "changePassword";
     63     private static final int MENU_SYNC_NOW_ID = Menu.FIRST;
     64     private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 1;
     65     private static final int REALLY_REMOVE_DIALOG = 100;
     66     private static final int FAILED_REMOVAL_DIALOG = 101;
     67     private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
     68     private static final boolean LDEBUG = Log.isLoggable(TAG, Log.DEBUG);
     69     private TextView mUserId;
     70     private TextView mProviderId;
     71     private ImageView mProviderIcon;
     72     private TextView mErrorInfoView;
     73     protected View mRemoveAccountArea;
     74     private java.text.DateFormat mDateFormat;
     75     private java.text.DateFormat mTimeFormat;
     76     private Preference mAuthenticatorPreferences;
     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 Button mRemoveAccountButton;
     82     private ArrayList<SyncStateCheckBoxPreference> mCheckBoxes =
     83                 new ArrayList<SyncStateCheckBoxPreference>();
     84     private ArrayList<String> mInvisibleAdapters = Lists.newArrayList();
     85 
     86     public void onClick(View v) {
     87         if (v == mRemoveAccountButton) {
     88             showDialog(REALLY_REMOVE_DIALOG);
     89         }
     90     }
     91 
     92     @Override
     93     protected Dialog onCreateDialog(final int id) {
     94         Dialog dialog = null;
     95         if (id == REALLY_REMOVE_DIALOG) {
     96             dialog = new AlertDialog.Builder(this)
     97                 .setTitle(R.string.really_remove_account_title)
     98                 .setMessage(R.string.really_remove_account_message)
     99                 .setNegativeButton(android.R.string.cancel, null)
    100                 .setPositiveButton(R.string.remove_account_label,
    101                         new DialogInterface.OnClickListener() {
    102                     public void onClick(DialogInterface dialog, int which) {
    103                         AccountManager.get(AccountSyncSettings.this).removeAccount(mAccount,
    104                                 new AccountManagerCallback<Boolean>() {
    105                             public void run(AccountManagerFuture<Boolean> future) {
    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) {
    119                                     showDialog(FAILED_REMOVAL_DIALOG);
    120                                 } else {
    121                                     finish();
    122                                 }
    123                             }
    124                         }, null);
    125                     }
    126                 })
    127                 .create();
    128         } else if (id == FAILED_REMOVAL_DIALOG) {
    129             dialog = new AlertDialog.Builder(this)
    130                 .setTitle(R.string.really_remove_account_title)
    131                 .setPositiveButton(android.R.string.ok, null)
    132                 .setMessage(R.string.remove_account_failed)
    133                 .create();
    134         } else if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
    135             dialog = new AlertDialog.Builder(this)
    136                 .setTitle(R.string.cant_sync_dialog_title)
    137                 .setMessage(R.string.cant_sync_dialog_message)
    138                 .setPositiveButton(android.R.string.ok, null)
    139                 .create();
    140         }
    141         return dialog;
    142     }
    143 
    144     @Override
    145     public void onCreate(Bundle icicle) {
    146         super.onCreate(icicle);
    147 
    148         setContentView(R.layout.account_sync_screen);
    149         addPreferencesFromResource(R.xml.account_sync_settings);
    150 
    151         mErrorInfoView = (TextView) findViewById(R.id.sync_settings_error_info);
    152         mErrorInfoView.setVisibility(View.GONE);
    153         mErrorInfoView.setCompoundDrawablesWithIntrinsicBounds(
    154                 getResources().getDrawable(R.drawable.ic_list_syncerror), null, null, null);
    155 
    156         mUserId = (TextView) findViewById(R.id.user_id);
    157         mProviderId = (TextView) findViewById(R.id.provider_id);
    158         mProviderIcon = (ImageView) findViewById(R.id.provider_icon);
    159         mRemoveAccountArea = (View) findViewById(R.id.remove_account_area);
    160         mRemoveAccountButton = (Button) findViewById(R.id.remove_account_button);
    161         mRemoveAccountButton.setOnClickListener(this);
    162 
    163 
    164         mDateFormat = DateFormat.getDateFormat(this);
    165         mTimeFormat = DateFormat.getTimeFormat(this);
    166 
    167         mAccount = (Account) getIntent().getParcelableExtra(ACCOUNT_KEY);
    168         if (mAccount != null) {
    169             if (LDEBUG) Log.v(TAG, "Got account: " + mAccount);
    170             mUserId.setText(mAccount.name);
    171             mProviderId.setText(mAccount.type);
    172         }
    173         AccountManager.get(this).addOnAccountsUpdatedListener(this, null, false);
    174         updateAuthDescriptions();
    175         onAccountsUpdated(AccountManager.get(this).getAccounts());
    176     }
    177 
    178     @Override
    179     protected void onDestroy() {
    180         super.onDestroy();
    181         AccountManager.get(this).removeOnAccountsUpdatedListener(this);
    182     }
    183 
    184     private void addSyncStateCheckBox(Account account, String authority) {
    185         SyncStateCheckBoxPreference item =
    186                 new SyncStateCheckBoxPreference(this, account, authority);
    187         item.setPersistent(false);
    188         final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0);
    189         CharSequence providerLabel = providerInfo != null
    190                 ? providerInfo.loadLabel(getPackageManager()) : null;
    191         if (TextUtils.isEmpty(providerLabel)) {
    192             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
    193             providerLabel = authority;
    194         }
    195         String title = getString(R.string.sync_item_title, providerLabel);
    196         item.setTitle(title);
    197         item.setKey(authority);
    198         getPreferenceScreen().addPreference(item);
    199         mCheckBoxes.add(item);
    200     }
    201 
    202     @Override
    203     public boolean onCreateOptionsMenu(Menu menu) {
    204         super.onCreateOptionsMenu(menu);
    205         menu.add(0, MENU_SYNC_NOW_ID, 0, getString(R.string.sync_menu_sync_now))
    206                 .setIcon(com.android.internal.R.drawable.ic_menu_refresh);
    207         menu.add(0, MENU_SYNC_CANCEL_ID, 0, getString(R.string.sync_menu_sync_cancel))
    208                 .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
    209         return true;
    210     }
    211 
    212     @Override
    213     public boolean onPrepareOptionsMenu(Menu menu) {
    214         super.onPrepareOptionsMenu(menu);
    215         boolean syncActive = ContentResolver.getCurrentSync() != null;
    216         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
    217         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
    218         return true;
    219     }
    220 
    221     @Override
    222     public boolean onOptionsItemSelected(MenuItem item) {
    223         switch (item.getItemId()) {
    224             case MENU_SYNC_NOW_ID:
    225                 startSyncForEnabledProviders();
    226                 return true;
    227             case MENU_SYNC_CANCEL_ID:
    228                 cancelSyncForEnabledProviders();
    229                 return true;
    230         }
    231         return super.onOptionsItemSelected(item);
    232     }
    233 
    234     @Override
    235     public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
    236         if (preference instanceof SyncStateCheckBoxPreference) {
    237             SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) preference;
    238             String authority = syncPref.getAuthority();
    239             Account account = syncPref.getAccount();
    240             boolean syncAutomatically = ContentResolver.getSyncAutomatically(account, authority);
    241             if (syncPref.isOneTimeSyncMode()) {
    242                 requestOrCancelSync(account, authority, true);
    243             } else {
    244                 boolean syncOn = syncPref.isChecked();
    245                 boolean oldSyncState = syncAutomatically;
    246                 if (syncOn != oldSyncState) {
    247                     // if we're enabling sync, this will request a sync as well
    248                     ContentResolver.setSyncAutomatically(account, authority, syncOn);
    249                     // if the master sync switch is off, the request above will
    250                     // get dropped.  when the user clicks on this toggle,
    251                     // we want to force the sync, however.
    252                     if (!ContentResolver.getMasterSyncAutomatically() || !syncOn) {
    253                         requestOrCancelSync(account, authority, syncOn);
    254                     }
    255                 }
    256             }
    257             return true;
    258         } else {
    259             return super.onPreferenceTreeClick(preferences, preference);
    260         }
    261     }
    262 
    263     private void startSyncForEnabledProviders() {
    264         requestOrCancelSyncForEnabledProviders(true /* start them */);
    265     }
    266 
    267     private void cancelSyncForEnabledProviders() {
    268         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    269     }
    270 
    271     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
    272         // sync everything that the user has enabled
    273         int count = getPreferenceScreen().getPreferenceCount();
    274         for (int i = 0; i < count; i++) {
    275             Preference pref = getPreferenceScreen().getPreference(i);
    276             if (! (pref instanceof SyncStateCheckBoxPreference)) {
    277                 continue;
    278             }
    279             SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) pref;
    280             if (!syncPref.isChecked()) {
    281                 continue;
    282             }
    283             requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
    284         }
    285         // plus whatever the system needs to sync, e.g., invisible sync adapters
    286         if (mAccount != null) {
    287             for (String authority : mInvisibleAdapters) {
    288                 requestOrCancelSync(mAccount, authority, startSync);
    289             }
    290         }
    291     }
    292 
    293     private void requestOrCancelSync(Account account, String authority, boolean flag) {
    294         if (flag) {
    295             Bundle extras = new Bundle();
    296             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    297             ContentResolver.requestSync(account, authority, extras);
    298         } else {
    299             ContentResolver.cancelSync(account, authority);
    300         }
    301     }
    302 
    303     @Override
    304     protected void onSyncStateUpdated() {
    305         // iterate over all the preferences, setting the state properly for each
    306         Date date = new Date();
    307         SyncInfo currentSync = ContentResolver.getCurrentSync();
    308         boolean syncIsFailing = false;
    309 
    310         // Refresh the sync status checkboxes - some syncs may have become active.
    311         updateAccountCheckboxes(mAccounts);
    312 
    313         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
    314             Preference pref = getPreferenceScreen().getPreference(i);
    315             if (! (pref instanceof SyncStateCheckBoxPreference)) {
    316                 continue;
    317             }
    318             SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) pref;
    319 
    320             String authority = syncPref.getAuthority();
    321             Account account = syncPref.getAccount();
    322 
    323             SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
    324             boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
    325             boolean authorityIsPending = status == null ? false : status.pending;
    326             boolean initialSync = status == null ? false : status.initialize;
    327 
    328             boolean activelySyncing = currentSync != null
    329                     && new Account(currentSync.account.name, currentSync.account.type).equals(account)
    330                     && currentSync.authority.equals(authority);
    331             boolean lastSyncFailed = status != null
    332                     && status.lastFailureTime != 0
    333                     && status.getLastFailureMesgAsInt(0)
    334                        != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
    335             if (!syncEnabled) lastSyncFailed = false;
    336             if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
    337                 syncIsFailing = true;
    338             }
    339             if (LDEBUG) {
    340                 Log.d(TAG, "Update sync status: " + account + " " + authority +
    341                         " active = " + activelySyncing + " pend =" +  authorityIsPending);
    342             }
    343 
    344             final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
    345             if (successEndTime != 0) {
    346                 date.setTime(successEndTime);
    347                 final String timeString = mDateFormat.format(date) + " "
    348                         + mTimeFormat.format(date);
    349                 syncPref.setSummary(timeString);
    350             } else {
    351                 syncPref.setSummary("");
    352             }
    353             int syncState = ContentResolver.getIsSyncable(account, authority);
    354 
    355             syncPref.setActive(activelySyncing && (syncState >= 0) &&
    356                     !initialSync);
    357             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
    358                     !initialSync);
    359 
    360             syncPref.setFailed(lastSyncFailed);
    361             ConnectivityManager connManager =
    362                 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    363             final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
    364             final boolean backgroundDataEnabled = connManager.getBackgroundDataSetting();
    365             final boolean oneTimeSyncMode = !masterSyncAutomatically || !backgroundDataEnabled;
    366             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
    367             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
    368         }
    369         mErrorInfoView.setVisibility(syncIsFailing ? View.VISIBLE : View.GONE);
    370     }
    371 
    372     @Override
    373     public void onAccountsUpdated(Account[] accounts) {
    374         super.onAccountsUpdated(accounts);
    375         mAccounts = accounts;
    376         updateAccountCheckboxes(accounts);
    377         onSyncStateUpdated();
    378     }
    379 
    380     private void updateAccountCheckboxes(Account[] accounts) {
    381         mInvisibleAdapters.clear();
    382 
    383         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
    384         HashMap<String, ArrayList<String>> accountTypeToAuthorities =
    385             Maps.newHashMap();
    386         for (int i = 0, n = syncAdapters.length; i < n; i++) {
    387             final SyncAdapterType sa = syncAdapters[i];
    388             if (sa.isUserVisible()) {
    389                 ArrayList<String> authorities = accountTypeToAuthorities.get(sa.accountType);
    390                 if (authorities == null) {
    391                     authorities = new ArrayList<String>();
    392                     accountTypeToAuthorities.put(sa.accountType, authorities);
    393                 }
    394                 if (LDEBUG) {
    395                     Log.d(TAG, "onAccountUpdated: added authority " + sa.authority
    396                             + " to accountType " + sa.accountType);
    397                 }
    398                 authorities.add(sa.authority);
    399             } else {
    400                 // keep track of invisible sync adapters, so sync now forces
    401                 // them to sync as well.
    402                 mInvisibleAdapters.add(sa.authority);
    403             }
    404         }
    405 
    406         for (int i = 0, n = mCheckBoxes.size(); i < n; i++) {
    407             getPreferenceScreen().removePreference(mCheckBoxes.get(i));
    408         }
    409         mCheckBoxes.clear();
    410 
    411         for (int i = 0, n = accounts.length; i < n; i++) {
    412             final Account account = accounts[i];
    413             if (LDEBUG) Log.d(TAG, "looking for sync adapters that match account " + account);
    414             final ArrayList<String> authorities = accountTypeToAuthorities.get(account.type);
    415             if (authorities != null && (mAccount == null || mAccount.equals(account))) {
    416                 for (int j = 0, m = authorities.size(); j < m; j++) {
    417                     final String authority = authorities.get(j);
    418                     // We could check services here....
    419                     int syncState = ContentResolver.getIsSyncable(account, authority);
    420                     if (LDEBUG) Log.d(TAG, "  found authority " + authority + " " + syncState);
    421                     if (syncState > 0) {
    422                         addSyncStateCheckBox(account, authority);
    423                     }
    424                 }
    425             }
    426         }
    427     }
    428 
    429     /**
    430      * Updates the titlebar with an icon for the provider type.
    431      */
    432     @Override
    433     protected void onAuthDescriptionsUpdated() {
    434         super.onAuthDescriptionsUpdated();
    435         getPreferenceScreen().removeAll();
    436         mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
    437         mProviderId.setText(getLabelForType(mAccount.type));
    438         PreferenceScreen prefs = addPreferencesForType(mAccount.type);
    439         if (prefs != null) {
    440             updatePreferenceIntents(prefs);
    441         }
    442         addPreferencesFromResource(R.xml.account_sync_settings);
    443     }
    444 
    445     private void updatePreferenceIntents(PreferenceScreen prefs) {
    446         for (int i = 0; i < prefs.getPreferenceCount(); i++) {
    447             Intent intent = prefs.getPreference(i).getIntent();
    448             if (intent != null) {
    449                 intent.putExtra(ACCOUNT_KEY, mAccount);
    450                 // This is somewhat of a hack. Since the preference screen we're accessing comes
    451                 // from another package, we need to modify the intent to launch it with
    452                 // FLAG_ACTIVITY_NEW_TASK.
    453                 // TODO: Do something smarter if we ever have PreferenceScreens of our own.
    454                 intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
    455             }
    456         }
    457     }
    458 }
    459