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

Commit 2d6930b2 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add account sync summary."

parents 985aa11e 771848dd
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@
      android:key="remove_account"
      android:layout="@layout/remove_account_button"
      android:order="1000"
      android:selectable="false"/>
      android:selectable="false"
      settings:allowDividerAbove="true"/>

</PreferenceScreen>
+64 −1
Original line number Diff line number Diff line
@@ -19,25 +19,32 @@ package com.android.settings.accounts;
import static android.content.Intent.EXTRA_USER;

import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncAdapterType;
import android.os.Bundle;
import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;

import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.accounts.AuthenticatorHelper;
import com.android.settingslib.core.AbstractPreferenceController;

public class AccountSyncPreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin {
        implements PreferenceControllerMixin, AuthenticatorHelper.OnAccountsUpdateListener {

    private static final String TAG = "AccountSyncController";
    private static final String KEY_ACCOUNT_SYNC = "account_sync";

    private Account mAccount;
    private UserHandle mUserHandle;
    private AuthenticatorHelper mAuthenticatorHelper;
    private Preference mPreference;

    public AccountSyncPreferenceController(Context context) {
        super(context);
@@ -67,8 +74,64 @@ public class AccountSyncPreferenceController extends AbstractPreferenceControlle
        return KEY_ACCOUNT_SYNC;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
    }

    @Override
    public void updateState(Preference preference) {
        updateSummary(preference);
    }

    @Override
    public void onAccountsUpdate(UserHandle userHandle) {
        updateSummary(mPreference);
    }

    public void init(Account account, UserHandle userHandle) {
        mAccount = account;
        mUserHandle = userHandle;
        mAuthenticatorHelper = new AuthenticatorHelper(mContext, mUserHandle, this);
    }

    @VisibleForTesting
    void updateSummary(Preference preference) {
        if (mAccount == null) {
            return;
        }
        final int userId = mUserHandle.getIdentifier();
        final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
        int total = 0;
        int enabled = 0;
        if (syncAdapters != null) {
            for (int i = 0, n = syncAdapters.length; i < n; i++) {
                final SyncAdapterType sa = syncAdapters[i];
                if (!sa.accountType.equals(mAccount.type) || !sa.isUserVisible()) {
                    continue;
                }
                final int syncState =
                        ContentResolver.getIsSyncableAsUser(mAccount, sa.authority, userId);
                if (syncState > 0) {
                    total++;
                    final boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(
                            mAccount, sa.authority, userId);
                    final boolean oneTimeSyncMode =
                            !ContentResolver.getMasterSyncAutomaticallyAsUser(userId);
                    if (oneTimeSyncMode || syncEnabled) {
                        enabled++;
                    }
                }
            }
        }
        if (enabled == 0) {
            preference.setSummary(R.string.account_sync_summary_all_off);
        } else if (enabled == total) {
            preference.setSummary(R.string.account_sync_summary_all_on);
        } else {
            preference.setSummary(
                    mContext.getString(R.string.account_sync_summary_some_on, enabled, total));
        }
    }
}
+15 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.settings.applications;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.res.TypedArrayUtils;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
@@ -33,19 +34,30 @@ import com.android.settings.Utils;
public class LayoutPreference extends Preference {

    private final View.OnClickListener mClickListener = v -> performClick(v);
    private boolean mAllowDividerAbove;
    private boolean mAllowDividerBelow;

    @VisibleForTesting
    View mRootView;

    public LayoutPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        final TypedArray a = context.obtainStyledAttributes(
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Preference);
        mAllowDividerAbove = TypedArrayUtils.getBoolean(a, R.styleable.Preference_allowDividerAbove,
                R.styleable.Preference_allowDividerAbove, false);
        mAllowDividerBelow = TypedArrayUtils.getBoolean(a, R.styleable.Preference_allowDividerBelow,
                R.styleable.Preference_allowDividerBelow, false);
        a.recycle();

        a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.Preference, 0, 0);
        int layoutResource = a.getResourceId(com.android.internal.R.styleable.Preference_layout,
                0);
        if (layoutResource == 0) {
            throw new IllegalArgumentException("LayoutPreference requires a layout to be defined");
        }
        a.recycle();

