Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d4b43228 authored by Fan Zhang's avatar Fan Zhang Committed by Android (Google) Code Review
Browse files

Merge "Improve UI pref when sync/cancel account syncs."

parents 28fbec0a 28299584
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
/*
 * Copyright (C) 2008 The Android Open Source Project
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.settings;
package com.android.settings.accounts;

import android.accounts.Account;
import android.content.Context;
@@ -24,6 +24,8 @@ import android.support.v7.preference.PreferenceViewHolder;
import android.util.Log;
import android.widget.ImageView;

import com.android.settings.R;

import java.util.ArrayList;

/**
@@ -78,6 +80,10 @@ public class AccountPreference extends Preference {
    }

    public void setSyncStatus(int status, boolean updateSummary) {
        if (mStatus == status) {
            Log.d(TAG, "Status is the same, not changing anything");
            return;
        }
        mStatus = status;
        if (!mShowTypeIcon && mSyncStatusIcon != null) {
            mSyncStatusIcon.setImageResource(getSyncStatusIcon(status));
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.support.v7.preference.PreferenceScreen;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Log;

import com.android.settings.SettingsPreferenceFragment;
@@ -46,6 +47,7 @@ abstract class AccountPreferenceBase extends SettingsPreferenceFragment
        implements AuthenticatorHelper.OnAccountsUpdateListener {

    protected static final String TAG = "AccountSettings";
    protected static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);

    public static final String AUTHORITIES_FILTER_KEY = "authorities";
    public static final String ACCOUNT_TYPES_FILTER_KEY = "account_types";
+82 −53
Original line number Diff line number Diff line
@@ -34,9 +34,11 @@ import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.support.v7.preference.PreferenceScreen;
import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -47,7 +49,6 @@ import android.view.ViewGroup;
import android.widget.TextView;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.AccountPreference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
@@ -56,8 +57,8 @@ import com.android.settingslib.accounts.AuthenticatorHelper;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static android.content.Intent.EXTRA_USER;

@@ -87,6 +88,8 @@ public class ManageAccountsSettings extends AccountPreferenceBase
    // mFirstAccount is used for the injected preferences
    private Account mFirstAccount;

    protected Set<String> mUserFacingSyncAuthorities;

    @Override
    public int getMetricsCategory() {
        return MetricsEvent.ACCOUNTS_MANAGE_ACCOUNTS;
@@ -188,8 +191,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
                mUserHandle.getIdentifier()).isEmpty();
        boolean syncActive = !getCurrentSyncs(mUserHandle.getIdentifier()).isEmpty();
        menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
        menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
    }
@@ -238,47 +240,58 @@ public class ManageAccountsSettings extends AccountPreferenceBase

    @Override
    protected void onSyncStateUpdated() {
        showSyncState();
        // Catch any delayed delivery of update messages
        final Activity activity = getActivity();
        if (activity != null) {
        // Catch any delayed delivery of update messages
        if (activity == null || activity.isFinishing()) {
            return;
        }
        showSyncState();
        activity.invalidateOptionsMenu();
    }

    private void tryInitUserFacingSyncAuthorities(int userId) {
        if (mUserFacingSyncAuthorities != null) {
            return;
        }
        mUserFacingSyncAuthorities = new ArraySet<>();

        // only track userfacing sync adapters when deciding if account is synced or not
        final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
        for (int k = 0, n = syncAdapters.length; k < n; k++) {
            final SyncAdapterType sa = syncAdapters[k];
            if (sa.isUserVisible()) {
                mUserFacingSyncAuthorities.add(sa.authority);
            }
        }
    }

    /**
     * Shows the sync state of the accounts. Note: it must be called after the accounts have been
     * loaded, @see #showAccountsIfNeeded().
     * loaded.
     *
     * @see {@link #showAccountsIfNeeded()}.
     */
    private void showSyncState() {
        // Catch any delayed delivery of update messages
        if (getActivity() == null || getActivity().isFinishing()) return;

    @VisibleForTesting
    void showSyncState() {
        final int userId = mUserHandle.getIdentifier();
        tryInitUserFacingSyncAuthorities(userId);

        // iterate over all the preferences, setting the state properly for each
        List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
        final List<SyncInfo> currentSyncs = getCurrentSyncs(userId);

        boolean anySyncFailed = false; // true if sync on any account failed
        Date date = new Date();

        // only track userfacing sync adapters when deciding if account is synced or not
        final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
        HashSet<String> userFacing = new HashSet<String>();
        for (int k = 0, n = syncAdapters.length; k < n; k++) {
            final SyncAdapterType sa = syncAdapters[k];
            if (sa.isUserVisible()) {
                userFacing.add(sa.authority);
            }
        }
        for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
            Preference pref = getPreferenceScreen().getPreference(i);
        final PreferenceScreen screen = getPreferenceScreen();
        final int prefCount = screen.getPreferenceCount();
        for (int i = 0; i < prefCount; i++) {
            Preference pref = screen.getPreference(i);
            if (!(pref instanceof AccountPreference)) {
                continue;
            }

            AccountPreference accountPref = (AccountPreference) pref;
            Account account = accountPref.getAccount();
            final AccountPreference accountPref = (AccountPreference) pref;
            final Account account = accountPref.getAccount();
            int syncCount = 0;
            long lastSuccessTime = 0;
            boolean syncIsFailing = false;
@@ -286,28 +299,33 @@ public class ManageAccountsSettings extends AccountPreferenceBase
            boolean syncingNow = false;
            if (authorities != null) {
                for (String authority : authorities) {
                    SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority,
                            userId);
                    SyncStatusInfo status = getSyncStatusInfo(account, authority, userId);
                    boolean syncEnabled = isSyncEnabled(userId, account, authority);
                    boolean authorityIsPending = ContentResolver.isSyncPending(account, authority);
                    boolean activelySyncing = isSyncing(currentSyncs, account, authority);
                    boolean lastSyncFailed = status != null
                            && syncEnabled
                            && status.lastFailureTime != 0
                            && status.getLastFailureMesgAsInt(0)
                            != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
                    if (lastSyncFailed && !activelySyncing && !authorityIsPending) {
                    if (lastSyncFailed && !activelySyncing
                            && !ContentResolver.isSyncPending(account, authority)) {
                        syncIsFailing = true;
                        anySyncFailed = true;
                        break;
                    }
                    syncingNow |= activelySyncing;

                    if (status != null && lastSuccessTime < status.lastSuccessTime) {
                        lastSuccessTime = status.lastSuccessTime;
                    }
                    syncCount += syncEnabled && userFacing.contains(authority) ? 1 : 0;
                    syncCount += syncEnabled && mUserFacingSyncAuthorities.contains(authority)
                            ? 1 : 0;
                    syncingNow |= activelySyncing;
                    if (syncingNow) {
                        break;
                    }
                }
            } else {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                if (VERBOSE) {
                    Log.v(TAG, "no syncadapters found for " + account);
                }
            }
@@ -332,10 +350,10 @@ public class ManageAccountsSettings extends AccountPreferenceBase
                accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true);
            }
        }

        if (mErrorInfoView != null) {
            mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE);
        }

    }

    private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
        final int count = currentSyncs.size();
