Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2015 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 android.accounts.Account;
     20 import android.accounts.AccountManager;
     21 import android.app.Activity;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.SyncAdapterType;
     26 import android.content.SyncInfo;
     27 import android.content.SyncStatusInfo;
     28 import android.content.SyncStatusObserver;
     29 import android.content.pm.PackageManager;
     30 import android.content.pm.ProviderInfo;
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.os.UserHandle;
     34 import android.support.v7.preference.Preference;
     35 import android.support.v7.preference.PreferenceGroup;
     36 import android.text.TextUtils;
     37 import android.text.format.DateUtils;
     38 import android.util.Log;
     39 
     40 import com.android.internal.logging.nano.MetricsProto;
     41 import com.android.settingslib.accounts.AuthenticatorHelper;
     42 import com.android.tv.settings.R;
     43 import com.android.tv.settings.SettingsPreferenceFragment;
     44 
     45 import com.google.android.collect.Lists;
     46 
     47 import java.util.ArrayList;
     48 import java.util.Collections;
     49 import java.util.List;
     50 
     51 /**
     52  * The account sync settings screen in TV Settings.
     53  */
     54 public class AccountSyncFragment extends SettingsPreferenceFragment implements
     55         AuthenticatorHelper.OnAccountsUpdateListener {
     56     private static final String TAG = "AccountSyncFragment";
     57 
     58     private static final String ARG_ACCOUNT = "account";
     59     private static final String KEY_REMOVE_ACCOUNT = "remove_account";
     60     private static final String KEY_SYNC_NOW = "sync_now";
     61     private static final String KEY_SYNC_ADAPTERS = "sync_adapters";
     62 
     63     private Object mStatusChangeListenerHandle;
     64     private UserHandle mUserHandle;
     65     private Account mAccount;
     66     private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
     67 
     68     private PreferenceGroup mSyncCategory;
     69 
     70     private final Handler mHandler = new Handler();
     71     private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
     72         public void onStatusChanged(int which) {
     73             mHandler.post(new Runnable() {
     74                 public void run() {
     75                     if (isResumed()) {
     76                         onSyncStateUpdated();
     77                     }
     78                 }
     79             });
     80         }
     81     };
     82     private AuthenticatorHelper mAuthenticatorHelper;
     83 
     84     public static AccountSyncFragment newInstance(Account account) {
     85         final Bundle b = new Bundle(1);
     86         prepareArgs(b, account);
     87         final AccountSyncFragment f = new AccountSyncFragment();
     88         f.setArguments(b);
     89         return f;
     90     }
     91 
     92     public static void prepareArgs(Bundle b, Account account) {
     93         b.putParcelable(ARG_ACCOUNT, account);
     94     }
     95 
     96     @Override
     97     public void onCreate(Bundle savedInstanceState) {
     98         mUserHandle = new UserHandle(UserHandle.myUserId());
     99         mAccount = getArguments().getParcelable(ARG_ACCOUNT);
    100         mAuthenticatorHelper = new AuthenticatorHelper(getActivity(), mUserHandle, this);
    101 
    102         super.onCreate(savedInstanceState);
    103 
    104         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    105             Log.v(TAG, "Got account: " + mAccount);
    106         }
    107     }
    108 
    109     @Override
    110     public void onStart() {
    111         super.onStart();
    112         mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
    113                 ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
    114                         | ContentResolver.SYNC_OBSERVER_TYPE_STATUS
    115                         | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS,
    116                 mSyncStatusObserver);
    117         onSyncStateUpdated();
    118         mAuthenticatorHelper.listenToAccountUpdates();
    119         mAuthenticatorHelper.updateAuthDescriptions(getActivity());
    120     }
    121 
    122     @Override
    123     public void onResume() {
    124         super.onResume();
    125         mHandler.post(() -> onAccountsUpdate(mUserHandle));
    126     }
    127 
    128     @Override
    129     public void onStop() {
    130         super.onStop();
    131         ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
    132         mAuthenticatorHelper.stopListeningToAccountUpdates();
    133     }
    134 
    135     @Override
    136     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
    137         setPreferencesFromResource(R.xml.account_preference, null);
    138 
    139         getPreferenceScreen().setTitle(mAccount.name);
    140 
    141         final Preference removeAccountPref = findPreference(KEY_REMOVE_ACCOUNT);
    142         removeAccountPref.setIntent(new Intent(getActivity(), RemoveAccountDialog.class)
    143                 .putExtra(AccountSyncActivity.EXTRA_ACCOUNT, mAccount.name));
    144 
    145         mSyncCategory = (PreferenceGroup) findPreference(KEY_SYNC_ADAPTERS);
    146     }
    147 
    148     @Override
    149     public boolean onPreferenceTreeClick(Preference preference) {
    150         if (preference instanceof SyncStateSwitchPreference) {
    151             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
    152             String authority = syncPref.getAuthority();
    153             Account account = syncPref.getAccount();
    154             final int userId = mUserHandle.getIdentifier();
    155             if (syncPref.isOneTimeSyncMode()) {
    156                 requestOrCancelSync(account, authority, true);
    157             } else {
    158                 boolean syncOn = syncPref.isChecked();
    159                 boolean oldSyncState = ContentResolver.getSyncAutomaticallyAsUser(account,
    160                         authority, userId);
    161                 if (syncOn != oldSyncState) {
    162                     // if we're enabling sync, this will request a sync as well
    163                     ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
    164                     // if the master sync switch is off, the request above will
    165                     // get dropped.  when the user clicks on this toggle,
    166                     // we want to force the sync, however.
    167                     if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
    168                         requestOrCancelSync(account, authority, syncOn);
    169                     }
    170                 }
    171             }
    172             return true;
    173         } else if (TextUtils.equals(preference.getKey(), KEY_SYNC_NOW)) {
    174             boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
    175                     mUserHandle.getIdentifier()).isEmpty();
    176             if (syncActive) {
    177                 cancelSyncForEnabledProviders();
    178             } else {
    179                 startSyncForEnabledProviders();
    180             }
    181             return true;
    182         } else {
    183             return super.onPreferenceTreeClick(preference);
    184         }
    185     }
    186 
    187     private void startSyncForEnabledProviders() {
    188         requestOrCancelSyncForEnabledProviders(true /* start them */);
    189         final Activity activity = getActivity();
    190         if (activity != null) {
    191             activity.invalidateOptionsMenu();
    192         }
    193     }
    194 
    195     private void cancelSyncForEnabledProviders() {
    196         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    197         final Activity activity = getActivity();
    198         if (activity != null) {
    199             activity.invalidateOptionsMenu();
    200         }
    201     }
    202 
    203     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
    204         // sync everything that the user has enabled
    205         int count = mSyncCategory.getPreferenceCount();
    206         for (int i = 0; i < count; i++) {
    207             Preference pref = mSyncCategory.getPreference(i);
    208             if (! (pref instanceof SyncStateSwitchPreference)) {
    209                 continue;
    210             }
    211             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
    212             if (!syncPref.isChecked()) {
    213                 continue;
    214             }
    215             requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
    216         }
    217         // plus whatever the system needs to sync, e.g., invisible sync adapters
    218         if (mAccount != null) {
    219             for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
    220                 requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
    221             }
    222         }
    223     }
    224 
    225     private void requestOrCancelSync(Account account, String authority, boolean flag) {
    226         if (flag) {
    227             Bundle extras = new Bundle();
    228             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    229             ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
    230                     extras);
    231         } else {
    232             ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
    233         }
    234     }
    235 
    236     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
    237         for (SyncInfo syncInfo : currentSyncs) {
    238             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
    239                 return true;
    240             }
    241         }
    242         return false;
    243     }
    244 
    245     private boolean accountExists(Account account) {
    246         if (account == null) return false;
    247 
    248         Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser(
    249                 account.type, mUserHandle);
    250         for (final Account other : accounts) {
    251             if (other.equals(account)) {
    252                 return true;
    253             }
    254         }
    255         return false;
    256     }
    257 
    258     @Override
    259     public void onAccountsUpdate(UserHandle userHandle) {
    260         if (!isResumed()) {
    261             return;
    262         }
    263         if (!accountExists(mAccount)) {
    264             // The account was deleted
    265             if (!getFragmentManager().popBackStackImmediate()) {
    266                 getActivity().finish();
    267             }
    268             return;
    269         }
    270         updateAccountSwitches();
    271         onSyncStateUpdated();
    272     }
    273 
    274     private void onSyncStateUpdated() {
    275         // iterate over all the preferences, setting the state properly for each
    276         final int userId = mUserHandle.getIdentifier();
    277         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
    278 //        boolean syncIsFailing = false;
    279 
    280         // Refresh the sync status switches - some syncs may have become active.
    281         updateAccountSwitches();
    282 
    283         for (int i = 0, count = mSyncCategory.getPreferenceCount(); i < count; i++) {
    284             Preference pref = mSyncCategory.getPreference(i);
    285             if (! (pref instanceof SyncStateSwitchPreference)) {
    286                 continue;
    287             }
    288             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
    289 
    290             String authority = syncPref.getAuthority();
    291             Account account = syncPref.getAccount();
    292 
    293             SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
    294             boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
    295                     userId);
    296             boolean authorityIsPending = status != null && status.pending;
    297             boolean initialSync = status != null && status.initialize;
    298 
    299             boolean activelySyncing = isSyncing(currentSyncs, account, authority);
    300             boolean lastSyncFailed = status != null
    301                     && status.lastFailureTime != 0
    302                     && status.getLastFailureMesgAsInt(0)
    303                     != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
    304             if (!syncEnabled) lastSyncFailed = false;
    305 //            if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
    306 //                syncIsFailing = true;
    307 //            }
    308             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    309                 Log.v(TAG, "Update sync status: " + account + " " + authority +
    310                         " active = " + activelySyncing + " pend =" +  authorityIsPending);
    311             }
    312 
    313             final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
    314             if (!syncEnabled) {
    315                 syncPref.setSummary(R.string.sync_disabled);
    316             } else if (activelySyncing) {
    317                 syncPref.setSummary(R.string.sync_in_progress);
    318             } else if (successEndTime != 0) {
    319                 final String timeString = DateUtils.formatDateTime(getActivity(), successEndTime,
    320                         DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
    321                 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString));
    322             } else {
    323                 syncPref.setSummary("");
    324             }
    325             int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
    326 
    327             syncPref.setActive(activelySyncing && (syncState >= 0) &&
    328                     !initialSync);
    329             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
    330                     !initialSync);
    331 
    332             syncPref.setFailed(lastSyncFailed);
    333             final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(
    334                     userId);
    335             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
    336             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
    337         }
    338     }
    339 
    340     private void updateAccountSwitches() {
    341         mInvisibleAdapters.clear();
    342 
    343         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
    344                 mUserHandle.getIdentifier());
    345         ArrayList<String> authorities = new ArrayList<>(syncAdapters.length);
    346         for (SyncAdapterType sa : syncAdapters) {
    347             // Only keep track of sync adapters for this account
    348             if (!sa.accountType.equals(mAccount.type)) continue;
    349             if (sa.isUserVisible()) {
    350                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    351                     Log.v(TAG, "updateAccountSwitches: added authority " + sa.authority
    352                             + " to accountType " + sa.accountType);
    353                 }
    354                 authorities.add(sa.authority);
    355             } else {
    356                 // keep track of invisible sync adapters, so sync now forces
    357                 // them to sync as well.
    358                 mInvisibleAdapters.add(sa);
    359             }
    360         }
    361 
    362         mSyncCategory.removeAll();
    363         final List<Preference> switches = new ArrayList<>(authorities.size());
    364 
    365         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    366             Log.v(TAG, "looking for sync adapters that match account " + mAccount);
    367         }
    368         for (final String authority : authorities) {
    369             // We could check services here....
    370             int syncState = ContentResolver.getIsSyncableAsUser(mAccount, authority,
    371                     mUserHandle.getIdentifier());
    372             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    373                 Log.v(TAG, "  found authority " + authority + " " + syncState);
    374             }
    375             if (syncState > 0) {
    376                 final Preference pref = createSyncStateSwitch(mAccount, authority);
    377                 switches.add(pref);
    378             }
    379         }
    380 
    381         Collections.sort(switches);
    382         for (final Preference pref : switches) {
    383             mSyncCategory.addPreference(pref);
    384         }
    385     }
    386 
    387     private Preference createSyncStateSwitch(Account account, String authority) {
    388         final Context themedContext = getPreferenceManager().getContext();
    389         SyncStateSwitchPreference preference =
    390                 new SyncStateSwitchPreference(themedContext, account, authority);
    391         preference.setPersistent(false);
    392         final PackageManager packageManager = getActivity().getPackageManager();
    393         final ProviderInfo providerInfo = packageManager.resolveContentProviderAsUser(
    394                 authority, 0, mUserHandle.getIdentifier());
    395         if (providerInfo == null) {
    396             return null;
    397         }
    398         CharSequence providerLabel = providerInfo.loadLabel(packageManager);
    399         if (TextUtils.isEmpty(providerLabel)) {
    400             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
    401             return null;
    402         }
    403         String title = getString(R.string.sync_item_title, providerLabel);
    404         preference.setTitle(title);
    405         preference.setKey(authority);
    406         return preference;
    407     }
    408 
    409     @Override
    410     public int getMetricsCategory() {
    411         return MetricsProto.MetricsEvent.ACCOUNTS_ACCOUNT_SYNC;
    412     }
    413 }
    414