        // Need to create view now so that findViewById can be called immediately.
        final View view = LayoutInflater.from(getContext())
                .inflate(layoutResource, null, false);
@@ -78,6 +90,8 @@ public class LayoutPreference extends Preference {
        final boolean selectable = isSelectable();
        holder.itemView.setFocusable(selectable);
        holder.itemView.setClickable(selectable);
        holder.setDividerAllowedAbove(mAllowDividerAbove);
        holder.setDividerAllowedBelow(mAllowDividerBelow);

        FrameLayout layout = (FrameLayout) holder.itemView;
        layout.removeAllViews();
+136 −12
Original line number Diff line number Diff line
@@ -16,11 +16,17 @@
package com.android.settings.accounts;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;

import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.when;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
import android.content.Context;
import android.content.Intent;
import android.content.SyncAdapterType;
import android.os.UserHandle;
import android.support.v7.preference.Preference;

@@ -28,29 +34,58 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.shadow.ShadowAccountManager;
import com.android.settings.testutils.shadow.ShadowContentResolver;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;

@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
        shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
public class AccountSyncPreferenceControllerTest {

    @Mock(answer = RETURNS_DEEP_STUBS)
    private AccountManager mAccountManager;

    private Context mContext;
    private AccountSyncPreferenceController mController;
    private Preference mPreference;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        ShadowApplication application = ShadowApplication.getInstance();
        application.setSystemService(Context.ACCOUNT_SERVICE, mAccountManager);
        mContext = application.getApplicationContext();

        when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())).thenReturn(
                new AuthenticatorDescription[0]);
        when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);

        mPreference = new Preference(mContext);
        mPreference.setKey("account_sync");

        mController = new AccountSyncPreferenceController(mContext);
        mController.init(new Account("acct1", "type1"), new UserHandle(3));
    }

    @After
    public void tearDown() {
        ShadowContentResolver.reset();
    }

    @Test
    public void handlePreferenceTreeClick_shouldStartFragment() {
        final ShadowApplication application = ShadowApplication.getInstance();
        final Context context = application.getApplicationContext();
        final Preference preference = new Preference(context);
        preference.setKey("account_sync");

        final AccountSyncPreferenceController controller =
                new AccountSyncPreferenceController(context);
        controller.init(new Account("acct1", "type1"), mock(UserHandle.class));
        controller.handlePreferenceTreeClick(preference);
        mController.handlePreferenceTreeClick(mPreference);

        final Intent nextActivity = application.getNextStartedActivity();
        final Intent nextActivity = ShadowApplication.getInstance().getNextStartedActivity();

        assertThat(nextActivity.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                .isEqualTo(AccountSyncSettings.class.getName());
@@ -58,4 +93,93 @@ public class AccountSyncPreferenceControllerTest {
                .isEqualTo(R.string.account_sync_title);
    }

    @Test
    public void updateSummary_adapterInvisible_shouldNotCount() {
        SyncAdapterType syncAdapterType = new SyncAdapterType("authority" /* authority */,
                "type1" /* accountType */, false /* userVisible */, true /* supportsUploading */);
        SyncAdapterType[] syncAdapters = {syncAdapterType};
        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);

        mController.updateSummary(mPreference);

        assertThat(mPreference.getSummary())
                .isEqualTo(mContext.getString(R.string.account_sync_summary_all_off));
    }

    @Test
    public void updateSummary_notSameAccountType_shouldNotCount() {
        SyncAdapterType syncAdapterType = new SyncAdapterType("authority" /* authority */,
                "type5" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType[] syncAdapters = {syncAdapterType};
        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);

        mController.updateSummary(mPreference);

        assertThat(mPreference.getSummary())
                .isEqualTo(mContext.getString(R.string.account_sync_summary_all_off));
    }

    @Test
    public void updateSummary_notSyncable_shouldNotCount() {
        SyncAdapterType syncAdapterType = new SyncAdapterType("authority" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType[] syncAdapters = {syncAdapterType};
        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
        ShadowContentResolver.setSyncable("authority", 0);

        mController.updateSummary(mPreference);

        assertThat(mPreference.getSummary())
                .isEqualTo(mContext.getString(R.string.account_sync_summary_all_off));
    }

    @Test
    public void updateSummary_syncDisabled_shouldNotCount() {
        SyncAdapterType syncAdapterType = new SyncAdapterType("authority" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType[] syncAdapters = {syncAdapterType};
        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);
        ShadowContentResolver.setSyncAutomatically("authority", false);
        ShadowContentResolver.setMasterSyncAutomatically(3, true);

        mController.updateSummary(mPreference);

        assertThat(mPreference.getSummary())
                .isEqualTo(mContext.getString(R.string.account_sync_summary_all_off));
    }

    @Test
    public void updateSummary_syncEnabled_shouldCount() {
        SyncAdapterType syncAdapterType = new SyncAdapterType("authority" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType[] syncAdapters = {syncAdapterType};
        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);

        mController.updateSummary(mPreference);

        assertThat(mPreference.getSummary())
                .isEqualTo(mContext.getString(R.string.account_sync_summary_all_on));
    }

    @Test
    public void updateSummary_multipleSyncAdapters_shouldSetSummary() {
        SyncAdapterType syncAdapterType1 = new SyncAdapterType("authority1" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType syncAdapterType2 = new SyncAdapterType("authority2" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType syncAdapterType3 = new SyncAdapterType("authority3" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType syncAdapterType4 = new SyncAdapterType("authority4" /* authority */,
                "type1" /* accountType */, true /* userVisible */, true /* supportsUploading */);
        SyncAdapterType[] syncAdapters =
                {syncAdapterType1, syncAdapterType2, syncAdapterType3, syncAdapterType4};
        ShadowContentResolver.setSyncAdapterTypes(syncAdapters);

        ShadowContentResolver.setSyncAutomatically("authority4", false);

        mController.updateSummary(mPreference);

        assertThat(mPreference.getSummary())
                .isEqualTo(mContext.getString(R.string.account_sync_summary_some_on, 3, 4));
    }
}
+50 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.testutils.shadow;

import android.accounts.Account;
import android.content.ContentResolver;
import android.content.SyncAdapterType;

@@ -28,12 +29,20 @@ import org.robolectric.annotation.Implements;

import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;

import java.util.HashMap;
import java.util.Map;

@Implements(ContentResolver.class)
public class ShadowContentResolver {

    private static SyncAdapterType[] sSyncAdapterTypes = new SyncAdapterType[0];
    private static Map<String, Integer> sSyncable = new HashMap<>();
    private static Map<String, Boolean> sSyncAutomatically = new HashMap<>();
    private static Map<Integer, Boolean> sMasterSyncAutomatically = new HashMap<>();

    @Implementation
    public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
        return new SyncAdapterType[0];
        return sSyncAdapterTypes;
    }

    @Implementation
@@ -44,4 +53,44 @@ public class ShadowContentResolver {
                .add(SearchIndexablesContract.NonIndexableKey.COLUMN_KEY_VALUE, "");
        return cursor;
    }

    @Implementation
    public static int getIsSyncableAsUser(Account account, String authority, int userId) {
        return sSyncable.containsKey(authority) ? sSyncable.get(authority) : 1;
    }

    @Implementation
    public static boolean getSyncAutomaticallyAsUser(Account account, String authority,
            int userId) {
        return sSyncAutomatically.containsKey(authority) ? sSyncAutomatically.get(authority) : true;
    }

    @Implementation
    public static boolean getMasterSyncAutomaticallyAsUser(int userId) {
        return sMasterSyncAutomatically.containsKey(userId)
                ? sMasterSyncAutomatically.get(userId) : true;
    }

    public static void setSyncAdapterTypes(SyncAdapterType[] syncAdapterTypes) {
        sSyncAdapterTypes = syncAdapterTypes;
    }

    public static void setSyncable(String authority, int syncable) {
        sSyncable.put(authority, syncable);
    }

    public static void setSyncAutomatically(String authority, boolean syncAutomatically) {
        sSyncAutomatically.put(authority, syncAutomatically);
    }

    public static void setMasterSyncAutomatically(int userId, boolean syncAutomatically) {
        sMasterSyncAutomatically.put(userId, syncAutomatically);
    }

    public static void reset() {
        sSyncable.clear();
        sSyncAutomatically.clear();
        sMasterSyncAutomatically.clear();
        sSyncAdapterTypes = new SyncAdapterType[0];
    }
}