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