Home | History | Annotate | Download | only in accounts
      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
     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
     88     public int getMetricsCategory() {
     89         return MetricsEvent.ACCOUNTS_ACCOUNT_SYNC;
     90     }
     91 
     92     @Override
     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
    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
    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 
    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
    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 
    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
    173     public void onResume() {
    174         removePreference("dummy");
    175         mAuthenticatorHelper.listenToAccountUpdates();
    176         updateAuthDescriptions();
    177         onAccountsUpdate(Binder.getCallingUserHandle());
    178         super.onResume();
    179     }
    180 
    181     @Override
    182     public void onPause() {
    183         super.onPause();
    184         mAuthenticatorHelper.stopListeningToAccountUpdates();
    185     }
    186 
    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
    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
    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
    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
    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
    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 
    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 
    350     private void startSyncForEnabledProviders() {
    351         requestOrCancelSyncForEnabledProviders(true /* start them */);
    352         final Activity activity = getActivity();
    353         if (activity != null) {
    354             activity.invalidateOptionsMenu();
    355         }
    356     }
    357 
    358     private void cancelSyncForEnabledProviders() {
    359         requestOrCancelSyncForEnabledProviders(false /* cancel them */);
    360         final Activity activity = getActivity();
    361         if (activity != null) {
    362             activity.invalidateOptionsMenu();
    363         }
    364     }
    365 
    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 
    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 
    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
    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 
    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
    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 
    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 
    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
    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
    575     public int getHelpResource() {
    576         return R.string.help_url_accounts;
    577     }
    578 }
    579