Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2014 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.tv.settings.accounts;
     18 
     19 import com.android.tv.settings.R;
     20 import com.android.tv.settings.dialog.old.Action;
     21 import com.android.tv.settings.dialog.old.ActionAdapter;
     22 import com.android.tv.settings.dialog.old.ActionFragment;
     23 import com.android.tv.settings.dialog.old.ContentFragment;
     24 import com.android.tv.settings.dialog.old.DialogActivity;
     25 
     26 import android.accounts.Account;
     27 import android.accounts.AccountManager;
     28 import android.accounts.OnAccountsUpdateListener;
     29 import android.content.ContentResolver;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.SyncAdapterType;
     33 import android.content.SyncInfo;
     34 import android.content.SyncStatusInfo;
     35 import android.content.SyncStatusObserver;
     36 import android.content.pm.ProviderInfo;
     37 import android.net.ConnectivityManager;
     38 import android.os.AsyncTask;
     39 import android.os.Bundle;
     40 import android.preference.Preference;
     41 import android.text.TextUtils;
     42 import android.text.format.DateUtils;
     43 import android.util.Log;
     44 
     45 import java.util.ArrayList;
     46 import java.util.Collections;
     47 import java.util.Comparator;
     48 import java.util.Date;
     49 import java.util.HashMap;
     50 import java.util.List;
     51 
     52 /**
     53  * Displays the sync settings for a given account.
     54  */
     55 public class AccountSyncSettings extends DialogActivity implements OnAccountsUpdateListener {
     56 
     57     private static final String TAG = "AccountSyncSettings";
     58 
     59     private static final String KEY_SYNC_NOW = "KEY_SYNC_NOW";
     60     private static final String KEY_CANCEL_SYNC = "KEY_SYNC_CANCEL";
     61 
     62     private static final String EXTRA_ONE_TIME_SYNC = "one_time_sync";
     63     private static final String EXTRA_ACCOUNT = "account";
     64 
     65     private AccountManager mAccountManager;
     66     private Account mAccount;
     67     private AuthenticatorHelper mHelper;
     68 
     69     /**
     70      * Adapters which are invisible. Store them so that sync now syncs everything.
     71      */
     72     private List<SyncAdapterType> mInvisibleAdapters;
     73 
     74     private Account[] mAccounts;
     75 
     76     private boolean mSyncIsFailing;
     77 
     78     private Object mStatusChangeListenerHandle;
     79 
     80     private ActionFragment mActionFragment;
     81 
     82     private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
     83 
     84         @Override
     85         public void onStatusChanged(int which) {
     86             onSyncStateUpdated();
     87         }
     88     };
     89 
     90     @Override
     91     protected void onCreate(Bundle savedInstanceState) {
     92         super.onCreate(savedInstanceState);
     93         mHelper = new AuthenticatorHelper();
     94         mAccountManager = AccountManager.get(this);
     95         mInvisibleAdapters = new ArrayList<SyncAdapterType>();
     96         String accountName = getIntent().getStringExtra(AccountSettingsActivity.EXTRA_ACCOUNT);
     97         if (!TextUtils.isEmpty(accountName)) {
     98             // Search for the account.
     99             for (Account account : mAccountManager.getAccounts()) {
    100                 if (account.name.equals(accountName)) {
    101                     mAccount = account;
    102                     break;
    103                 }
    104             }
    105         }
    106         if (mAccount == null) {
    107             finish();
    108             return;
    109         }
    110         mActionFragment = ActionFragment.newInstance(getActions(mAccountManager.getAccounts()));
    111         // Start with an empty list and then fill in with the sync adapters.
    112         setContentAndActionFragments(ContentFragment.newInstance(
    113                 accountName, mAccount.type, "", R.drawable.ic_settings_sync,
    114                 getResources().getColor(R.color.icon_background)), mActionFragment);
    115     }
    116 
    117     @Override
    118     protected void onResume() {
    119         super.onResume();
    120         AccountManager.get(this).addOnAccountsUpdatedListener(this, null, false);
    121         updateAuthDescriptions();
    122         onAccountsUpdated(AccountManager.get(this).getAccounts());
    123         mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
    124                 ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
    125                 | ContentResolver.SYNC_OBSERVER_TYPE_STATUS
    126                 | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
    127     }
    128 
    129     @Override
    130     public void onPause() {
    131         ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
    132         AccountManager.get(this).removeOnAccountsUpdatedListener(this);
    133         super.onPause();
    134     }
    135 
    136     @Override
    137     public void onAccountsUpdated(Account[] accounts) {
    138         mAccounts = accounts;
    139         loadSyncActions(accounts);
    140     }
    141 
    142     @Override
    143     public void onActionClicked(Action action) {
    144         String key = action.getKey();
    145         if (KEY_SYNC_NOW.equals(key)) {
    146             startSyncForEnabledProviders();
    147         } else if (KEY_CANCEL_SYNC.equals(key)) {
    148             cancelSyncForEnabledProviders();
    149         } else {
    150             // This is a specific sync adapter.
    151             Account account = action.getIntent().getParcelableExtra(EXTRA_ACCOUNT);
    152             String authority = action.getKey();
    153             boolean syncAutomatically = ContentResolver.getSyncAutomatically(account, authority);
    154             if (action.getIntent().getBooleanExtra(EXTRA_ONE_TIME_SYNC, false)) {
    155                 requestOrCancelSync(account, authority, true);
    156             } else {
    157                 boolean syncOn = !action.isChecked(); // toggle
    158                 boolean oldSyncState = syncAutomatically;
    159                 if (syncOn != oldSyncState) {
    160                     // if we're enabling sync, this will request a sync as well
    161                     ContentResolver.setSyncAutomatically(account, authority, syncOn);
    162                     // if the master sync switch is off, the request above will
    163                     // get dropped.  when the user clicks on this toggle,
    164                     // we want to force the sync, however.
    165                     if (!ContentResolver.getMasterSyncAutomatically() || !syncOn) {
    166                         requestOrCancelSync(account, authority, syncOn);
    167                     }
    168                 }
    169                 if (mAccounts != null) {
    170                     loadSyncActions(mAccounts);
    171                 }
    172             }
    173         }
    174     }
    175 
    176     private void onSyncStateUpdated() {
    177         if (!isResumed() || mAccounts == null) {
    178             return;
    179         }
    180         loadSyncActions(mAccounts);
    181     }
    182 
    183     private void updateAuthDescriptions() {
    184         mHelper.updateAuthDescriptions(this);
    185         onAuthDescriptionsUpdated();
    186     }
    187 
    188     private void onAuthDescriptionsUpdated() {
    189         ((ContentFragment) getContentFragment()).setBreadCrumbText(
    190                 mHelper.getLabelForType(this, mAccount.type).toString());
    191     }
    192 
    193 
    194     private void loadSyncActions(Account[] accounts) {
    195         new LoadActionsTask().execute(accounts);
    196     }
    197 
    198     private ArrayList<Action> getActions(Account[] accounts) {
    199         ArrayList<Action> actions = new ArrayList<Action>();
    200 
    201         Date date = new Date();
    202         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncs();
    203         mSyncIsFailing = false;
    204 
    205         mInvisibleAdapters.clear();
    206 
    207         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
    208         HashMap<String, ArrayList<String>> accountTypeToAuthorities =
    209                 new HashMap<String, ArrayList<String>>();
    210         for (int i = 0, n = syncAdapters.length; i < n; i++) {
    211             final SyncAdapterType sa = syncAdapters[i];
    212             if (sa.isUserVisible()) {
    213                 ArrayList<String> authorities = accountTypeToAuthorities.get(sa.accountType);
    214                 if (authorities == null) {
    215                     authorities = new ArrayList<String>();
    216                     accountTypeToAuthorities.put(sa.accountType, authorities);
    217                 }
    218                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    219                     Log.d(TAG, "onAccountUpdated: added authority " + sa.authority
    220                             + " to accountType " + sa.accountType);
    221                 }
    222                 authorities.add(sa.authority);
    223             } else {
    224                 // keep track of invisible sync adapters, so sync now forces
    225                 // them to sync as well.
    226                 mInvisibleAdapters.add(sa);
    227             }
    228         }
    229 
    230         for (int i = 0, n = accounts.length; i < n; i++) {
    231             final Account account = accounts[i];
    232             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    233                 Log.d(TAG, "looking for sync adapters that match account " + account);
    234             }
    235             final ArrayList<String> authorities = accountTypeToAuthorities.get(account.type);
    236             if (authorities != null && (mAccount == null || mAccount.equals(account))) {
    237                 for (int j = 0, m = authorities.size(); j < m; j++) {
    238                     final String authority = authorities.get(j);
    239                     // We could check services here....
    240                     int syncState = ContentResolver.getIsSyncable(account, authority);
    241                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    242                         Log.d(TAG, "  found authority " + authority + " " + syncState);
    243                     }
    244                     if (syncState > 0) {
    245                         Action action = getAction(account, authority, currentSyncs);
    246                         if (action != null) {
    247                             actions.add(action);
    248                         }
    249                     }
    250                 }
    251             }
    252         }
    253 
    254         Collections.sort(actions, ADAPTER_COMPARATOR);
    255         // Always add a "Sync now | cancel sync" action at the beginning.
    256         boolean syncActive = false;
    257         List<SyncInfo> syncList = ContentResolver.getCurrentSyncs();
    258         for (SyncInfo info : syncList) {
    259             if (mAccount.equals(info.account)) {
    260                 syncActive = true;
    261                 break;
    262             }
    263         }
    264 
    265         actions.add(0, new Action.Builder()
    266                     .key(!syncActive ? KEY_SYNC_NOW : KEY_CANCEL_SYNC)
    267                     .title(getString(!syncActive ? R.string.sync_now : R.string.sync_cancel))
    268                     .build());
    269         return actions;
    270     }
    271 
    272     /**
    273      * Gets an action item with the appropriate description / checkmark / drawable.
    274      * <p>
    275      * Returns null if the provider can't be shown for some reason.
    276      */
    277     private Action getAction(Account account, String authority, List<SyncInfo> currentSyncs) {
    278         final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0);
    279         if (providerInfo == null) {
    280             return null;
    281         }
    282         CharSequence providerLabel = providerInfo.loadLabel(getPackageManager());
    283         if (TextUtils.isEmpty(providerLabel)) {
    284             return null;
    285         }
    286         String description;
    287         boolean isSyncing;
    288         Date date = new Date();
    289         SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
    290         boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
    291         boolean authorityIsPending = status == null ? false : status.pending;
    292         boolean initialSync = status == null ? false : status.initialize;
    293 
    294         boolean activelySyncing = isSyncing(currentSyncs, account, authority);
    295         boolean lastSyncFailed = status != null
    296                 && status.lastFailureTime != 0
    297                 && status.getLastFailureMesgAsInt(0)
    298                    != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
    299         if (!syncEnabled) lastSyncFailed = false;
    300         if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
    301             mSyncIsFailing = true;
    302         }
    303         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    304             Log.d(TAG, "Update sync status: " + account + " " + authority +
    305                     " active = " + activelySyncing + " pend =" +  authorityIsPending);
    306         }
    307 
    308         final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
    309         if (!syncEnabled) {
    310             description = getString(R.string.sync_disabled);
    311         } else if (activelySyncing) {
    312             description = getString(R.string.sync_in_progress);
    313         } else if (successEndTime != 0) {
    314             date.setTime(successEndTime);
    315             final String timeString = formatSyncDate(date);
    316             description = getString(R.string.last_synced, timeString);
    317         } else {
    318             description = "";
    319         }
    320         int syncState = ContentResolver.getIsSyncable(account, authority);
    321 
    322         boolean pending = authorityIsPending && (syncState >= 0) && !initialSync;
    323         boolean active = activelySyncing && (syncState >= 0) && !initialSync;
    324         boolean activeVisible = pending || active;
    325         // TODO: set drawable based on these flags.
    326 
    327         ConnectivityManager connManager =
    328                 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    329         final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
    330         final boolean backgroundDataEnabled = connManager.getBackgroundDataSetting();
    331         final boolean oneTimeSyncMode = !masterSyncAutomatically || !backgroundDataEnabled;
    332         boolean checked = oneTimeSyncMode || syncEnabled;
    333 
    334         // Store extras in the intent
    335         Intent intent = new Intent()
    336                 .putExtra(EXTRA_ONE_TIME_SYNC, oneTimeSyncMode)
    337                 .putExtra(EXTRA_ACCOUNT, account);
    338 
    339         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    340             Log.d(TAG, "Creating action " + providerLabel.toString() + " " + description);
    341         }
    342         return new Action.Builder()
    343                 .key(authority)
    344                 .title(providerLabel.toString())
    345                 .description(description)
    346                 .checked(checked)
    347                 .intent(intent)
    348                 .build();
    349     }
    350 
    351     private void startSyncForEnabledProviders() {
    352         requestOrCancelSyncForEnabledProviders(true /* start them */);
    353     }
    354 
    355     private void cancelSyncForEnabledProviders() {
    356         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    357     }
    358 
    359     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
    360         // sync everything that the user has enabled
    361         int count = mActionFragment.getAdapter().getCount();
    362         for (int i = 0; i < count; i++) {
    363             Action action = (Action) mActionFragment.getAdapter().getItem(i);
    364             if (action.getIntent() == null) {
    365                 continue;
    366             }
    367             if (!action.isChecked()) {
    368                 continue;
    369             }
    370             Account account = action.getIntent().getParcelableExtra(EXTRA_ACCOUNT);
    371             requestOrCancelSync(account, action.getKey(), startSync);
    372         }
    373         // plus whatever the system needs to sync, e.g., invisible sync adapters
    374         if (mAccount != null) {
    375             // Make a copy of these in case we update while calling this.
    376             List<SyncAdapterType> invisibleAdapters = new ArrayList<SyncAdapterType>(
    377                     mInvisibleAdapters);
    378             int size = invisibleAdapters.size();
    379             for (int index = 0; index < size; ++index) {
    380                 SyncAdapterType syncAdapter = invisibleAdapters.get(index);
    381                 // invisible sync adapters' account type should be same as current account type
    382                 if (syncAdapter.accountType.equals(mAccount.type)) {
    383                     requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
    384                 }
    385             }
    386         }
    387     }
    388 
    389     private void requestOrCancelSync(Account account, String authority, boolean sync) {
    390         if (sync) {
    391             Bundle extras = new Bundle();
    392             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    393             ContentResolver.requestSync(account, authority, extras);
    394         } else {
    395             ContentResolver.cancelSync(account, authority);
    396         }
    397     }
    398 
    399     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
    400         for (SyncInfo syncInfo : currentSyncs) {
    401             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
    402                 return true;
    403             }
    404         }
    405         return false;
    406     }
    407 
    408     protected String formatSyncDate(Date date) {
    409         return DateUtils.formatDateTime(this, date.getTime(),
    410                 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
    411     }
    412 
    413     private class LoadActionsTask extends AsyncTask<Account[], Void, ArrayList<Action>> {
    414 
    415         @Override
    416         protected ArrayList<Action> doInBackground(Account[]... params) {
    417             return getActions(params[0]);
    418         }
    419 
    420         @Override
    421         protected void onPostExecute(ArrayList<Action> result) {
    422             // Set the icon based on whether sync is failing.
    423             ContentFragment contentFragment = ((ContentFragment) getContentFragment());
    424             if (contentFragment != null) {
    425                 contentFragment.setIcon(mSyncIsFailing ? R.drawable.ic_settings_sync_error :
    426                         R.drawable.ic_settings_sync);
    427                 contentFragment.setDescriptionText(mSyncIsFailing ?
    428                         getString(R.string.sync_is_failing) : "");
    429             }
    430             ((ActionAdapter) mActionFragment.getAdapter()).setActions(result);
    431         }
    432     }
    433 
    434     private static final Comparator<Action> ADAPTER_COMPARATOR = new Comparator<Action>() {
    435 
    436         @Override
    437         public int compare(Action lhs, Action rhs) {
    438             if (lhs == null && rhs == null) {
    439                 return 0;
    440             }
    441             if (lhs != null && rhs == null) {
    442                 return 1;
    443             }
    444             if (rhs != null && lhs == null) {
    445                 return -1;
    446             }
    447             return lhs.getTitle().compareTo(rhs.getTitle());
    448         }
    449     };
    450 }
    451