@@ -348,7 +366,8 @@ public class ManageAccountsSettings extends AccountPreferenceBase
        return false;
    }

    private boolean isSyncEnabled(int userId, Account account, String authority) {
    @VisibleForTesting
    protected boolean isSyncEnabled(int userId, Account account, String authority) {
        return ContentResolver.getSyncAutomaticallyAsUser(account, authority, userId)
                && ContentResolver.getMasterSyncAutomaticallyAsUser(userId)
                && (ContentResolver.getIsSyncableAsUser(account, authority, userId) > 0);
@@ -536,4 +555,14 @@ public class ManageAccountsSettings extends AccountPreferenceBase
            }
        }
    }

    @VisibleForTesting
    protected List<SyncInfo> getCurrentSyncs(int userId) {
        return ContentResolver.getCurrentSyncsAsUser(userId);
    }

    @VisibleForTesting
    protected SyncStatusInfo getSyncStatusInfo(Account account, String authority, int userId) {
        return ContentResolver.getSyncStatusAsUser(account, authority, userId);
    }
}
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.accounts;

import android.accounts.Account;
import android.content.Context;

import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;

import java.util.ArrayList;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AccountPreferenceTest {

    private Context mContext;
    private Account mAccount;
    private ArrayList<String> mAuthorities;
    private AccountPreference mPreference;

    @Before
    public void setUp() {
        mContext = ShadowApplication.getInstance().getApplicationContext();
        mAccount = new Account("name", "type");
        mAuthorities = new ArrayList<>();
        mAuthorities.add("authority");

        mPreference = spy(new AccountPreference(
                mContext, mAccount, null /* icon */, mAuthorities, false /* showTypeIcon */));
    }

    @Test
    public void setSyncStatus_differentStatus_shouldUpdate() {
        mPreference.setSyncStatus(AccountPreference.SYNC_ERROR, true);
        verify(mPreference).setSummary(R.string.sync_error);
    }

    @Test
    public void setSyncStatus_sameStatus_shouldNotUpdate() {
        // Set it once, should update summary
        mPreference.setSyncStatus(AccountPreference.SYNC_ERROR, true);
        verify(mPreference).setSummary(R.string.sync_error);

        // Set it again, should not update summary
        mPreference.setSyncStatus(AccountPreference.SYNC_ERROR, true);
        verify(mPreference).setSummary(R.string.sync_error);
    }
}
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.accounts;

