• 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 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 
newInstance(Account account)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 
prepareArgs(Bundle b, Account account)92     public static void prepareArgs(Bundle b, Account account) {
93         b.putParcelable(ARG_ACCOUNT, account);
94     }
95 
96     @Override
onCreate(Bundle savedInstanceState)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
onStart()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
onResume()123     public void onResume() {
124         super.onResume();
125         mHandler.post(() -> onAccountsUpdate(mUserHandle));
126     }
127 
128     @Override
onStop()129     public void onStop() {
130         super.onStop();
131         ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
132         mAuthenticatorHelper.stopListeningToAccountUpdates();
133     }
134 
135     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)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
onPreferenceTreeClick(Preference preference)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 
startSyncForEnabledProviders()187     private void startSyncForEnabledProviders() {
188         requestOrCancelSyncForEnabledProviders(true /* start them */);
189         final Activity activity = getActivity();
190         if (activity != null) {
191             activity.invalidateOptionsMenu();
192         }
193     }
194 
cancelSyncForEnabledProviders()195     private void cancelSyncForEnabledProviders() {
196         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
197         final Activity activity = getActivity();
198         if (activity != null) {
199             activity.invalidateOptionsMenu();
200         }
201     }
202 
requestOrCancelSyncForEnabledProviders(boolean startSync)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 
requestOrCancelSync(Account account, String authority, boolean flag)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 
isSyncing(List<SyncInfo> currentSyncs, Account account, String authority)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 
accountExists(Account account)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
onAccountsUpdate(UserHandle userHandle)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 
onSyncStateUpdated()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 
updateAccountSwitches()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 
createSyncStateSwitch(Account account, String authority)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
getMetricsCategory()410     public int getMetricsCategory() {
411         return MetricsProto.MetricsEvent.ACCOUNTS_ACCOUNT_SYNC;
412     }
413 }
414