• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

packages/apps/Settings


Commit MetaInfo

Revisão5bfe1d3f9c8900e9ab196767fefe183a19389db1 (tree)
Hora2020-10-13 09:06:48
AutorJeff DeCew <jeffdq@goog...>
CommiterJeff DeCew

Mensagem de Log

Update NotificationChannelSettings Preferences in place to prevent re-layout.

(cherry picked from commit b409b640451cfc58fb529659b80cd872ed717095)

Fixes: 110093185
Test: ChannelListPreferenceControllerTest
Change-Id: If6acf305c44085e502a3304ea57e409ce049b40f
Merged-In: If6acf305c44085e502a3304ea57e409ce049b40f

Mudança Sumário

Diff

--- 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.NotificationChannel;
2323 import android.app.NotificationChannelGroup;
2424 import android.app.settings.SettingsEnums;
2525 import android.content.Context;
26-import android.graphics.BlendMode;
27-import android.graphics.BlendModeColorFilter;
2826 import android.graphics.drawable.Drawable;
29-import android.graphics.drawable.GradientDrawable;
30-import android.graphics.drawable.LayerDrawable;
3127 import android.os.AsyncTask;
3228 import android.os.Bundle;
3329 import android.provider.Settings;
3430 import android.text.TextUtils;
3531
32+import androidx.annotation.NonNull;
33+import androidx.annotation.Nullable;
3634 import androidx.preference.Preference;
3735 import androidx.preference.PreferenceCategory;
3836 import androidx.preference.PreferenceGroup;
@@ -53,7 +51,8 @@ import java.util.List;
5351 public class ChannelListPreferenceController extends NotificationPreferenceController {
5452
5553 private static final String KEY = "channels";
56- private static String KEY_GENERAL_CATEGORY = "categories";
54+ private static final String KEY_GENERAL_CATEGORY = "categories";
55+ private static final String KEY_ZERO_CATEGORIES = "zeroCategories";
5756 public static final String ARG_FROM_SETTINGS = "fromSettings";
5857
5958 private List<NotificationChannelGroup> mChannelGroupList;
@@ -102,62 +101,192 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
102101 if (mContext == null) {
103102 return;
104103 }
105- populateList();
104+ updateFullList(mPreference, mChannelGroupList);
106105 }
107106 }.execute();
108107 }
109108
110- private void populateList() {
111- // TODO: if preference has children, compare with newly loaded list
112- mPreference.removeAll();
109+ /**
110+ * Update the preferences group to match the
111+ * @param groupPrefsList
112+ * @param channelGroups
113+ */
114+ void updateFullList(@NonNull PreferenceCategory groupPrefsList,
115+ @NonNull List<NotificationChannelGroup> channelGroups) {
116+ if (channelGroups.isEmpty()) {
117+ if (groupPrefsList.getPreferenceCount() == 1
118+ && KEY_ZERO_CATEGORIES.equals(groupPrefsList.getPreference(0).getKey())) {
119+ // Ensure the titles are correct for the current language, but otherwise leave alone
120+ PreferenceGroup groupCategory = (PreferenceGroup) groupPrefsList.getPreference(0);
121+ groupCategory.setTitle(R.string.notification_channels);
122+ groupCategory.getPreference(0).setTitle(R.string.no_channels);
123+ } else {
124+ // Clear any contents and create the 'zero-categories' group.
125+ groupPrefsList.removeAll();
113126
114- if (mChannelGroupList.isEmpty()) {
115- PreferenceCategory groupCategory = new PreferenceCategory(mContext);
116- groupCategory.setTitle(R.string.notification_channels);
117- groupCategory.setKey(KEY_GENERAL_CATEGORY);
118- mPreference.addPreference(groupCategory);
127+ PreferenceCategory groupCategory = new PreferenceCategory(mContext);
128+ groupCategory.setTitle(R.string.notification_channels);
129+ groupCategory.setKey(KEY_ZERO_CATEGORIES);
130+ groupPrefsList.addPreference(groupCategory);
119131
120- Preference empty = new Preference(mContext);
121- empty.setTitle(R.string.no_channels);
122- empty.setEnabled(false);
123- groupCategory.addPreference(empty);
132+ Preference empty = new Preference(mContext);
133+ empty.setTitle(R.string.no_channels);
134+ empty.setEnabled(false);
135+ groupCategory.addPreference(empty);
136+ }
124137 } else {
125- populateGroupList();
138+ updateGroupList(groupPrefsList, channelGroups);
126139 }
127140 }
128141
129- private void populateGroupList() {
130- for (NotificationChannelGroup group : mChannelGroupList) {
131- PreferenceCategory groupCategory = new PreferenceCategory(mContext);
132- groupCategory.setOrderingAsAdded(true);
133- mPreference.addPreference(groupCategory);
134- if (group.getId() == null) {
135- groupCategory.setTitle(R.string.notification_channels_other);
136- groupCategory.setKey(KEY_GENERAL_CATEGORY);
137- } else {
138- groupCategory.setTitle(group.getName());
139- groupCategory.setKey(group.getId());
140- populateGroupToggle(groupCategory, group);
142+ /**
143+ * Looks for the category for the given group's key at the expected index, if that doesn't
144+ * match, it checks all groups, and if it can't find that group anywhere, it creates it.
145+ */
146+ @NonNull
147+ private PreferenceCategory findOrCreateGroupCategoryForKey(
148+ @NonNull PreferenceCategory groupPrefsList, @Nullable String key, int expectedIndex) {
149+ if (key == null) {
150+ key = KEY_GENERAL_CATEGORY;
151+ }
152+ int preferenceCount = groupPrefsList.getPreferenceCount();
153+ if (expectedIndex < preferenceCount) {
154+ Preference preference = groupPrefsList.getPreference(expectedIndex);
155+ if (key.equals(preference.getKey())) {
156+ return (PreferenceCategory) preference;
141157 }
142- if (!group.isBlocked()) {
143- final List<NotificationChannel> channels = group.getChannels();
144- Collections.sort(channels, CHANNEL_COMPARATOR);
145- int N = channels.size();
146- for (int i = 0; i < N; i++) {
147- final NotificationChannel channel = channels.get(i);
148- // conversations get their own section
149- if (TextUtils.isEmpty(channel.getConversationId()) || channel.isDemoted()) {
150- populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
151- }
152- }
158+ }
159+ for (int i = 0; i < preferenceCount; i++) {
160+ Preference preference = groupPrefsList.getPreference(i);
161+ if (key.equals(preference.getKey())) {
162+ preference.setOrder(expectedIndex);
163+ return (PreferenceCategory) preference;
164+ }
165+ }
166+ PreferenceCategory groupCategory = new PreferenceCategory(mContext);
167+ groupCategory.setOrder(expectedIndex);
168+ groupCategory.setKey(key);
169+ groupPrefsList.addPreference(groupCategory);
170+ return groupCategory;
171+ }
172+
173+ private void updateGroupList(@NonNull PreferenceCategory groupPrefsList,
174+ @NonNull List<NotificationChannelGroup> channelGroups) {
175+ // Update the list, but optimize for the most common case where the list hasn't changed.
176+ int numFinalGroups = channelGroups.size();
177+ int initialPrefCount = groupPrefsList.getPreferenceCount();
178+ List<PreferenceCategory> finalOrderedGroups = new ArrayList<>(numFinalGroups);
179+ for (int i = 0; i < numFinalGroups; i++) {
180+ NotificationChannelGroup group = channelGroups.get(i);
181+ PreferenceCategory groupCategory =
182+ findOrCreateGroupCategoryForKey(groupPrefsList, group.getId(), i);
183+ finalOrderedGroups.add(groupCategory);
184+ updateGroupPreferences(group, groupCategory);
185+ }
186+ int postAddPrefCount = groupPrefsList.getPreferenceCount();
187+ // If any groups were inserted (into a non-empty list) or need to be removed, we need to
188+ // remove all groups and re-add them all.
189+ // This is required to ensure proper ordering of inserted groups, and it simplifies logic
190+ // at the cost of computation in the rare case that the list is changing.
191+ boolean hasInsertions = initialPrefCount != 0 && initialPrefCount != numFinalGroups;
192+ boolean requiresRemoval = postAddPrefCount != numFinalGroups;
193+ if (hasInsertions || requiresRemoval) {
194+ groupPrefsList.removeAll();
195+ for (PreferenceCategory group : finalOrderedGroups) {
196+ groupPrefsList.addPreference(group);
153197 }
154198 }
155199 }
156200
157- protected void populateGroupToggle(final PreferenceGroup parent,
158- NotificationChannelGroup group) {
159- RestrictedSwitchPreference preference =
160- new RestrictedSwitchPreference(mContext);
201+ /**
202+ * Looks for the channel preference for the given channel's key at the expected index, if that
203+ * doesn't match, it checks all rows, and if it can't find that channel anywhere, it creates
204+ * the preference.
205+ */
206+ @NonNull
207+ private MasterSwitchPreference findOrCreateChannelPrefForKey(
208+ @NonNull PreferenceGroup groupPrefGroup, @NonNull String key, int expectedIndex) {
209+ int preferenceCount = groupPrefGroup.getPreferenceCount();
210+ if (expectedIndex < preferenceCount) {
211+ Preference preference = groupPrefGroup.getPreference(expectedIndex);
212+ if (key.equals(preference.getKey())) {
213+ return (MasterSwitchPreference) preference;
214+ }
215+ }
216+ for (int i = 0; i < preferenceCount; i++) {
217+ Preference preference = groupPrefGroup.getPreference(i);
218+ if (key.equals(preference.getKey())) {
219+ preference.setOrder(expectedIndex);
220+ return (MasterSwitchPreference) preference;
221+ }
222+ }
223+ MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
224+ channelPref.setOrder(expectedIndex);
225+ channelPref.setKey(key);
226+ groupPrefGroup.addPreference(channelPref);
227+ return channelPref;
228+ }
229+
230+ private void updateGroupPreferences(@NonNull NotificationChannelGroup group,
231+ @NonNull PreferenceGroup groupPrefGroup) {
232+ int initialPrefCount = groupPrefGroup.getPreferenceCount();
233+ List<Preference> finalOrderedPrefs = new ArrayList<>();
234+ if (group.getId() == null) {
235+ // For the 'null' group, set the "Other" title.
236+ groupPrefGroup.setTitle(R.string.notification_channels_other);
237+ } else {
238+ // For an app-defined group, set their name and create a row to toggle 'isBlocked'.
239+ groupPrefGroup.setTitle(group.getName());
240+ finalOrderedPrefs.add(addOrUpdateGroupToggle(groupPrefGroup, group));
241+ }
242+ // Here "empty" means having no channel rows; the group toggle is ignored for this purpose.
243+ boolean initiallyEmpty = groupPrefGroup.getPreferenceCount() == finalOrderedPrefs.size();
244+
245+ // For each channel, add or update the preference object.
246+ final List<NotificationChannel> channels =
247+ group.isBlocked() ? Collections.emptyList() : group.getChannels();
248+ Collections.sort(channels, CHANNEL_COMPARATOR);
249+ for (NotificationChannel channel : channels) {
250+ if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) {
251+ // conversations get their own section
252+ continue;
253+ }
254+ // Get or create the row, and populate its current state.
255+ MasterSwitchPreference channelPref = findOrCreateChannelPrefForKey(groupPrefGroup,
256+ channel.getId(), /* expectedIndex */ finalOrderedPrefs.size());
257+ updateSingleChannelPrefs(channelPref, channel, group.isBlocked());
258+ finalOrderedPrefs.add(channelPref);
259+ }
260+ int postAddPrefCount = groupPrefGroup.getPreferenceCount();
261+
262+ // If any channels were inserted (into a non-empty list) or need to be removed, we need to
263+ // remove all preferences and re-add them all.
264+ // This is required to ensure proper ordering of inserted channels, and it simplifies logic
265+ // at the cost of computation in the rare case that the list is changing.
266+ int numFinalGroups = finalOrderedPrefs.size();
267+ boolean hasInsertions = !initiallyEmpty && initialPrefCount != numFinalGroups;
268+ boolean requiresRemoval = postAddPrefCount != numFinalGroups;
269+ if (hasInsertions || requiresRemoval) {
270+ groupPrefGroup.removeAll();
271+ for (Preference preference : finalOrderedPrefs) {
272+ groupPrefGroup.addPreference(preference);
273+ }
274+ }
275+ }
276+
277+ /** Add or find and update the toggle for disabling the entire notification channel group. */
278+ private Preference addOrUpdateGroupToggle(@NonNull final PreferenceGroup parent,
279+ @NonNull final NotificationChannelGroup group) {
280+ boolean shouldAdd = false;
281+ final RestrictedSwitchPreference preference;
282+ if (parent.getPreferenceCount() > 0
283+ && parent.getPreference(0) instanceof RestrictedSwitchPreference) {
284+ preference = (RestrictedSwitchPreference) parent.getPreference(0);
285+ } else {
286+ shouldAdd = true;
287+ preference = new RestrictedSwitchPreference(mContext);
288+ }
289+ preference.setOrder(-1);
161290 preference.setTitle(mContext.getString(
162291 R.string.notification_switch_label, group.getName()));
163292 preference.setEnabled(mAdmin == null
@@ -171,23 +300,26 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
171300 onGroupBlockStateChanged(group);
172301 return true;
173302 });
174-
175- parent.addPreference(preference);
303+ if (shouldAdd) {
304+ parent.addPreference(preference);
305+ }
306+ return preference;
176307 }
177308
178- protected Preference populateSingleChannelPrefs(PreferenceGroup parent,
179- final NotificationChannel channel, final boolean groupBlocked) {
180- MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
309+ /** Update the properties of the channel preference with the values from the channel object. */
310+ private void updateSingleChannelPrefs(@NonNull final MasterSwitchPreference channelPref,
311+ @NonNull final NotificationChannel channel,
312+ final boolean groupBlocked) {
181313 channelPref.setSwitchEnabled(mAdmin == null
182314 && isChannelBlockable(channel)
183315 && isChannelConfigurable(channel)
184316 && !groupBlocked);
185- channelPref.setIcon(null);
186317 if (channel.getImportance() > IMPORTANCE_LOW) {
187318 channelPref.setIcon(getAlertingIcon());
319+ } else {
320+ channelPref.setIcon(null);
188321 }
189322 channelPref.setIconSize(MasterSwitchPreference.ICON_SIZE_SMALL);
190- channelPref.setKey(channel.getId());
191323 channelPref.setTitle(channel.getName());
192324 channelPref.setSummary(NotificationBackend.getSentSummary(
193325 mContext, mAppRow.sentByChannel.get(channel.getId()), false));
@@ -219,10 +351,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
219351
220352 return true;
221353 });
222- if (parent.findPreference(channelPref.getKey()) == null) {
223- parent.addPreference(channelPref);
224- }
225- return channelPref;
226354 }
227355
228356 private Drawable getAlertingIcon() {
@@ -235,30 +363,9 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
235363 if (group == null) {
236364 return;
237365 }
238- PreferenceGroup groupGroup = mPreference.findPreference(group.getId());
239-
240- if (groupGroup != null) {
241- if (group.isBlocked()) {
242- List<Preference> toRemove = new ArrayList<>();
243- int childCount = groupGroup.getPreferenceCount();
244- for (int i = 0; i < childCount; i++) {
245- Preference pref = groupGroup.getPreference(i);
246- if (pref instanceof MasterSwitchPreference) {
247- toRemove.add(pref);
248- }
249- }
250- for (Preference pref : toRemove) {
251- groupGroup.removePreference(pref);
252- }
253- } else {
254- final List<NotificationChannel> channels = group.getChannels();
255- Collections.sort(channels, CHANNEL_COMPARATOR);
256- int N = channels.size();
257- for (int i = 0; i < N; i++) {
258- final NotificationChannel channel = channels.get(i);
259- populateSingleChannelPrefs(groupGroup, channel, group.isBlocked());
260- }
261- }
366+ PreferenceGroup groupPrefGroup = mPreference.findPreference(group.getId());
367+ if (groupPrefGroup != null) {
368+ updateGroupPreferences(group, groupPrefGroup);
262369 }
263370 }
264371 }
--- a/src/com/android/settings/widget/MasterSwitchPreference.java
+++ b/src/com/android/settings/widget/MasterSwitchPreference.java
@@ -23,6 +23,8 @@ import android.view.View;
2323 import android.view.View.OnClickListener;
2424 import android.widget.Switch;
2525
26+import androidx.annotation.Keep;
27+import androidx.annotation.Nullable;
2628 import androidx.preference.PreferenceViewHolder;
2729
2830 import com.android.settings.R;
@@ -101,6 +103,16 @@ public class MasterSwitchPreference extends RestrictedPreference {
101103 return mSwitch != null && mChecked;
102104 }
103105
106+ /**
107+ * Used to validate the state of mChecked and mCheckedSet when testing, without requiring
108+ * that a ViewHolder be bound to the object.
109+ */
110+ @Keep
111+ @Nullable
112+ public Boolean getCheckedState() {
113+ return mCheckedSet ? mChecked : null;
114+ }
115+
104116 public void setChecked(boolean checked) {
105117 // Always set checked the first time; don't assume the field's default of false.
106118 final boolean changed = mChecked != checked;
--- /dev/null
+++ b/tests/unit/src/com/android/settings/notification/app/ChannelListPreferenceControllerTest.java
@@ -0,0 +1,395 @@
1+/*
2+ * Copyright (C) 2020 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.android.settings.notification.app;
18+
19+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
20+import static android.app.NotificationManager.IMPORTANCE_HIGH;
21+import static android.app.NotificationManager.IMPORTANCE_NONE;
22+
23+import static junit.framework.TestCase.assertEquals;
24+import static junit.framework.TestCase.assertFalse;
25+import static junit.framework.TestCase.assertNotNull;
26+import static junit.framework.TestCase.assertNull;
27+import static junit.framework.TestCase.assertTrue;
28+
29+import android.app.Instrumentation;
30+import android.app.NotificationChannel;
31+import android.app.NotificationChannelGroup;
32+import android.content.Context;
33+
34+import androidx.preference.PreferenceCategory;
35+import androidx.preference.PreferenceGroup;
36+import androidx.preference.PreferenceManager;
37+import androidx.preference.PreferenceScreen;
38+import androidx.preference.SwitchPreference;
39+import androidx.test.annotation.UiThreadTest;
40+import androidx.test.filters.SmallTest;
41+import androidx.test.platform.app.InstrumentationRegistry;
42+import androidx.test.runner.AndroidJUnit4;
43+
44+import com.android.settings.notification.NotificationBackend;
45+import com.android.settings.notification.NotificationBackend.NotificationsSentState;
46+import com.android.settings.widget.MasterSwitchPreference;
47+
48+import org.junit.Before;
49+import org.junit.Test;
50+import org.junit.runner.RunWith;
51+
52+import java.util.ArrayList;
53+import java.util.List;
54+
55+@RunWith(AndroidJUnit4.class)
56+@SmallTest
57+public class ChannelListPreferenceControllerTest {
58+ private Context mContext;
59+ private NotificationBackend mBackend;
60+ private NotificationBackend.AppRow mAppRow;
61+ private ChannelListPreferenceController mController;
62+ private PreferenceManager mPreferenceManager;
63+ private PreferenceScreen mPreferenceScreen;
64+ private PreferenceCategory mGroupList;
65+
66+ @Before
67+ public void setUp() throws Exception {
68+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
69+ mContext = instrumentation.getTargetContext();
70+
71+ instrumentation.runOnMainSync(() -> {
72+ mBackend = new NotificationBackend();
73+ mAppRow = mBackend.loadAppRow(mContext,
74+ mContext.getPackageManager(), mContext.getApplicationInfo());
75+ mController = new ChannelListPreferenceController(mContext, mBackend);
76+ mController.onResume(mAppRow, null, null, null, null, null);
77+ mPreferenceManager = new PreferenceManager(mContext);
78+ mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
79+ mGroupList = new PreferenceCategory(mContext);
80+ mPreferenceScreen.addPreference(mGroupList);
81+ });
82+ }
83+
84+ @Test
85+ @UiThreadTest
86+ public void testUpdateFullList_incrementalUpdates() {
87+ // Start by testing the case with no groups or channels
88+ List<NotificationChannelGroup> inGroups = new ArrayList<>();
89+ mController.updateFullList(mGroupList, inGroups);
90+ {
91+ assertEquals(1, mGroupList.getPreferenceCount());
92+ assertEquals("zeroCategories", mGroupList.getPreference(0).getKey());
93+ }
94+
95+ // Test that adding a group clears the zero category and adds everything
96+ NotificationChannelGroup inGroup1 = new NotificationChannelGroup("group1", "Group 1");
97+ inGroup1.addChannel(new NotificationChannel("ch1a", "Channel 1A", IMPORTANCE_DEFAULT));
98+ inGroups.add(inGroup1);
99+ mController.updateFullList(mGroupList, inGroups);
100+ {
101+ assertEquals(1, mGroupList.getPreferenceCount());
102+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
103+ assertEquals("group1", group1.getKey());
104+ assertEquals(2, group1.getPreferenceCount());
105+ assertNull(group1.getPreference(0).getKey());
106+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
107+ assertEquals("ch1a", group1.getPreference(1).getKey());
108+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
109+ }
110+
111+ // Test that adding a channel works -- no dupes or omissions
112+ inGroup1.addChannel(new NotificationChannel("ch1b", "Channel 1B", IMPORTANCE_DEFAULT));
113+ mController.updateFullList(mGroupList, inGroups);
114+ {
115+ assertEquals(1, mGroupList.getPreferenceCount());
116+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
117+ assertEquals("group1", group1.getKey());
118+ assertEquals(3, group1.getPreferenceCount());
119+ assertNull(group1.getPreference(0).getKey());
120+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
121+ assertEquals("ch1a", group1.getPreference(1).getKey());
122+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
123+ assertEquals("ch1b", group1.getPreference(2).getKey());
124+ assertEquals("Channel 1B", group1.getPreference(2).getTitle());
125+ }
126+
127+ // Test that renaming a channel does in fact rename the preferences
128+ inGroup1.getChannels().get(1).setName("Channel 1B - Renamed");
129+ mController.updateFullList(mGroupList, inGroups);
130+ {
131+ assertEquals(1, mGroupList.getPreferenceCount());
132+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
133+ assertEquals("group1", group1.getKey());
134+ assertEquals(3, group1.getPreferenceCount());
135+ assertNull(group1.getPreference(0).getKey());
136+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
137+ assertEquals("ch1a", group1.getPreference(1).getKey());
138+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
139+ assertEquals("ch1b", group1.getPreference(2).getKey());
140+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
141+ }
142+
143+ // Test that adding a group works and results in the correct sorting.
144+ NotificationChannelGroup inGroup0 = new NotificationChannelGroup("group0", "Group 0");
145+ inGroup0.addChannel(new NotificationChannel("ch0b", "Channel 0B", IMPORTANCE_DEFAULT));
146+ // NOTE: updateFullList takes a List which has been sorted, so we insert at 0 for this check
147+ inGroups.add(0, inGroup0);
148+ mController.updateFullList(mGroupList, inGroups);
149+ {
150+ assertEquals(2, mGroupList.getPreferenceCount());
151+ PreferenceGroup group0 = (PreferenceGroup) mGroupList.getPreference(0);
152+ assertEquals("group0", group0.getKey());
153+ assertEquals(2, group0.getPreferenceCount());
154+ assertNull(group0.getPreference(0).getKey());
155+ assertEquals("All \"Group 0\" notifications", group0.getPreference(0).getTitle());
156+ assertEquals("ch0b", group0.getPreference(1).getKey());
157+ assertEquals("Channel 0B", group0.getPreference(1).getTitle());
158+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(1);
159+ assertEquals("group1", group1.getKey());
160+ assertEquals(3, group1.getPreferenceCount());
161+ assertNull(group1.getPreference(0).getKey());
162+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
163+ assertEquals("ch1a", group1.getPreference(1).getKey());
164+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
165+ assertEquals("ch1b", group1.getPreference(2).getKey());
166+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
167+ }
168+
169+ // Test that adding a channel that comes before another works and has correct ordering.
170+ // NOTE: the channels within a group are sorted inside updateFullList.
171+ inGroup0.addChannel(new NotificationChannel("ch0a", "Channel 0A", IMPORTANCE_DEFAULT));
172+ mController.updateFullList(mGroupList, inGroups);
173+ {
174+ assertEquals(2, mGroupList.getPreferenceCount());
175+ PreferenceGroup group0 = (PreferenceGroup) mGroupList.getPreference(0);
176+ assertEquals("group0", group0.getKey());
177+ assertEquals(3, group0.getPreferenceCount());
178+ assertNull(group0.getPreference(0).getKey());
179+ assertEquals("All \"Group 0\" notifications", group0.getPreference(0).getTitle());
180+ assertEquals("ch0a", group0.getPreference(1).getKey());
181+ assertEquals("Channel 0A", group0.getPreference(1).getTitle());
182+ assertEquals("ch0b", group0.getPreference(2).getKey());
183+ assertEquals("Channel 0B", group0.getPreference(2).getTitle());
184+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(1);
185+ assertEquals("group1", group1.getKey());
186+ assertEquals(3, group1.getPreferenceCount());
187+ assertNull(group1.getPreference(0).getKey());
188+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
189+ assertEquals("ch1a", group1.getPreference(1).getKey());
190+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
191+ assertEquals("ch1b", group1.getPreference(2).getKey());
192+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
193+ }
194+
195+ // Test that the "Other" group works.
196+ // Also test a simultaneous addition and deletion.
197+ inGroups.remove(inGroup0);
198+ NotificationChannelGroup inGroupOther = new NotificationChannelGroup(null, null);
199+ inGroupOther.addChannel(new NotificationChannel("chXa", "Other A", IMPORTANCE_DEFAULT));
200+ inGroupOther.addChannel(new NotificationChannel("chXb", "Other B", IMPORTANCE_DEFAULT));
201+ inGroups.add(inGroupOther);
202+ mController.updateFullList(mGroupList, inGroups);
203+ {
204+ assertEquals(2, mGroupList.getPreferenceCount());
205+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
206+ assertEquals("group1", group1.getKey());
207+ assertEquals(3, group1.getPreferenceCount());
208+ assertNull(group1.getPreference(0).getKey());
209+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
210+ assertEquals("ch1a", group1.getPreference(1).getKey());
211+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
212+ assertEquals("ch1b", group1.getPreference(2).getKey());
213+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
214+ PreferenceGroup groupOther = (PreferenceGroup) mGroupList.getPreference(1);
215+ assertEquals("categories", groupOther.getKey());
216+ assertEquals(2, groupOther.getPreferenceCount());
217+ assertEquals("chXa", groupOther.getPreference(0).getKey());
218+ assertEquals("Other A", groupOther.getPreference(0).getTitle());
219+ assertEquals("chXb", groupOther.getPreference(1).getKey());
220+ assertEquals("Other B", groupOther.getPreference(1).getTitle());
221+ }
222+
223+ // Test that the removal of a channel works.
224+ inGroupOther.getChannels().remove(0);
225+ mController.updateFullList(mGroupList, inGroups);
226+ {
227+ assertEquals(2, mGroupList.getPreferenceCount());
228+ PreferenceGroup group1 = (PreferenceGroup) mGroupList.getPreference(0);
229+ assertEquals("group1", group1.getKey());
230+ assertEquals(3, group1.getPreferenceCount());
231+ assertNull(group1.getPreference(0).getKey());
232+ assertEquals("All \"Group 1\" notifications", group1.getPreference(0).getTitle());
233+ assertEquals("ch1a", group1.getPreference(1).getKey());
234+ assertEquals("Channel 1A", group1.getPreference(1).getTitle());
235+ assertEquals("ch1b", group1.getPreference(2).getKey());
236+ assertEquals("Channel 1B - Renamed", group1.getPreference(2).getTitle());
237+ PreferenceGroup groupOther = (PreferenceGroup) mGroupList.getPreference(1);
238+ assertEquals("categories", groupOther.getKey());
239+ assertEquals(1, groupOther.getPreferenceCount());
240+ assertEquals("chXb", groupOther.getPreference(0).getKey());
241+ assertEquals("Other B", groupOther.getPreference(0).getTitle());
242+ }
243+
244+ // Test that we go back to the empty state when clearing all groups and channels.
245+ inGroups.clear();
246+ mController.updateFullList(mGroupList, inGroups);
247+ {
248+ assertEquals(1, mGroupList.getPreferenceCount());
249+ assertEquals("zeroCategories", mGroupList.getPreference(0).getKey());
250+ }
251+ }
252+
253+
254+ @Test
255+ @UiThreadTest
256+ public void testUpdateFullList_groupBlockedChange() {
257+ List<NotificationChannelGroup> inGroups = new ArrayList<>();
258+ NotificationChannelGroup inGroup = new NotificationChannelGroup("group", "My Group");
259+ inGroup.addChannel(new NotificationChannel("channelA", "Channel A", IMPORTANCE_DEFAULT));
260+ inGroup.addChannel(new NotificationChannel("channelB", "Channel B", IMPORTANCE_NONE));
261+ inGroups.add(inGroup);
262+
263+ // Test that the group is initially showing all preferences
264+ mController.updateFullList(mGroupList, inGroups);
265+ {
266+ assertEquals(1, mGroupList.getPreferenceCount());
267+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
268+ assertEquals("group", group.getKey());
269+ assertEquals(3, group.getPreferenceCount());
270+ SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
271+ assertNull(groupBlockPref.getKey());
272+ assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle());
273+ assertTrue(groupBlockPref.isChecked());
274+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
275+ assertEquals("channelA", channelAPref.getKey());
276+ assertEquals("Channel A", channelAPref.getTitle());
277+ assertEquals(Boolean.TRUE, channelAPref.getCheckedState());
278+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
279+ assertEquals("channelB", channelBPref.getKey());
280+ assertEquals("Channel B", channelBPref.getTitle());
281+ assertEquals(Boolean.FALSE, channelBPref.getCheckedState());
282+ }
283+
284+ // Test that when a group is blocked, the list removes its individual channel preferences
285+ inGroup.setBlocked(true);
286+ mController.updateFullList(mGroupList, inGroups);
287+ {
288+ assertEquals(1, mGroupList.getPreferenceCount());
289+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
290+ assertEquals("group", group.getKey());
291+ assertEquals(1, group.getPreferenceCount());
292+ SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
293+ assertNull(groupBlockPref.getKey());
294+ assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle());
295+ assertFalse(groupBlockPref.isChecked());
296+ }
297+
298+ // Test that when a group is unblocked, the list adds its individual channel preferences
299+ inGroup.setBlocked(false);
300+ mController.updateFullList(mGroupList, inGroups);
301+ {
302+ assertEquals(1, mGroupList.getPreferenceCount());
303+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
304+ assertEquals("group", group.getKey());
305+ assertEquals(3, group.getPreferenceCount());
306+ SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
307+ assertNull(groupBlockPref.getKey());
308+ assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle());
309+ assertTrue(groupBlockPref.isChecked());
310+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
311+ assertEquals("channelA", channelAPref.getKey());
312+ assertEquals("Channel A", channelAPref.getTitle());
313+ assertEquals(Boolean.TRUE, channelAPref.getCheckedState());
314+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
315+ assertEquals("channelB", channelBPref.getKey());
316+ assertEquals("Channel B", channelBPref.getTitle());
317+ assertEquals(Boolean.FALSE, channelBPref.getCheckedState());
318+ }
319+ }
320+
321+ @Test
322+ @UiThreadTest
323+ public void testUpdateFullList_channelUpdates() {
324+ List<NotificationChannelGroup> inGroups = new ArrayList<>();
325+ NotificationChannelGroup inGroup = new NotificationChannelGroup("group", "Group");
326+ NotificationChannel channelA =
327+ new NotificationChannel("channelA", "Channel A", IMPORTANCE_HIGH);
328+ NotificationChannel channelB =
329+ new NotificationChannel("channelB", "Channel B", IMPORTANCE_NONE);
330+ inGroup.addChannel(channelA);
331+ inGroup.addChannel(channelB);
332+ inGroups.add(inGroup);
333+
334+ NotificationsSentState sentA = new NotificationsSentState();
335+ sentA.avgSentDaily = 2;
336+ sentA.avgSentWeekly = 10;
337+ NotificationsSentState sentB = new NotificationsSentState();
338+ sentB.avgSentDaily = 0;
339+ sentB.avgSentWeekly = 2;
340+ mAppRow.sentByChannel.put("channelA", sentA);
341+
342+ // Test that the channels' properties are reflected in the preference
343+ mController.updateFullList(mGroupList, inGroups);
344+ {
345+ assertEquals(1, mGroupList.getPreferenceCount());
346+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
347+ assertEquals("group", group.getKey());
348+ assertEquals(3, group.getPreferenceCount());
349+ assertNull(group.getPreference(0).getKey());
350+ assertEquals("All \"Group\" notifications", group.getPreference(0).getTitle());
351+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
352+ assertEquals("channelA", channelAPref.getKey());
353+ assertEquals("Channel A", channelAPref.getTitle());
354+ assertEquals(Boolean.TRUE, channelAPref.getCheckedState());
355+ assertEquals("~2 notifications per day", channelAPref.getSummary());
356+ assertNotNull(channelAPref.getIcon());
357+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
358+ assertEquals("channelB", channelBPref.getKey());
359+ assertEquals("Channel B", channelBPref.getTitle());
360+ assertEquals(Boolean.FALSE, channelBPref.getCheckedState());
361+ assertNull(channelBPref.getSummary());
362+ assertNull(channelBPref.getIcon());
363+ }
364+
365+ channelA.setImportance(IMPORTANCE_NONE);
366+ channelB.setImportance(IMPORTANCE_DEFAULT);
367+
368+ mAppRow.sentByChannel.remove("channelA");
369+ mAppRow.sentByChannel.put("channelB", sentB);
370+
371+ // Test that changing the channels' properties correctly updates the preference
372+ mController.updateFullList(mGroupList, inGroups);
373+ {
374+ assertEquals(1, mGroupList.getPreferenceCount());
375+ PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
376+ assertEquals("group", group.getKey());
377+ assertEquals(3, group.getPreferenceCount());
378+ assertNull(group.getPreference(0).getKey());
379+ assertEquals("All \"Group\" notifications", group.getPreference(0).getTitle());
380+ MasterSwitchPreference channelAPref = (MasterSwitchPreference) group.getPreference(1);
381+ assertEquals("channelA", channelAPref.getKey());
382+ assertEquals("Channel A", channelAPref.getTitle());
383+ assertEquals(Boolean.FALSE, channelAPref.getCheckedState());
384+ assertNull(channelAPref.getSummary());
385+ assertNull(channelAPref.getIcon());
386+ MasterSwitchPreference channelBPref = (MasterSwitchPreference) group.getPreference(2);
387+ assertEquals("channelB", channelBPref.getKey());
388+ assertEquals("Channel B", channelBPref.getTitle());
389+ assertEquals(Boolean.TRUE, channelBPref.getCheckedState());
390+ assertEquals("~2 notifications per week", channelBPref.getSummary());
391+ assertNotNull(channelBPref.getIcon());
392+ }
393+ }
394+
395+}