Update NotificationChannelSettings Preferences in place to prevent re-layout.
(cherry picked from commit b409b640451cfc58fb529659b80cd872ed717095)
Fixes: 110093185
Test: ChannelListPreferenceControllerTest
Change-Id: If6acf305c44085e502a3304ea57e409ce049b40f
Merged-In: If6acf305c44085e502a3304ea57e409ce049b40f
diff --git a/src/com/android/settings/notification/app/ChannelListPreferenceController.java b/src/com/android/settings/notification/app/ChannelListPreferenceController.java
index 8a34672..88d960d 100644
--- a/src/com/android/settings/notification/app/ChannelListPreferenceController.java
+++ b/src/com/android/settings/notification/app/ChannelListPreferenceController.java
@@ -23,16 +23,14 @@
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.graphics.BlendMode;
-import android.graphics.BlendModeColorFilter;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
@@ -53,7 +51,8 @@
public class ChannelListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "channels";
- private static String KEY_GENERAL_CATEGORY = "categories";
+ private static final String KEY_GENERAL_CATEGORY = "categories";
+ private static final String KEY_ZERO_CATEGORIES = "zeroCategories";
public static final String ARG_FROM_SETTINGS = "fromSettings";
private List<NotificationChannelGroup> mChannelGroupList;
@@ -102,62 +101,192 @@
if (mContext == null) {
return;
}
- populateList();
+ updateFullList(mPreference, mChannelGroupList);
}
}.execute();
}
- private void populateList() {
- // TODO: if preference has children, compare with newly loaded list
- mPreference.removeAll();
-
- if (mChannelGroupList.isEmpty()) {
- PreferenceCategory groupCategory = new PreferenceCategory(mContext);
- groupCategory.setTitle(R.string.notification_channels);
- groupCategory.setKey(KEY_GENERAL_CATEGORY);
- mPreference.addPreference(groupCategory);
-
- Preference empty = new Preference(mContext);
- empty.setTitle(R.string.no_channels);
- empty.setEnabled(false);
- groupCategory.addPreference(empty);
- } else {
- populateGroupList();
- }
- }
-
- private void populateGroupList() {
- for (NotificationChannelGroup group : mChannelGroupList) {
- PreferenceCategory groupCategory = new PreferenceCategory(mContext);
- groupCategory.setOrderingAsAdded(true);
- mPreference.addPreference(groupCategory);
- if (group.getId() == null) {
- groupCategory.setTitle(R.string.notification_channels_other);
- groupCategory.setKey(KEY_GENERAL_CATEGORY);
+ /**
+ * Update the preferences group to match the
+ * @param groupPrefsList
+ * @param channelGroups
+ */
+ void updateFullList(@NonNull PreferenceCategory groupPrefsList,
+ @NonNull List<NotificationChannelGroup> channelGroups) {
+ if (channelGroups.isEmpty()) {
+ if (groupPrefsList.getPreferenceCount() == 1
+ && KEY_ZERO_CATEGORIES.equals(groupPrefsList.getPreference(0).getKey())) {
+ // Ensure the titles are correct for the current language, but otherwise leave alone
+ PreferenceGroup groupCategory = (PreferenceGroup) groupPrefsList.getPreference(0);
+ groupCategory.setTitle(R.string.notification_channels);
+ groupCategory.getPreference(0).setTitle(R.string.no_channels);
} else {
- groupCategory.setTitle(group.getName());
- groupCategory.setKey(group.getId());
- populateGroupToggle(groupCategory, group);
+ // Clear any contents and create the 'zero-categories' group.
+ groupPrefsList.removeAll();
+
+ PreferenceCategory groupCategory = new PreferenceCategory(mContext);
+ groupCategory.setTitle(R.string.notification_channels);
+ groupCategory.setKey(KEY_ZERO_CATEGORIES);
+ groupPrefsList.addPreference(groupCategory);
+
+ Preference empty = new Preference(mContext);
+ empty.setTitle(R.string.no_channels);
+ empty.setEnabled(false);
+ groupCategory.addPreference(empty);
}
- if (!group.isBlocked()) {
- final List<NotificationChannel> channels = group.getChannels();
- Collections.sort(channels, CHANNEL_COMPARATOR);
- int N = channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel channel = channels.get(i);
- // conversations get their own section
- if (TextUtils.isEmpty(channel.getConversationId()) || channel.isDemoted()) {
- populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
- }
- }
+ } else {
+ updateGroupList(groupPrefsList, channelGroups);
+ }
+ }
+
+ /**
+ * Looks for the category for the given group's key at the expected index, if that doesn't
+ * match, it checks all groups, and if it can't find that group anywhere, it creates it.
+ */
+ @NonNull
+ private PreferenceCategory findOrCreateGroupCategoryForKey(
+ @NonNull PreferenceCategory groupPrefsList, @Nullable String key, int expectedIndex) {
+ if (key == null) {
+ key = KEY_GENERAL_CATEGORY;
+ }
+ int preferenceCount = groupPrefsList.getPreferenceCount();
+ if (expectedIndex < preferenceCount) {
+ Preference preference = groupPrefsList.getPreference(expectedIndex);
+ if (key.equals(preference.getKey())) {
+ return (PreferenceCategory) preference;
+ }
+ }
+ for (int i = 0; i < preferenceCount; i++) {
+ Preference preference = groupPrefsList.getPreference(i);
+ if (key.equals(preference.getKey())) {
+ preference.setOrder(expectedIndex);
+ return (PreferenceCategory) preference;
+ }
+ }
+ PreferenceCategory groupCategory = new PreferenceCategory(mContext);
+ groupCategory.setOrder(expectedIndex);
+ groupCategory.setKey(key);
+ groupPrefsList.addPreference(groupCategory);
+ return groupCategory;
+ }
+
+ private void updateGroupList(@NonNull PreferenceCategory groupPrefsList,
+ @NonNull List<NotificationChannelGroup> channelGroups) {
+ // Update the list, but optimize for the most common case where the list hasn't changed.
+ int numFinalGroups = channelGroups.size();
+ int initialPrefCount = groupPrefsList.getPreferenceCount();
+ List<PreferenceCategory> finalOrderedGroups = new ArrayList<>(numFinalGroups);
+ for (int i = 0; i < numFinalGroups; i++) {
+ NotificationChannelGroup group = channelGroups.get(i);
+ PreferenceCategory groupCategory =
+ findOrCreateGroupCategoryForKey(groupPrefsList, group.getId(), i);
+ finalOrderedGroups.add(groupCategory);
+ updateGroupPreferences(group, groupCategory);
+ }
+ int postAddPrefCount = groupPrefsList.getPreferenceCount();
+ // If any groups were inserted (into a non-empty list) or need to be removed, we need to
+ // remove all groups and re-add them all.
+ // This is required to ensure proper ordering of inserted groups, and it simplifies logic
+ // at the cost of computation in the rare case that the list is changing.
+ boolean hasInsertions = initialPrefCount != 0 && initialPrefCount != numFinalGroups;
+ boolean requiresRemoval = postAddPrefCount != numFinalGroups;
+ if (hasInsertions || requiresRemoval) {
+ groupPrefsList.removeAll();
+ for (PreferenceCategory group : finalOrderedGroups) {
+ groupPrefsList.addPreference(group);
}
}
}
- protected void populateGroupToggle(final PreferenceGroup parent,
- NotificationChannelGroup group) {
- RestrictedSwitchPreference preference =
- new RestrictedSwitchPreference(mContext);
+ /**
+ * Looks for the channel preference for the given channel's key at the expected index, if that
+ * doesn't match, it checks all rows, and if it can't find that channel anywhere, it creates
+ * the preference.
+ */
+ @NonNull
+ private MasterSwitchPreference findOrCreateChannelPrefForKey(
+ @NonNull PreferenceGroup groupPrefGroup, @NonNull String key, int expectedIndex) {
+ int preferenceCount = groupPrefGroup.getPreferenceCount();
+ if (expectedIndex < preferenceCount) {
+ Preference preference = groupPrefGroup.getPreference(expectedIndex);
+ if (key.equals(preference.getKey())) {
+ return (MasterSwitchPreference) preference;
+ }
+ }
+ for (int i = 0; i < preferenceCount; i++) {
+ Preference preference = groupPrefGroup.getPreference(i);
+ if (key.equals(preference.getKey())) {
+ preference.setOrder(expectedIndex);
+ return (MasterSwitchPreference) preference;
+ }
+ }
+ MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
+ channelPref.setOrder(expectedIndex);
+ channelPref.setKey(key);
+ groupPrefGroup.addPreference(channelPref);
+ return channelPref;
+ }
+
+ private void updateGroupPreferences(@NonNull NotificationChannelGroup group,
+ @NonNull PreferenceGroup groupPrefGroup) {
+ int initialPrefCount = groupPrefGroup.getPreferenceCount();
+ List<Preference> finalOrderedPrefs = new ArrayList<>();
+ if (group.getId() == null) {
+ // For the 'null' group, set the "Other" title.
+ groupPrefGroup.setTitle(R.string.notification_channels_other);
+ } else {
+ // For an app-defined group, set their name and create a row to toggle 'isBlocked'.
+ groupPrefGroup.setTitle(group.getName());
+ finalOrderedPrefs.add(addOrUpdateGroupToggle(groupPrefGroup, group));
+ }
+ // Here "empty" means having no channel rows; the group toggle is ignored for this purpose.
+ boolean initiallyEmpty = groupPrefGroup.getPreferenceCount() == finalOrderedPrefs.size();
+
+ // For each channel, add or update the preference object.
+ final List<NotificationChannel> channels =
+ group.isBlocked() ? Collections.emptyList() : group.getChannels();
+ Collections.sort(channels, CHANNEL_COMPARATOR);
+ for (NotificationChannel channel : channels) {
+ if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) {
+ // conversations get their own section
+ continue;
+ }
+ // Get or create the row, and populate its current state.
+ MasterSwitchPreference channelPref = findOrCreateChannelPrefForKey(groupPrefGroup,
+ channel.getId(), /* expectedIndex */ finalOrderedPrefs.size());
+ updateSingleChannelPrefs(channelPref, channel, group.isBlocked());
+ finalOrderedPrefs.add(channelPref);
+ }
+ int postAddPrefCount = groupPrefGroup.getPreferenceCount();
+
+ // If any channels were inserted (into a non-empty list) or need to be removed, we need to
+ // remove all preferences and re-add them all.
+ // This is required to ensure proper ordering of inserted channels, and it simplifies logic
+ // at the cost of computation in the rare case that the list is changing.
+ int numFinalGroups = finalOrderedPrefs.size();
+ boolean hasInsertions = !initiallyEmpty && initialPrefCount != numFinalGroups;
+ boolean requiresRemoval = postAddPrefCount != numFinalGroups;
+ if (hasInsertions || requiresRemoval) {
+ groupPrefGroup.removeAll();
+ for (Preference preference : finalOrderedPrefs) {
+ groupPrefGroup.addPreference(preference);
+ }
+ }
+ }
+
+ /** Add or find and update the toggle for disabling the entire notification channel group. */
+ private Preference addOrUpdateGroupToggle(@NonNull final PreferenceGroup parent,
+ @NonNull final NotificationChannelGroup group) {
+ boolean shouldAdd = false;
+ final RestrictedSwitchPreference preference;
+ if (parent.getPreferenceCount() > 0
+ && parent.getPreference(0) instanceof RestrictedSwitchPreference) {
+ preference = (RestrictedSwitchPreference) parent.getPreference(0);
+ } else {
+ shouldAdd = true;
+ preference = new RestrictedSwitchPreference(mContext);
+ }
+ preference.setOrder(-1);
preference.setTitle(mContext.getString(
R.string.notification_switch_label, group.getName()));
preference.setEnabled(mAdmin == null
@@ -171,23 +300,26 @@
onGroupBlockStateChanged(group);
return true;
});
-
- parent.addPreference(preference);
+ if (shouldAdd) {
+ parent.addPreference(preference);
+ }
+ return preference;
}
- protected Preference populateSingleChannelPrefs(PreferenceGroup parent,
- final NotificationChannel channel, final boolean groupBlocked) {
- MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
+ /** Update the properties of the channel preference with the values from the channel object. */
+ private void updateSingleChannelPrefs(@NonNull final MasterSwitchPreference channelPref,
+ @NonNull final NotificationChannel channel,
+ final boolean groupBlocked) {
channelPref.setSwitchEnabled(mAdmin == null
&& isChannelBlockable(channel)
&& isChannelConfigurable(channel)
&& !groupBlocked);
- channelPref.setIcon(null);
if (channel.getImportance() > IMPORTANCE_LOW) {
channelPref.setIcon(getAlertingIcon());
+ } else {
+ channelPref.setIcon(null);
}
channelPref.setIconSize(MasterSwitchPreference.ICON_SIZE_SMALL);
- channelPref.setKey(channel.getId());
channelPref.setTitle(channel.getName());
channelPref.setSummary(NotificationBackend.getSentSummary(
mContext, mAppRow.sentByChannel.get(channel.getId()), false));
@@ -219,10 +351,6 @@
return true;
});
- if (parent.findPreference(channelPref.getKey()) == null) {
- parent.addPreference(channelPref);
- }
- return channelPref;
}
private Drawable getAlertingIcon() {
@@ -235,30 +363,9 @@
if (group == null) {
return;
}
- PreferenceGroup groupGroup = mPreference.findPreference(group.getId());
-
- if (groupGroup != null) {
- if (group.isBlocked()) {
- List<Preference> toRemove = new ArrayList<>();
- int childCount = groupGroup.getPreferenceCount();
- for (int i = 0; i < childCount; i++) {
- Preference pref = groupGroup.getPreference(i);
- if (pref instanceof MasterSwitchPreference) {
- toRemove.add(pref);
- }
- }
- for (Preference pref : toRemove) {
- groupGroup.removePreference(pref);
- }
- } else {
- final List<NotificationChannel> channels = group.getChannels();
- Collections.sort(channels, CHANNEL_COMPARATOR);
- int N = channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel channel = channels.get(i);
- populateSingleChannelPrefs(groupGroup, channel, group.isBlocked());
- }
- }
+ PreferenceGroup groupPrefGroup = mPreference.findPreference(group.getId());
+ if (groupPrefGroup != null) {
+ updateGroupPreferences(group, groupPrefGroup);
}
}
}
diff --git a/src/com/android/settings/widget/MasterSwitchPreference.java b/src/com/android/settings/widget/MasterSwitchPreference.java
index 9fe077e..7221035 100644
--- a/src/com/android/settings/widget/MasterSwitchPreference.java
+++ b/src/com/android/settings/widget/MasterSwitchPreference.java
@@ -23,6 +23,8 @@
import android.view.View.OnClickListener;
import android.widget.Switch;
+import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
@@ -101,6 +103,16 @@
return mSwitch != null && mChecked;
}
+ /**
+ * Used to validate the state of mChecked and mCheckedSet when testing, without requiring
+ * that a ViewHolder be bound to the object.
+ */
+ @Keep
+ @Nullable
+ public Boolean getCheckedState() {
+ return mCheckedSet ? mChecked : null;
+ }
+
public void setChecked(boolean checked) {
// Always set checked the first time; don't assume the field's default of false.
final boolean changed = mChecked != checked;
diff --git a/tests/unit/src/com/android/settings/notification/app/ChannelListPreferenceControllerTest.java b/tests/unit/src/com/android/settings/notification/app/ChannelListPreferenceControllerTest.java
new file mode 100644
index 0000000..f9c8132
--- /dev/null
+++ b/tests/unit/src/com/android/settings/notification/app/ChannelListPreferenceControllerTest.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2020 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.notification.app;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+
+import android.app.Instrumentation;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.settings.notification.NotificationBackend;
+import com.android.settings.notification.NotificationBackend.NotificationsSentState;
+import com.android.settings.widget.MasterSwitchPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ChannelListPreferenceControllerTest {
+ private Context mContext;
+ private NotificationBackend mBackend;
+ private NotificationBackend.AppRow mAppRow;
+ private ChannelListPreferenceController mController;
+ private PreferenceManager mPreferenceManager;
+ private PreferenceScreen mPreferenceScreen;
+ private PreferenceCategory mGroupList;
+
+ @Before
+ public void setUp() throws Exception {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = instrumentation.getTargetContext();
+
+ instrumentation.runOnMainSync(() -> {
+ mBackend = new NotificationBackend();
+ mAppRow = mBackend.loadAppRow(mContext,
+ mContext.getPackageManager(), mContext.getApplicationInfo());
+ mController = new ChannelListPreferenceController(mContext, mBackend);
+ mController.onResume(mAppRow, null, null, null, null, null);
+ mPreferenceManager = new PreferenceManager(mContext);
+ mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
+ mGroupList = new PreferenceCategory(mContext);
+ mPreferenceScreen.addPreference(mGroupList);
+ });
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUpdateFullList_incrementalUpdates() {
+ // Start by testing the case with no groups or channels
+ List<NotificationChannelGroup> inGroups = new ArrayList<>();
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ assertEquals("zeroCategories", mGroupList.getPreference(0).getKey());
+ }
+
+ // Test that adding a group clears the zero category and adds everything
+ NotificationChannelGroup inGroup1 = new NotificationChannelGroup("group1", "Group 1");
+ inGroup1.addChannel(new NotificationChannel("ch1a", "Channel 1A", IMPORTANCE_DEFAULT));
+ inGroups.add(inGroup1);
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group1", group1.getKey());
+ assertEquals(2, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ }
+
+ // Test that adding a channel works -- no dupes or omissions
+ inGroup1.addChannel(new NotificationChannel("ch1b", "Channel 1B", IMPORTANCE_DEFAULT));
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group1", group1.getKey());
+ assertEquals(3, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ assertEquals("ch1b", group1.getPreference(2).getKey());
+ assertEquals("Channel 1B", group1.getPreference(2).getTitle());
+ }
+
+ // Test that renaming a channel does in fact rename the preferences
+ inGroup1.getChannels().get(1).setName("Channel 1B - Renamed");
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group1", group1.getKey());
+ assertEquals(3, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ assertEquals("ch1b", group1.getPreference(2).getKey());
+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
+ }
+
+ // Test that adding a group works and results in the correct sorting.
+ NotificationChannelGroup inGroup0 = new NotificationChannelGroup("group0", "Group 0");
+ inGroup0.addChannel(new NotificationChannel("ch0b", "Channel 0B", IMPORTANCE_DEFAULT));
+ // NOTE: updateFullList takes a List which has been sorted, so we insert at 0 for this check
+ inGroups.add(0, inGroup0);
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(2, mGroupList.getPreferenceCount());
+ PreferenceGroup group0 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group0", group0.getKey());
+ assertEquals(2, group0.getPreferenceCount());
+ assertNull(group0.getPreference(0).getKey());
+ assertEquals("All \"Group 0\" notifications", group0.getPreference(0).getTitle());
+ assertEquals("ch0b", group0.getPreference(1).getKey());
+ assertEquals("Channel 0B", group0.getPreference(1).getTitle());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(1);
+ assertEquals("group1", group1.getKey());
+ assertEquals(3, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ assertEquals("ch1b", group1.getPreference(2).getKey());
+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
+ }
+
+ // Test that adding a channel that comes before another works and has correct ordering.
+ // NOTE: the channels within a group are sorted inside updateFullList.
+ inGroup0.addChannel(new NotificationChannel("ch0a", "Channel 0A", IMPORTANCE_DEFAULT));
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(2, mGroupList.getPreferenceCount());
+ PreferenceGroup group0 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group0", group0.getKey());
+ assertEquals(3, group0.getPreferenceCount());
+ assertNull(group0.getPreference(0).getKey());
+ assertEquals("All \"Group 0\" notifications", group0.getPreference(0).getTitle());
+ assertEquals("ch0a", group0.getPreference(1).getKey());
+ assertEquals("Channel 0A", group0.getPreference(1).getTitle());
+ assertEquals("ch0b", group0.getPreference(2).getKey());
+ assertEquals("Channel 0B", group0.getPreference(2).getTitle());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(1);
+ assertEquals("group1", group1.getKey());
+ assertEquals(3, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ assertEquals("ch1b", group1.getPreference(2).getKey());
+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
+ }
+
+ // Test that the "Other" group works.
+ // Also test a simultaneous addition and deletion.
+ inGroups.remove(inGroup0);
+ NotificationChannelGroup inGroupOther = new NotificationChannelGroup(null, null);
+ inGroupOther.addChannel(new NotificationChannel("chXa", "Other A", IMPORTANCE_DEFAULT));
+ inGroupOther.addChannel(new NotificationChannel("chXb", "Other B", IMPORTANCE_DEFAULT));
+ inGroups.add(inGroupOther);
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(2, mGroupList.getPreferenceCount());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group1", group1.getKey());
+ assertEquals(3, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ assertEquals("ch1b", group1.getPreference(2).getKey());
+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
+ PreferenceGroup groupOther = (PreferenceGroup) mGroupList.getPreference(1);
+ assertEquals("categories", groupOther.getKey());
+ assertEquals(2, groupOther.getPreferenceCount());
+ assertEquals("chXa", groupOther.getPreference(0).getKey());
+ assertEquals("Other A", groupOther.getPreference(0).getTitle());
+ assertEquals("chXb", groupOther.getPreference(1).getKey());
+ assertEquals("Other B", groupOther.getPreference(1).getTitle());
+ }
+
+ // Test that the removal of a channel works.
+ inGroupOther.getChannels().remove(0);
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(2, mGroupList.getPreferenceCount());
+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group1", group1.getKey());
+ assertEquals(3, group1.getPreferenceCount());
+ assertNull(group1.getPreference(0).getKey());
+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
+ assertEquals("ch1a", group1.getPreference(1).getKey());
+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
+ assertEquals("ch1b", group1.getPreference(2).getKey());
+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
+ PreferenceGroup groupOther = (PreferenceGroup) mGroupList.getPreference(1);
+ assertEquals("categories", groupOther.getKey());
+ assertEquals(1, groupOther.getPreferenceCount());
+ assertEquals("chXb", groupOther.getPreference(0).getKey());
+ assertEquals("Other B", groupOther.getPreference(0).getTitle());
+ }
+
+ // Test that we go back to the empty state when clearing all groups and channels.
+ inGroups.clear();
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ assertEquals("zeroCategories", mGroupList.getPreference(0).getKey());
+ }
+ }
+
+
+ @Test
+ @UiThreadTest
+ public void testUpdateFullList_groupBlockedChange() {
+ List<NotificationChannelGroup> inGroups = new ArrayList<>();
+ NotificationChannelGroup inGroup = new NotificationChannelGroup("group", "My Group");
+ inGroup.addChannel(new NotificationChannel("channelA", "Channel A", IMPORTANCE_DEFAULT));
+ inGroup.addChannel(new NotificationChannel("channelB", "Channel B", IMPORTANCE_NONE));
+ inGroups.add(inGroup);
+
+ // Test that the group is initially showing all preferences
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group", group.getKey());
+ assertEquals(3, group.getPreferenceCount());
+ SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
+ assertNull(groupBlockPref.getKey());
+ assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle());
+ assertTrue(groupBlockPref.isChecked());
+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
+ assertEquals("channelA", channelAPref.getKey());
+ assertEquals("Channel A", channelAPref.getTitle());
+ assertEquals(Boolean.TRUE, channelAPref.getCheckedState());
+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
+ assertEquals("channelB", channelBPref.getKey());
+ assertEquals("Channel B", channelBPref.getTitle());
+ assertEquals(Boolean.FALSE, channelBPref.getCheckedState());
+ }
+
+ // Test that when a group is blocked, the list removes its individual channel preferences
+ inGroup.setBlocked(true);
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group", group.getKey());
+ assertEquals(1, group.getPreferenceCount());
+ SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
+ assertNull(groupBlockPref.getKey());
+ assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle());
+ assertFalse(groupBlockPref.isChecked());
+ }
+
+ // Test that when a group is unblocked, the list adds its individual channel preferences
+ inGroup.setBlocked(false);
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group", group.getKey());
+ assertEquals(3, group.getPreferenceCount());
+ SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
+ assertNull(groupBlockPref.getKey());
+ assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle());
+ assertTrue(groupBlockPref.isChecked());
+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
+ assertEquals("channelA", channelAPref.getKey());
+ assertEquals("Channel A", channelAPref.getTitle());
+ assertEquals(Boolean.TRUE, channelAPref.getCheckedState());
+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
+ assertEquals("channelB", channelBPref.getKey());
+ assertEquals("Channel B", channelBPref.getTitle());
+ assertEquals(Boolean.FALSE, channelBPref.getCheckedState());
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUpdateFullList_channelUpdates() {
+ List<NotificationChannelGroup> inGroups = new ArrayList<>();
+ NotificationChannelGroup inGroup = new NotificationChannelGroup("group", "Group");
+ NotificationChannel channelA =
+ new NotificationChannel("channelA", "Channel A", IMPORTANCE_HIGH);
+ NotificationChannel channelB =
+ new NotificationChannel("channelB", "Channel B", IMPORTANCE_NONE);
+ inGroup.addChannel(channelA);
+ inGroup.addChannel(channelB);
+ inGroups.add(inGroup);
+
+ NotificationsSentState sentA = new NotificationsSentState();
+ sentA.avgSentDaily = 2;
+ sentA.avgSentWeekly = 10;
+ NotificationsSentState sentB = new NotificationsSentState();
+ sentB.avgSentDaily = 0;
+ sentB.avgSentWeekly = 2;
+ mAppRow.sentByChannel.put("channelA", sentA);
+
+ // Test that the channels' properties are reflected in the preference
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group", group.getKey());
+ assertEquals(3, group.getPreferenceCount());
+ assertNull(group.getPreference(0).getKey());
+ assertEquals("All \"Group\" notifications", group.getPreference(0).getTitle());
+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
+ assertEquals("channelA", channelAPref.getKey());
+ assertEquals("Channel A", channelAPref.getTitle());
+ assertEquals(Boolean.TRUE, channelAPref.getCheckedState());
+ assertEquals("~2 notifications per day", channelAPref.getSummary());
+ assertNotNull(channelAPref.getIcon());
+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
+ assertEquals("channelB", channelBPref.getKey());
+ assertEquals("Channel B", channelBPref.getTitle());
+ assertEquals(Boolean.FALSE, channelBPref.getCheckedState());
+ assertNull(channelBPref.getSummary());
+ assertNull(channelBPref.getIcon());
+ }
+
+ channelA.setImportance(IMPORTANCE_NONE);
+ channelB.setImportance(IMPORTANCE_DEFAULT);
+
+ mAppRow.sentByChannel.remove("channelA");
+ mAppRow.sentByChannel.put("channelB", sentB);
+
+ // Test that changing the channels' properties correctly updates the preference
+ mController.updateFullList(mGroupList, inGroups);
+ {
+ assertEquals(1, mGroupList.getPreferenceCount());
+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
+ assertEquals("group", group.getKey());
+ assertEquals(3, group.getPreferenceCount());
+ assertNull(group.getPreference(0).getKey());
+ assertEquals("All \"Group\" notifications", group.getPreference(0).getTitle());
+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
+ assertEquals("channelA", channelAPref.getKey());
+ assertEquals("Channel A", channelAPref.getTitle());
+ assertEquals(Boolean.FALSE, channelAPref.getCheckedState());
+ assertNull(channelAPref.getSummary());
+ assertNull(channelAPref.getIcon());
+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
+ assertEquals("channelB", channelBPref.getKey());
+ assertEquals("Channel B", channelBPref.getTitle());
+ assertEquals(Boolean.TRUE, channelBPref.getCheckedState());
+ assertEquals("~2 notifications per week", channelBPref.getSummary());
+ assertNotNull(channelBPref.getIcon());
+ }
+ }
+
+}