• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.accounts;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.app.Activity;
22 import android.app.Dialog;
23 import android.app.settings.SettingsEnums;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentSender;
28 import android.content.SyncAdapterType;
29 import android.content.SyncInfo;
30 import android.content.SyncStatusInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ProviderInfo;
33 import android.content.pm.UserInfo;
34 import android.os.Binder;
35 import android.os.Bundle;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.text.TextUtils;
39 import android.text.format.DateUtils;
40 import android.util.Log;
41 import android.view.Menu;
42 import android.view.MenuInflater;
43 import android.view.MenuItem;
44 
45 import androidx.appcompat.app.AlertDialog;
46 import androidx.preference.Preference;
47 
48 import com.android.settings.R;
49 import com.android.settings.Utils;
50 import com.android.settings.widget.EntityHeaderController;
51 
52 import com.google.android.collect.Lists;
53 
54 import java.util.ArrayList;
55 import java.util.Date;
56 import java.util.List;
57 
58 public class AccountSyncSettings extends AccountPreferenceBase {
59 
60     public static final String ACCOUNT_KEY = "account";
61     private static final int MENU_SYNC_NOW_ID = Menu.FIRST;
62     private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 1;
63     private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
64 
65     private Account mAccount;
66     private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
67 
68     @Override
onCreateDialog(final int id)69     public Dialog onCreateDialog(final int id) {
70         Dialog dialog = null;
71         if (id == CANT_DO_ONETIME_SYNC_DIALOG) {
72             dialog = new AlertDialog.Builder(getActivity())
73                     .setTitle(R.string.cant_sync_dialog_title)
74                     .setMessage(R.string.cant_sync_dialog_message)
75                     .setPositiveButton(android.R.string.ok, null)
76                     .create();
77         }
78         return dialog;
79     }
80 
81     @Override
getMetricsCategory()82     public int getMetricsCategory() {
83         return SettingsEnums.ACCOUNTS_ACCOUNT_SYNC;
84     }
85 
86     @Override
getDialogMetricsCategory(int dialogId)87     public int getDialogMetricsCategory(int dialogId) {
88         switch (dialogId) {
89             case CANT_DO_ONETIME_SYNC_DIALOG:
90                 return SettingsEnums.DIALOG_ACCOUNT_SYNC_CANNOT_ONETIME_SYNC;
91             default:
92                 return 0;
93         }
94     }
95 
96     @Override
onCreate(Bundle icicle)97     public void onCreate(Bundle icicle) {
98         super.onCreate(icicle);
99         addPreferencesFromResource(R.xml.account_sync_settings);
100         getPreferenceScreen().setOrderingAsAdded(false);
101         setAccessibilityTitle();
102     }
103 
104     @Override
onActivityCreated(Bundle savedInstanceState)105     public void onActivityCreated(Bundle savedInstanceState) {
106         super.onActivityCreated(savedInstanceState);
107 
108         Bundle arguments = getArguments();
109         if (arguments == null) {
110             Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
111             finish();
112             return;
113         }
114         mAccount = arguments.getParcelable(ACCOUNT_KEY);
115         if (!accountExists(mAccount)) {
116             Log.e(TAG, "Account provided does not exist: " + mAccount);
117             finish();
118             return;
119         }
120         if (Log.isLoggable(TAG, Log.VERBOSE)) {
121             Log.v(TAG, "Got account: " + mAccount);
122         }
123         final Activity activity = getActivity();
124         final Preference pref = EntityHeaderController
125                 .newInstance(activity, this, null /* header */)
126                 .setRecyclerView(getListView(), getSettingsLifecycle())
127                 .setIcon(getDrawableForType(mAccount.type))
128                 .setLabel(mAccount.name)
129                 .setSummary(getLabelForType(mAccount.type))
130                 .done(activity, getPrefContext());
131         pref.setOrder(0);
132         getPreferenceScreen().addPreference(pref);
133     }
134 
setAccessibilityTitle()135     private void setAccessibilityTitle() {
136         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
137         UserInfo user = um.getUserInfo(mUserHandle.getIdentifier());
138         boolean isWorkProfile = user != null ? user.isManagedProfile() : false;
139         CharSequence currentTitle = getActivity().getTitle();
140         String accessibilityTitle =
141                 getString(isWorkProfile
142                         ? R.string.accessibility_work_account_title
143                         : R.string.accessibility_personal_account_title, currentTitle);
144         getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibilityTitle));
145     }
146 
147     @Override
onResume()148     public void onResume() {
149         mAuthenticatorHelper.listenToAccountUpdates();
150         updateAuthDescriptions();
151         onAccountsUpdate(Binder.getCallingUserHandle());
152         super.onResume();
153     }
154 
155     @Override
onPause()156     public void onPause() {
157         super.onPause();
158         mAuthenticatorHelper.stopListeningToAccountUpdates();
159     }
160 
addSyncStateSwitch(Account account, String authority, String packageName, int uid)161     private void addSyncStateSwitch(Account account, String authority,
162             String packageName, int uid) {
163         SyncStateSwitchPreference item = (SyncStateSwitchPreference) getCachedPreference(authority);
164         if (item == null) {
165             item = new SyncStateSwitchPreference(getPrefContext(), account, authority,
166                     packageName, uid);
167             getPreferenceScreen().addPreference(item);
168         } else {
169             item.setup(account, authority, packageName, uid);
170         }
171         final PackageManager packageManager = getPackageManager();
172         item.setPersistent(false);
173         final ProviderInfo providerInfo = packageManager.resolveContentProviderAsUser(
174                 authority, 0, mUserHandle.getIdentifier());
175         if (providerInfo == null) {
176             return;
177         }
178         final CharSequence providerLabel = providerInfo.loadLabel(packageManager);
179         if (TextUtils.isEmpty(providerLabel)) {
180             Log.e(TAG, "Provider needs a label for authority '" + authority + "'");
181             return;
182         }
183         item.setTitle(providerLabel);
184         item.setKey(authority);
185     }
186 
187     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)188     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
189         MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
190                 getString(R.string.sync_menu_sync_now))
191                 .setIcon(R.drawable.ic_menu_refresh_holo_dark);
192         MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
193                 getString(R.string.sync_menu_sync_cancel))
194                 .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
195 
196         syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
197                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
198         syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
199                 MenuItem.SHOW_AS_ACTION_WITH_TEXT);
200 
201         super.onCreateOptionsMenu(menu, inflater);
202     }
203 
204     @Override
onPrepareOptionsMenu(Menu menu)205     public void onPrepareOptionsMenu(Menu menu) {
206         super.onPrepareOptionsMenu(menu);
207         // Note that this also counts accounts that are not currently displayed
208         boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
209                 mUserHandle.getIdentifier()).isEmpty();
210         menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
211         menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
212     }
213 
214     @Override
onOptionsItemSelected(MenuItem item)215     public boolean onOptionsItemSelected(MenuItem item) {
216         switch (item.getItemId()) {
217             case MENU_SYNC_NOW_ID:
218                 startSyncForEnabledProviders();
219                 return true;
220             case MENU_SYNC_CANCEL_ID:
221                 cancelSyncForEnabledProviders();
222                 return true;
223         }
224         return super.onOptionsItemSelected(item);
225     }
226 
227     @Override
onActivityResult(int requestCode, int resultCode, Intent data)228     public void onActivityResult(int requestCode, int resultCode, Intent data) {
229         if (resultCode == Activity.RESULT_OK) {
230             final int uid = requestCode;
231             final int count = getPreferenceScreen().getPreferenceCount();
232             for (int i = 0; i < count; i++) {
233                 Preference preference = getPreferenceScreen().getPreference(i);
234                 if (preference instanceof SyncStateSwitchPreference) {
235                     SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
236                     if (syncPref.getUid() == uid) {
237                         onPreferenceTreeClick(syncPref);
238                         return;
239                     }
240                 }
241             }
242         }
243     }
244 
245     @Override
onPreferenceTreeClick(Preference preference)246     public boolean onPreferenceTreeClick(Preference preference) {
247         if (getActivity() == null) {
248             return false;
249         }
250         if (preference instanceof SyncStateSwitchPreference) {
251             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
252             final String authority = syncPref.getAuthority();
253             if (TextUtils.isEmpty(authority)) {
254                 return false;
255             }
256             final Account account = syncPref.getAccount();
257             final int userId = mUserHandle.getIdentifier();
258             final String packageName = syncPref.getPackageName();
259 
260             boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account,
261                     authority, userId);
262             if (syncPref.isOneTimeSyncMode()) {
263                 // If the sync adapter doesn't have access to the account we either
264                 // request access by starting an activity if possible or kick off the
265                 // sync which will end up posting an access request notification.
266                 if (requestAccountAccessIfNeeded(packageName)) {
267                     return true;
268                 }
269                 requestOrCancelSync(account, authority, true);
270             } else {
271                 boolean syncOn = syncPref.isChecked();
272                 boolean oldSyncState = syncAutomatically;
273                 if (syncOn != oldSyncState) {
274                     // Toggling this switch triggers sync but we may need a user approval.
275                     // If the sync adapter doesn't have access to the account we either
276                     // request access by starting an activity if possible or kick off the
277                     // sync which will end up posting an access request notification.
278                     if (syncOn && requestAccountAccessIfNeeded(packageName)) {
279                         return true;
280                     }
281                     // if we're enabling sync, this will request a sync as well
282                     ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
283                     // if the master sync switch is off, the request above will
284                     // get dropped.  when the user clicks on this toggle,
285                     // we want to force the sync, however.
286                     if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
287                         requestOrCancelSync(account, authority, syncOn);
288                     }
289                 }
290             }
291             return true;
292         } else {
293             return super.onPreferenceTreeClick(preference);
294         }
295     }
296 
requestAccountAccessIfNeeded(String packageName)297     private boolean requestAccountAccessIfNeeded(String packageName) {
298         if (packageName == null) {
299             return false;
300         }
301 
302         final int uid;
303         try {
304             uid = getContext().getPackageManager().getPackageUidAsUser(
305                     packageName, mUserHandle.getIdentifier());
306         } catch (PackageManager.NameNotFoundException e) {
307             Log.e(TAG, "Invalid sync ", e);
308             return false;
309         }
310 
311         AccountManager accountManager = getContext().getSystemService(AccountManager.class);
312         if (!accountManager.hasAccountAccess(mAccount, packageName, mUserHandle)) {
313             IntentSender intent = accountManager.createRequestAccountAccessIntentSenderAsUser(
314                     mAccount, packageName, mUserHandle);
315             if (intent != null) {
316                 try {
317                     startIntentSenderForResult(intent, uid, null, 0, 0, 0, null);
318                     return true;
319                 } catch (IntentSender.SendIntentException e) {
320                     Log.e(TAG, "Error requesting account access", e);
321                 }
322             }
323         }
324         return false;
325     }
326 
startSyncForEnabledProviders()327     private void startSyncForEnabledProviders() {
328         requestOrCancelSyncForEnabledProviders(true /* start them */);
329         final Activity activity = getActivity();
330         if (activity != null) {
331             activity.invalidateOptionsMenu();
332         }
333     }
334 
cancelSyncForEnabledProviders()335     private void cancelSyncForEnabledProviders() {
336         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
337         final Activity activity = getActivity();
338         if (activity != null) {
339             activity.invalidateOptionsMenu();
340         }
341     }
342 
requestOrCancelSyncForEnabledProviders(boolean startSync)343     private void requestOrCancelSyncForEnabledProviders(boolean startSync) {
344         // sync everything that the user has enabled
345         int count = getPreferenceScreen().getPreferenceCount();
346         for (int i = 0; i < count; i++) {
347             Preference pref = getPreferenceScreen().getPreference(i);
348             if (!(pref instanceof SyncStateSwitchPreference)) {
349                 continue;
350             }
351             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
352             if (!syncPref.isChecked()) {
353                 continue;
354             }
355             requestOrCancelSync(syncPref.getAccount(), syncPref.getAuthority(), startSync);
356         }
357         // plus whatever the system needs to sync, e.g., invisible sync adapters
358         if (mAccount != null) {
359             for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
360                 requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
361             }
362         }
363     }
364 
requestOrCancelSync(Account account, String authority, boolean flag)365     private void requestOrCancelSync(Account account, String authority, boolean flag) {
366         if (flag) {
367             Bundle extras = new Bundle();
368             extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
369             ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
370                     extras);
371         } else {
372             ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
373         }
374     }
375 
isSyncing(List<SyncInfo> currentSyncs, Account account, String authority)376     private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
377         for (SyncInfo syncInfo : currentSyncs) {
378             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
379                 return true;
380             }
381         }
382         return false;
383     }
384 
385     @Override
onSyncStateUpdated()386     protected void onSyncStateUpdated() {
387         if (!isResumed()) return;
388         setFeedsState();
389         final Activity activity = getActivity();
390         if (activity != null) {
391             activity.invalidateOptionsMenu();
392         }
393     }
394 
setFeedsState()395     private void setFeedsState() {
396         // iterate over all the preferences, setting the state properly for each
397         Date date = new Date();
398         final int userId = mUserHandle.getIdentifier();
399         List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
400         boolean syncIsFailing = false;
401 
402         // Refresh the sync status switches - some syncs may have become active.
403         updateAccountSwitches();
404 
405         for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
406             Preference pref = getPreferenceScreen().getPreference(i);
407             if (!(pref instanceof SyncStateSwitchPreference)) {
408                 continue;
409             }
410             SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
411 
412             String authority = syncPref.getAuthority();
413             Account account = syncPref.getAccount();
414 
415             SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
416             boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
417                     userId);
418             boolean authorityIsPending = status == null ? false : status.pending;
419             boolean initialSync = status == null ? false : status.initialize;
420 
421             boolean activelySyncing = isSyncing(currentSyncs, account, authority);
422             boolean lastSyncFailed = status != null
423                     && status.lastFailureTime != 0
424                     && status.getLastFailureMesgAsInt(0)
425                     != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
426             if (!syncEnabled) lastSyncFailed = false;
427             if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
428                 syncIsFailing = true;
429             }
430             if (Log.isLoggable(TAG, Log.DEBUG)) {
431                 Log.d(TAG, "Update sync status: " + account + " " + authority +
432                         " active = " + activelySyncing + " pend =" + authorityIsPending);
433             }
434 
435             final long successEndTime = (status == null) ? 0 : status.lastSuccessTime;
436             if (!syncEnabled) {
437                 syncPref.setSummary(R.string.sync_disabled);
438             } else if (activelySyncing) {
439                 syncPref.setSummary(R.string.sync_in_progress);
440             } else if (successEndTime != 0) {
441                 date.setTime(successEndTime);
442                 final String timeString = formatSyncDate(getContext(), date);
443                 syncPref.setSummary(getResources().getString(R.string.last_synced, timeString));
444             } else {
445                 syncPref.setSummary("");
446             }
447             int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
448 
449             syncPref.setActive(activelySyncing && (syncState >= 0) &&
450                     !initialSync);
451             syncPref.setPending(authorityIsPending && (syncState >= 0) &&
452                     !initialSync);
453 
454             syncPref.setFailed(lastSyncFailed);
455             final boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(
456                     userId);
457             syncPref.setOneTimeSyncMode(oneTimeSyncMode);
458             syncPref.setChecked(oneTimeSyncMode || syncEnabled);
459         }
460         if (syncIsFailing) {
461             mFooterPreferenceMixin.createFooterPreference()
462                     .setTitle(R.string.sync_is_failing);
463         }
464     }
465 
466     @Override
onAccountsUpdate(final UserHandle userHandle)467     public void onAccountsUpdate(final UserHandle userHandle) {
468         super.onAccountsUpdate(userHandle);
469         if (!accountExists(mAccount)) {
470             // The account was deleted
471             finish();
472             return;
473         }
474         updateAccountSwitches();
475         onSyncStateUpdated();
476     }
477 
accountExists(Account account)478     private boolean accountExists(Account account) {
479         if (account == null) return false;
480 
481         Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser(
482                 account.type, mUserHandle);
483         final int count = accounts.length;
484         for (int i = 0; i < count; i++) {
485             if (accounts[i].equals(account)) {
486                 return true;
487             }
488         }
489         return false;
490     }
491 
updateAccountSwitches()492     private void updateAccountSwitches() {
493         mInvisibleAdapters.clear();
494 
495         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
496                 mUserHandle.getIdentifier());
497         ArrayList<SyncAdapterType> authorities = new ArrayList<>();
498         for (int i = 0, n = syncAdapters.length; i < n; i++) {
499             final SyncAdapterType sa = syncAdapters[i];
500             // Only keep track of sync adapters for this account
501             if (!sa.accountType.equals(mAccount.type)) continue;
502             if (sa.isUserVisible()) {
503                 if (Log.isLoggable(TAG, Log.DEBUG)) {
504                     Log.d(TAG, "updateAccountSwitches: added authority " + sa.authority
505                             + " to accountType " + sa.accountType);
506                 }
507                 authorities.add(sa);
508             } else {
509                 // keep track of invisible sync adapters, so sync now forces
510                 // them to sync as well.
511                 mInvisibleAdapters.add(sa);
512             }
513         }
514 
515         if (Log.isLoggable(TAG, Log.DEBUG)) {
516             Log.d(TAG, "looking for sync adapters that match account " + mAccount);
517         }
518 
519         cacheRemoveAllPrefs(getPreferenceScreen());
520         getCachedPreference(EntityHeaderController.PREF_KEY_APP_HEADER);
521         for (int j = 0, m = authorities.size(); j < m; j++) {
522             final SyncAdapterType syncAdapter = authorities.get(j);
523             // We could check services here....
524             int syncState = ContentResolver.getIsSyncableAsUser(mAccount, syncAdapter.authority,
525                     mUserHandle.getIdentifier());
526             if (Log.isLoggable(TAG, Log.DEBUG)) {
527                 Log.d(TAG, "  found authority " + syncAdapter.authority + " " + syncState);
528             }
529             if (syncState > 0) {
530                 final int uid;
531                 try {
532                     uid = getContext().getPackageManager().getPackageUidAsUser(
533                             syncAdapter.getPackageName(), mUserHandle.getIdentifier());
534                     addSyncStateSwitch(mAccount, syncAdapter.authority,
535                             syncAdapter.getPackageName(), uid);
536                 } catch (PackageManager.NameNotFoundException e) {
537                     Log.e(TAG, "No uid for package" + syncAdapter.getPackageName(), e);
538                 }
539             }
540         }
541         removeCachedPrefs(getPreferenceScreen());
542     }
543 
544     @Override
getHelpResource()545     public int getHelpResource() {
546         return R.string.help_url_accounts;
547     }
548 
formatSyncDate(Context context, Date date)549     private static String formatSyncDate(Context context, Date date) {
550         return DateUtils.formatDateTime(context, date.getTime(),
551                 DateUtils.FORMAT_SHOW_DATE
552                         | DateUtils.FORMAT_SHOW_YEAR
553                         | DateUtils.FORMAT_SHOW_TIME);
554     }
555 }
556