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

Commit a0c9ce5b authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Remove jank on DND schedule page" into udc-dev

parents f6244e41 cb6a88b8
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -66,9 +66,7 @@ abstract public class AbstractZenModeAutomaticRulePreferenceController extends
    }

    protected Map.Entry<String, AutomaticZenRule>[] getRules() {
        if (mRules == null) {
        mRules = mBackend.getAutomaticZenRules();
        }
        return mRules;
    }

+46 −61
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.settings.notification.zen;

import android.app.AutomaticZenRule;
import android.content.Context;
import android.util.ArrayMap;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
@@ -28,23 +27,21 @@ import androidx.preference.PreferenceScreen;

import com.android.settingslib.core.lifecycle.Lifecycle;

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

public class ZenModeAutomaticRulesPreferenceController extends
        AbstractZenModeAutomaticRulePreferenceController {

    protected static final String KEY = "zen_mode_automatic_rules";

    @VisibleForTesting
    protected PreferenceCategory mPreferenceCategory;

    // Map of rule key -> preference so that we can update each preference as needed
    @VisibleForTesting
    protected Map<String, ZenRulePreference> mZenRulePreferences = new ArrayMap<>();
    Map.Entry<String, AutomaticZenRule>[] mSortedRules;

    public ZenModeAutomaticRulesPreferenceController(Context context, Fragment parent, Lifecycle
            lifecycle) {
            lifecycle, ZenModeBackend backend) {
        super(context, KEY, parent, lifecycle);
        mBackend = backend;
    }

    @Override
@@ -60,81 +57,69 @@ public class ZenModeAutomaticRulesPreferenceController extends
    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreferenceCategory = screen.findPreference(getPreferenceKey());
        mPreferenceCategory.setPersistent(false);

        // if mPreferenceCategory was un-set, make sure to clear out mZenRulePreferences too, just
        // in case
        if (mPreferenceCategory.getPreferenceCount() == 0) {
            mZenRulePreferences.clear();
        }
        PreferenceCategory preferenceCategory = screen.findPreference(getPreferenceKey());
        preferenceCategory.setPersistent(false);
        mSortedRules = getRules();
        updateRules(preferenceCategory);
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
        Map.Entry<String, AutomaticZenRule>[] sortedRules = getRules();

        // refresh the whole preference category list if the total number of rules has changed, or
        // if any individual rules have changed, so we can rebuild the list & keep things in sync
        boolean refreshPrefs = false;
        if (mPreferenceCategory.getPreferenceCount() != sortedRules.length) {
            refreshPrefs = true;
        boolean rulesChanged = false;
        if (sortedRules.length != mSortedRules.length) {
            rulesChanged = true;
        } else {
            // check whether any rules in sortedRules are not in mZenRulePreferences; that should
            // be enough to see whether something has changed
            for (int i = 0; i < sortedRules.length; i++) {
                if (!mZenRulePreferences.containsKey(sortedRules[i].getKey())) {
                    refreshPrefs = true;
            for (int i = 0; i < mSortedRules.length; i++) {
                if (!Objects.equals(mSortedRules[i].getKey(), sortedRules[i].getKey())
                || !Objects.equals(mSortedRules[i].getValue(), sortedRules[i].getValue())) {
                    rulesChanged = true;
                    break;
                }
            }
        }

        // if we need to refresh the whole list, clear the preference category and also start a
        // new map of preferences according to the preference category contents
        // we need to not update the existing one yet, as we'll need to know what preferences
        // previously existed in order to update and re-attach them to the preference category
        Map<String, ZenRulePreference> newPrefs = new ArrayMap<>();
        if (refreshPrefs) {
            mPreferenceCategory.removeAll();
        if (rulesChanged) {
            mSortedRules = sortedRules;
            updateRules((PreferenceCategory) preference);
        }
    }

    private void updateRules(PreferenceCategory preferenceCategory) {
        Map<String, ZenRulePreference> originalPreferences = new HashMap<>();
        for (int i = 0; i < preferenceCategory.getPreferenceCount(); i++) {
            ZenRulePreference pref = (ZenRulePreference) preferenceCategory.getPreference(i);
            originalPreferences.put(pref.getKey(), pref);
        }

        // Loop through each rule, either updating the existing rule or creating the rule's
        // preference if needed (and, in the case where we need to rebuild the preference category
        // list, do so as well)
        for (int i = 0; i < sortedRules.length; i++) {
            String key = sortedRules[i].getKey();
            if (mZenRulePreferences.containsKey(key)) {
                // existing rule; update its info if it's changed since the last display
                AutomaticZenRule rule = sortedRules[i].getValue();
                ZenRulePreference pref = mZenRulePreferences.get(key);
                pref.updatePreference(rule);
        // preference
        for (int i = 0; i < mSortedRules.length; i++) {
            String key = mSortedRules[i].getKey();

                // only add to preference category if the overall set of rules has changed so this
                // needs to be rearranged
                if (refreshPrefs) {
                    mPreferenceCategory.addPreference(pref);
                    newPrefs.put(key, pref);
                }
            if (originalPreferences.containsKey(key)) {
                // existing rule; update its info if it's changed since the last display
                AutomaticZenRule rule = mSortedRules[i].getValue();
                originalPreferences.get(key).updatePreference(rule);
            } else {
                // new rule; create a new ZenRulePreference & add it to the preference category
                // and the map so we'll know about it later
                ZenRulePreference pref = createZenRulePreference(sortedRules[i]);
                mPreferenceCategory.addPreference(pref);
                newPrefs.put(key, pref);
            }
                ZenRulePreference pref = createZenRulePreference(
                        mSortedRules[i], preferenceCategory);
                preferenceCategory.addPreference(pref);
            }

        // If anything was new, then make sure we overwrite mZenRulePreferences with our new data
        if (refreshPrefs) {
            mZenRulePreferences = newPrefs;
            originalPreferences.remove(key);
        }
        // Remove preferences that no longer have a rule
        for (String key : originalPreferences.keySet()) {
            preferenceCategory.removePreferenceRecursively(key);
        }
    }

    @VisibleForTesting
    ZenRulePreference createZenRulePreference(Map.Entry<String, AutomaticZenRule> rule) {
        return new ZenRulePreference(mPreferenceCategory.getContext(),
                rule, mParent, mMetricsFeatureProvider);
    ZenRulePreference createZenRulePreference(Map.Entry<String, AutomaticZenRule> rule,
            PreferenceCategory preferenceCategory) {
        return new ZenRulePreference(preferenceCategory.getContext(),
                rule, mParent, mMetricsFeatureProvider, mBackend);
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.service.notification.ConditionProviderService;
import android.view.Menu;
import android.view.MenuInflater;
@@ -59,10 +58,12 @@ public class ZenModeAutomationSettings extends ZenModeSettingsBase {

    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
            Fragment parent, ZenServiceListing serviceListing, Lifecycle lifecycle) {
        ZenModeBackend backend = new ZenModeBackend(context);
        List<AbstractPreferenceController> controllers = new ArrayList<>();
        controllers.add(new ZenModeAddAutomaticRulePreferenceController(context, parent,
                serviceListing, lifecycle));
        controllers.add(new ZenModeAutomaticRulesPreferenceController(context, parent, lifecycle));
        controllers.add(new ZenModeAutomaticRulesPreferenceController(
                context, parent, lifecycle, backend));

        return controllers;
    }
+4 −2
Original line number Diff line number Diff line
@@ -60,13 +60,15 @@ public class ZenRulePreference extends PrimarySwitchPreference {

    public ZenRulePreference(Context context,
            final Map.Entry<String, AutomaticZenRule> ruleEntry,
            Fragment parent, MetricsFeatureProvider metricsProvider) {
            Fragment parent, MetricsFeatureProvider metricsProvider,
            ZenModeBackend backend) {
        super(context);
        mBackend = ZenModeBackend.getInstance(context);
        mBackend = backend;
        mContext = context;
        mRule = ruleEntry.getValue();
        mName = mRule.getName();
        mId = ruleEntry.getKey();
        setKey(mId);
        mParent = parent;
        mPm = mContext.getPackageManager();
        mServiceListing = new ZenServiceListing(mContext, CONFIG);
+104 −89
Original line number Diff line number Diff line
@@ -17,13 +17,15 @@
package com.android.settings.notification.zen;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -31,13 +33,19 @@ import static org.mockito.Mockito.when;

import android.app.AutomaticZenRule;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;

import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.google.common.collect.ImmutableList;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,6 +55,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.util.ReflectionHelpers;

import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;

@@ -56,101 +65,122 @@ public class ZenModeAutomaticRulesPreferenceControllerTest {
    private ZenModeAutomaticRulesPreferenceController mController;
    @Mock
    private ZenModeBackend mBackend;
    @Mock
    private PreferenceCategory mockPref;
    @Mock
    private PreferenceCategory mPreferenceCategory;
    private PreferenceScreen mPreferenceScreen;
    @Mock
    private ZenRulePreference mZenRulePreference;
    private Context mContext;
    @Mock
    PackageManager mPm;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mContext = ApplicationProvider.getApplicationContext();
        mController = spy(new ZenModeAutomaticRulesPreferenceController(mContext, mock(Fragment.class),
            null));
        ReflectionHelpers.setField(mController, "mBackend", mBackend);
        when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
            mockPref);
        mController.displayPreference(mPreferenceScreen);
        doReturn(mZenRulePreference).when(mController).createZenRulePreference(any());
        mContext = spy(ApplicationProvider.getApplicationContext());
        when(mContext.getPackageManager()).thenReturn(mPm);
        when(mPm.queryIntentActivities(any(), any())).thenReturn(
                ImmutableList.of(mock(ResolveInfo.class)));
        when(mBackend.getAutomaticZenRules()).thenReturn(new Map.Entry[0]);
        mController = spy(new ZenModeAutomaticRulesPreferenceController(
                mContext, mock(Fragment.class), null, mBackend));
        final PreferenceManager preferenceManager = new PreferenceManager(mContext);
        mPreferenceScreen = preferenceManager.createPreferenceScreen(mContext);
        mPreferenceCategory = spy(new PreferenceCategory(mContext));
        mPreferenceCategory.setKey(mController.getPreferenceKey());
        mPreferenceScreen.addPreference(mPreferenceCategory);
    }

    @Test
    public void testDisplayPreference_resetsPreferencesWhenCategoryEmpty() {
        // when the PreferenceCategory is empty (no preferences), make sure we clear out any
        // stale state in the cached set of zen rule preferences
        mController.mZenRulePreferences.put("test1_id", mZenRulePreference);
        when(mockPref.getPreferenceCount()).thenReturn(0);
    public void testDisplayPreference_notPersistent() {
        mController.displayPreference(mPreferenceScreen);
        assertTrue(mController.mZenRulePreferences.isEmpty());
        assertFalse(mPreferenceCategory.isPersistent());
    }

    @Test
    public void testUpdateState_clearsPreferencesWhenAddingNewPreferences() {
        final int NUM_RULES = 3;
    public void testDisplayThenUpdateState_onlyAddsOnceRulesUnchanged() {
        final int NUM_RULES = 1;
        Map<String, AutomaticZenRule> rMap = new HashMap<>();

        String ruleId1 = "test1_id";
        String ruleId2 = "test2_id";
        String ruleId3 = "test3_id";

        AutomaticZenRule autoRule1 = new AutomaticZenRule("test_rule_1", null, null,
                null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 10);
        AutomaticZenRule autoRule2 = new AutomaticZenRule("test_rule_2", null, null,
            null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 20);
        AutomaticZenRule autoRule3 = new AutomaticZenRule("test_rule_3", null, null,
            null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 30);

        rMap.put(ruleId1, autoRule1);
        rMap.put(ruleId2, autoRule2);
        rMap.put(ruleId3, autoRule3);

        // should add 3 new preferences to mockPref
        // should add 1 new preferences to mockPref
        mockGetAutomaticZenRules(NUM_RULES, rMap);
        mController.updateState(mockPref);
        verify(mockPref, times(1)).removeAll();
        verify(mockPref, times(NUM_RULES)).addPreference(any());
        assertEquals(NUM_RULES, mController.mZenRulePreferences.size());
        mController.displayPreference(mPreferenceScreen);
        mController.updateState(mPreferenceCategory);
        assertEquals(NUM_RULES, mPreferenceCategory.getPreferenceCount());
        verify(mPreferenceCategory, times(1)).addPreference(any());
    }

    @Test
    public void testUpdateState_clearsPreferencesWhenRemovingPreferences(){
        final int NUM_RULES = 2;
    public void testDisplayThenUpdateState_addsIfRulesChange() {
        Map<String, AutomaticZenRule> rMap = new HashMap<>();

        String ruleId1 = "test1_id";
        AutomaticZenRule autoRule1 = new AutomaticZenRule("test_rule_1", null, null,
                null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 10);
        rMap.put(ruleId1, autoRule1);
        mockGetAutomaticZenRules(1, rMap);
        // adds one
        mController.displayPreference(mPreferenceScreen);

        String ruleId2 = "test2_id";
        AutomaticZenRule autoRule2 = new AutomaticZenRule("test_rule_2", null, null,
                null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 20);
        rMap.put(ruleId2, autoRule2);
        mockGetAutomaticZenRules(2, rMap);

        mController.updateState(mPreferenceCategory);
        assertEquals(2, mPreferenceCategory.getPreferenceCount());
        verify(mPreferenceCategory, times(2)).addPreference(any());
    }

    @Test
    public void testUpdateState_addingNewPreferences() {
        mController.displayPreference(mPreferenceScreen);
        final int NUM_RULES = 3;
        Map<String, AutomaticZenRule> rMap = new HashMap<>();

        String ruleId1 = "test1_id";
        String ruleId2 = "test2_id";
        String ruleId3 = "test3_id";

        AutomaticZenRule autoRule1 = new AutomaticZenRule("test_rule_1", null, null,
            null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 10);
        AutomaticZenRule autoRule2 = new AutomaticZenRule("test_rule_2", null, null,
            null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 20);
        AutomaticZenRule autoRule3 = new AutomaticZenRule("test_rule_3", null, null,
            null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 30);

        rMap.put(ruleId1, autoRule1);
        rMap.put(ruleId2, autoRule2);
        rMap.put(ruleId3, autoRule3);

        // Add three preferences to the set of previously-known-about ZenRulePreferences; in this
        // case, test3_id is "deleted"
        mController.mZenRulePreferences.put("test1_id", mZenRulePreference);
        mController.mZenRulePreferences.put("test2_id", mZenRulePreference);
        mController.mZenRulePreferences.put("test3_id", mZenRulePreference);

        // update state should re-add all preferences since a preference was deleted
        when(mockPref.getPreferenceCount()).thenReturn(NUM_RULES + 1);
        // should add 3 new preferences to mockPref
        mockGetAutomaticZenRules(NUM_RULES, rMap);
        mController.updateState(mockPref);
        verify(mockPref, times(1)).removeAll();
        verify(mockPref, times(NUM_RULES)).addPreference(any());
        assertEquals(NUM_RULES, mController.mZenRulePreferences.size());
        mController.updateState(mPreferenceCategory);
        assertEquals(NUM_RULES, mPreferenceCategory.getPreferenceCount());
    }

    @Test
    public void testUpdateState_clearsPreferencesWhenSameNumberButDifferentPrefs() {
    public void testUpdateState_addsAndRemoves(){
        mController.displayPreference(mPreferenceScreen);
        final int NUM_RULES = 2;
        Map<String, AutomaticZenRule> rMap = new HashMap<>();

        String FAKE_1 = "fake key 1";
        String FAKE_2 = "fake 2";
        mPreferenceCategory.addPreference(new ZenRulePreference(mContext,
                new AbstractMap.SimpleEntry<>(FAKE_1, mock(AutomaticZenRule.class)),
                null, null, mBackend));
        mPreferenceCategory.addPreference(new ZenRulePreference(mContext,
                new AbstractMap.SimpleEntry<>(FAKE_2, mock(AutomaticZenRule.class)),
                null, null, mBackend));
        assertNotNull(mPreferenceCategory.findPreference(FAKE_1));
        assertNotNull(mPreferenceCategory.findPreference(FAKE_2));

        String ruleId1 = "test1_id";
        String ruleId2 = "test2_id";

@@ -162,52 +192,37 @@ public class ZenModeAutomaticRulesPreferenceControllerTest {
        rMap.put(ruleId1, autoRule1);
        rMap.put(ruleId2, autoRule2);

        // Add two preferences to the set of previously-known-about ZenRulePreferences; in this
        // case, test3_id is "deleted" but test2_id is "added"
        mController.mZenRulePreferences.put("test1_id", mZenRulePreference);
        mController.mZenRulePreferences.put("test3_id", mZenRulePreference);

        // update state should re-add all preferences since a preference was deleted
        when(mockPref.getPreferenceCount()).thenReturn(NUM_RULES);
        mockGetAutomaticZenRules(NUM_RULES, rMap);
        mController.updateState(mockPref);
        verify(mockPref, times(1)).removeAll();
        verify(mockPref, times(NUM_RULES)).addPreference(any());
        assertEquals(NUM_RULES, mController.mZenRulePreferences.size());
        mController.updateState(mPreferenceCategory);
        assertNull(mPreferenceCategory.findPreference(FAKE_1));
        assertNull(mPreferenceCategory.findPreference(FAKE_2));
        assertNotNull(mPreferenceCategory.findPreference(ruleId1));
        assertNotNull(mPreferenceCategory.findPreference(ruleId2));
    }

    @Test
    public void testUpdateState_updateEnableState() throws NoSuchFieldException {
        final int NUM_RULES = 1;
        Map<String, AutomaticZenRule> rMap = new HashMap<>();
    public void testUpdateState_updateEnableState() {
        mController.displayPreference(mPreferenceScreen);
        String testId = "test1_id";
        AutomaticZenRule rule = new AutomaticZenRule("rule_name", null, null,
                null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 10);
        rMap.put(testId, rule);

        when(mockPref.getPreferenceCount()).thenReturn(NUM_RULES);
        when(mockPref.getPreference(anyInt())).thenReturn(mZenRulePreference);
        mController.mZenRulePreferences.put("test1_id", mZenRulePreference);
        mPreferenceCategory.addPreference(new ZenRulePreference(mContext,
                new AbstractMap.SimpleEntry<>(testId, rule),
                null, null, mBackend));

        // update state should NOT re-add all the preferences, should only update enable state
        rule.setEnabled(false);
        rMap.put(testId, rule);
        final int NUM_RULES = 1;
        Map<String, AutomaticZenRule> rMap = new HashMap<>();
        AutomaticZenRule ruleUpdated = new AutomaticZenRule("rule_name", null, null,
                null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 10);
        ruleUpdated.setEnabled(false);
        rMap.put(testId, ruleUpdated);
        mockGetAutomaticZenRules(NUM_RULES, rMap);
        setZenRulePreferenceField("mId", testId);
        mController.updateState(mockPref);
        verify(mZenRulePreference, times(1)).updatePreference(any());
        verify(mockPref, never()).removeAll();
        assertEquals(NUM_RULES, mController.mZenRulePreferences.size());
    }

    private void setZenRulePreferenceField(String name, Object value) {
        try {
            Field field = ZenRulePreference.class.getDeclaredField("mId");
            field.setAccessible(true);
            field.set(mZenRulePreference, value);
        } catch (ReflectiveOperationException e) {
            fail("Unable to set mZenRulePreference field: " + name);
        }
        mController.updateState(mPreferenceCategory);
        assertFalse(mPreferenceCategory.findPreference(testId).isEnabled());
        assertEquals(NUM_RULES, mPreferenceCategory.getPreferenceCount());
    }

    private void mockGetAutomaticZenRules(int numRules, Map<String, AutomaticZenRule> rules) {