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