diff --git a/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java b/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java index cce715de524..e8d132998ad 100644 --- a/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java +++ b/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java @@ -66,9 +66,7 @@ abstract public class AbstractZenModeAutomaticRulePreferenceController extends } protected Map.Entry[] getRules() { - if (mRules == null) { - mRules = mBackend.getAutomaticZenRules(); - } + mRules = mBackend.getAutomaticZenRules(); return mRules; } diff --git a/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceController.java index ee6e8289521..1f9cf3c61be 100644 --- a/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceController.java @@ -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 mZenRulePreferences = new ArrayMap<>(); + Map.Entry[] 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[] 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 newPrefs = new ArrayMap<>(); - if (refreshPrefs) { - mPreferenceCategory.removeAll(); + if (rulesChanged) { + mSortedRules = sortedRules; + updateRules((PreferenceCategory) preference); + } + } + + private void updateRules(PreferenceCategory preferenceCategory) { + Map 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 rule) { - return new ZenRulePreference(mPreferenceCategory.getContext(), - rule, mParent, mMetricsFeatureProvider); + ZenRulePreference createZenRulePreference(Map.Entry rule, + PreferenceCategory preferenceCategory) { + return new ZenRulePreference(preferenceCategory.getContext(), + rule, mParent, mMetricsFeatureProvider, mBackend); } } diff --git a/src/com/android/settings/notification/zen/ZenModeAutomationSettings.java b/src/com/android/settings/notification/zen/ZenModeAutomationSettings.java index 4f18e5d7e02..5f317814b22 100644 --- a/src/com/android/settings/notification/zen/ZenModeAutomationSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeAutomationSettings.java @@ -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 buildPreferenceControllers(Context context, Fragment parent, ZenServiceListing serviceListing, Lifecycle lifecycle) { + ZenModeBackend backend = new ZenModeBackend(context); List 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; } diff --git a/src/com/android/settings/notification/zen/ZenRulePreference.java b/src/com/android/settings/notification/zen/ZenRulePreference.java index db06003eccd..ed65037d6ed 100644 --- a/src/com/android/settings/notification/zen/ZenRulePreference.java +++ b/src/com/android/settings/notification/zen/ZenRulePreference.java @@ -60,13 +60,15 @@ public class ZenRulePreference extends PrimarySwitchPreference { public ZenRulePreference(Context context, final Map.Entry 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); diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceControllerTest.java index 3791c23193e..9f622b18310 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAutomaticRulesPreferenceControllerTest.java @@ -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,39 +65,81 @@ 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() { + public void testDisplayThenUpdateState_onlyAddsOnceRulesUnchanged() { + final int NUM_RULES = 1; + Map 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); + + // should add 1 new preferences to mockPref + mockGetAutomaticZenRules(NUM_RULES, rMap); + mController.displayPreference(mPreferenceScreen); + mController.updateState(mPreferenceCategory); + assertEquals(NUM_RULES, mPreferenceCategory.getPreferenceCount()); + verify(mPreferenceCategory, times(1)).addPreference(any()); + } + + @Test + public void testDisplayThenUpdateState_addsIfRulesChange() { + Map 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 rMap = new HashMap<>(); @@ -109,17 +160,27 @@ public class ZenModeAutomaticRulesPreferenceControllerTest { // 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_clearsPreferencesWhenRemovingPreferences(){ + public void testUpdateState_addsAndRemoves(){ + mController.displayPreference(mPreferenceScreen); final int NUM_RULES = 2; Map 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"; @@ -131,83 +192,37 @@ public class ZenModeAutomaticRulesPreferenceControllerTest { rMap.put(ruleId1, autoRule1); rMap.put(ruleId2, autoRule2); - // 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); 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_clearsPreferencesWhenSameNumberButDifferentPrefs() { - final int NUM_RULES = 2; - Map rMap = new HashMap<>(); - - String ruleId1 = "test1_id"; - String ruleId2 = "test2_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); - - 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()); - } - - @Test - public void testUpdateState_updateEnableState() throws NoSuchFieldException { - final int NUM_RULES = 1; - Map 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); + null, null, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, true, 10); - 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 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 rules) {