import android.accounts.Account;
import android.content.SyncInfo;
import android.content.SyncStatusInfo;
import android.os.UserHandle;
import android.support.v7.preference.PreferenceScreen;
import android.util.ArraySet;

import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import java.util.ArrayList;
import java.util.List;

import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class ManageAccountsSettingsTest {

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private AccountPreference mAccountPref;
    private Account mAccount;
    private ArrayList<String> mAuthorities;
    private TestFragment mSettings;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mAuthorities = new ArrayList<>();
        mAuthorities.add("authority");
        mAccount = new Account("name", "type");
        when(mAccountPref.getAccount()).thenReturn(mAccount);
        when(mAccountPref.getAuthorities()).thenReturn(mAuthorities);
        mSettings = new TestFragment();
    }

    @Test
    public void showSyncState_noAccountPrefs_shouldUpdateNothing() {
        when(mAccountPref.getAuthorities()).thenReturn(null);
        mSettings.showSyncState();
        verify(mSettings.getPreferenceScreen(), never()).getPreference(anyInt());
    }

    @Test
    public void showSyncState_syncInProgress_shouldUpdateInProgress() {
        mSettings.mUserFacingSyncAuthorities.add(mAuthorities.get(0));
        mSettings.mSyncInfos.add(new SyncInfo(0, mAccount, mAuthorities.get(0), 0));
        mSettings.mSyncStatusInfo = new SyncStatusInfo(0);
        when(mSettings.getPreferenceScreen().getPreferenceCount()).thenReturn(1);
        when(mSettings.getPreferenceScreen().getPreference(0)).thenReturn(mAccountPref);

        mSettings.showSyncState();

        verify(mSettings.getPreferenceScreen()).getPreference(anyInt());
        verify(mAccountPref).setSyncStatus(AccountPreference.SYNC_IN_PROGRESS, true);
    }

    @Test
    public void showSyncState_noUserFacingSynclets_shouldUpdateToDisabled() {
        mSettings.mSyncInfos.add(new SyncInfo(0, mAccount, mAuthorities.get(0), 0));
        mSettings.mSyncStatusInfo = new SyncStatusInfo(0);
        when(mSettings.getPreferenceScreen().getPreferenceCount()).thenReturn(1);
        when(mSettings.getPreferenceScreen().getPreference(0)).thenReturn(mAccountPref);

        mSettings.showSyncState();

        verify(mSettings.getPreferenceScreen()).getPreference(anyInt());
        verify(mAccountPref).setSyncStatus(AccountPreference.SYNC_DISABLED, true);
    }

    public static class TestFragment extends ManageAccountsSettings {

        private PreferenceScreen mScreen;
        private List<SyncInfo> mSyncInfos;
        private SyncStatusInfo mSyncStatusInfo;

        public TestFragment() {
            mUserHandle = mock(UserHandle.class);
            mScreen = mock(PreferenceScreen.class);
            mUserFacingSyncAuthorities = new ArraySet<>();
            mSyncInfos = new ArrayList<>();
        }

        @Override
        public PreferenceScreen getPreferenceScreen() {
            return mScreen;
        }

        @Override
        protected boolean isSyncEnabled(int userId, Account account, String authority) {
            return true;
        }

        @Override
        protected List<SyncInfo> getCurrentSyncs(int userId) {
            return mSyncInfos;
        }

        @Override
        protected SyncStatusInfo getSyncStatusInfo(Account account, String authority, int userId) {
            return mSyncStatusInfo;
        }
    }

}
 No newline at end of file