blob: 882504bdb5e4f7640cb778f1b458db863af862c6 [file] [log] [blame]
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001/*
2 * Copyright (C) 2019 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
17package com.android.systemui.bubbles;
18
Lyn Han72f50902019-10-25 15:55:49 -070019import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
20
Mark Renouf9ba6cea2019-04-17 11:53:50 -040021import static com.google.common.truth.Truth.assertThat;
22
Mark Renouf9ba6cea2019-04-17 11:53:50 -040023import static org.mockito.Mockito.mock;
Mark Renoufba5ab512019-05-02 15:21:01 -040024import static org.mockito.Mockito.reset;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040025import static org.mockito.Mockito.verify;
Mark Renouf82a40e62019-05-23 16:16:24 -040026import static org.mockito.Mockito.verifyZeroInteractions;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040027import static org.mockito.Mockito.when;
28
29import android.app.Notification;
30import android.app.PendingIntent;
31import android.graphics.drawable.Icon;
32import android.os.UserHandle;
33import android.service.notification.StatusBarNotification;
34import android.testing.AndroidTestingRunner;
35import android.testing.TestableLooper;
Mark Renouf82a40e62019-05-23 16:16:24 -040036import android.util.Pair;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040037
38import androidx.test.filters.SmallTest;
39
40import com.android.systemui.SysuiTestCase;
41import com.android.systemui.bubbles.BubbleData.TimeSource;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040042import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Beverly79c89ec2019-12-13 10:33:01 -050043import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
Mady Mellorb25f9062019-12-09 12:12:57 -080044import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Kevin Han933dc7c2020-01-29 11:17:46 -080045import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040046
47import com.google.common.collect.ImmutableList;
48
49import org.junit.Before;
50import org.junit.Test;
51import org.junit.runner.RunWith;
Mark Renouf82a40e62019-05-23 16:16:24 -040052import org.mockito.ArgumentCaptor;
53import org.mockito.Captor;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040054import org.mockito.Mock;
55import org.mockito.MockitoAnnotations;
56
Mark Renouf82a40e62019-05-23 16:16:24 -040057/**
58 * Tests operations and the resulting state managed by BubbleData.
59 * <p>
60 * After each operation to verify, {@link #verifyUpdateReceived()} ensures the listener was called
61 * and captures the Update object received there.
62 * <p>
63 * Other methods beginning with 'assert' access the captured update object and assert on specific
64 * aspects of it.
65 */
Mark Renouf9ba6cea2019-04-17 11:53:50 -040066@SmallTest
67@RunWith(AndroidTestingRunner.class)
68@TestableLooper.RunWithLooper(setAsMainLooper = true)
69public class BubbleDataTest extends SysuiTestCase {
70
71 private NotificationEntry mEntryA1;
72 private NotificationEntry mEntryA2;
73 private NotificationEntry mEntryA3;
74 private NotificationEntry mEntryB1;
75 private NotificationEntry mEntryB2;
76 private NotificationEntry mEntryB3;
77 private NotificationEntry mEntryC1;
Lyn Han72f50902019-10-25 15:55:49 -070078 private NotificationEntry mEntryInterruptive;
79 private NotificationEntry mEntryDismissed;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040080
81 private Bubble mBubbleA1;
82 private Bubble mBubbleA2;
83 private Bubble mBubbleA3;
84 private Bubble mBubbleB1;
85 private Bubble mBubbleB2;
86 private Bubble mBubbleB3;
87 private Bubble mBubbleC1;
Mady Mellor3df7ab02019-12-09 15:07:10 -080088 private Bubble mBubbleInterruptive;
89 private Bubble mBubbleDismissed;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040090
91 private BubbleData mBubbleData;
92
93 @Mock
94 private TimeSource mTimeSource;
95 @Mock
96 private BubbleData.Listener mListener;
97 @Mock
98 private PendingIntent mExpandIntent;
99 @Mock
100 private PendingIntent mDeleteIntent;
101
102 private NotificationTestHelper mNotificationTestHelper;
103
Mark Renouf82a40e62019-05-23 16:16:24 -0400104 @Captor
105 private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
106
Mady Mellorf44b6832020-01-14 13:26:14 -0800107 @Mock
108 private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
109
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400110 @Before
111 public void setUp() throws Exception {
Kevin Hana7c21be2020-04-01 17:58:35 -0700112 mNotificationTestHelper = new NotificationTestHelper(
113 mContext,
114 mDependency,
115 TestableLooper.get(this));
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400116 MockitoAnnotations.initMocks(this);
117
118 mEntryA1 = createBubbleEntry(1, "a1", "package.a");
119 mEntryA2 = createBubbleEntry(1, "a2", "package.a");
120 mEntryA3 = createBubbleEntry(1, "a3", "package.a");
121 mEntryB1 = createBubbleEntry(1, "b1", "package.b");
122 mEntryB2 = createBubbleEntry(1, "b2", "package.b");
123 mEntryB3 = createBubbleEntry(1, "b3", "package.b");
124 mEntryC1 = createBubbleEntry(1, "c1", "package.c");
125
Lyn Han72f50902019-10-25 15:55:49 -0700126 mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d");
127 modifyRanking(mEntryInterruptive)
128 .setVisuallyInterruptive(true)
129 .build();
Mady Mellorf44b6832020-01-14 13:26:14 -0800130 mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener);
Lyn Han72f50902019-10-25 15:55:49 -0700131
Mady Mellorb25f9062019-12-09 12:12:57 -0800132 ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
Lyn Han72f50902019-10-25 15:55:49 -0700133 mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
Mady Mellorb25f9062019-12-09 12:12:57 -0800134 mEntryDismissed.setRow(row);
Mady Mellorf44b6832020-01-14 13:26:14 -0800135 mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener);
Lyn Han72f50902019-10-25 15:55:49 -0700136
Mady Mellorf44b6832020-01-14 13:26:14 -0800137 mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener);
138 mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener);
139 mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener);
140 mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener);
141 mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener);
142 mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener);
143 mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400144
145 mBubbleData = new BubbleData(getContext());
146
147 // Used by BubbleData to set lastAccessedTime
148 when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
149 mBubbleData.setTimeSource(mTimeSource);
Mark Renoufba5ab512019-05-02 15:21:01 -0400150
151 // Assert baseline starting state
152 assertThat(mBubbleData.hasBubbles()).isFalse();
153 assertThat(mBubbleData.isExpanded()).isFalse();
154 assertThat(mBubbleData.getSelectedBubble()).isNull();
155 }
156
157 @Test
158 public void testAddBubble() {
159 // Setup
160 mBubbleData.setListener(mListener);
161
162 // Test
163 sendUpdatedEntryAtTime(mEntryA1, 1000);
164
165 // Verify
Mark Renouf82a40e62019-05-23 16:16:24 -0400166 verifyUpdateReceived();
167 assertBubbleAdded(mBubbleA1);
168 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400169 }
170
171 @Test
172 public void testRemoveBubble() {
173 // Setup
174 sendUpdatedEntryAtTime(mEntryA1, 1000);
175 sendUpdatedEntryAtTime(mEntryA2, 2000);
176 sendUpdatedEntryAtTime(mEntryA3, 3000);
177 mBubbleData.setListener(mListener);
178
179 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700180 mBubbleData.notificationEntryRemoved(
181 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renoufba5ab512019-05-02 15:21:01 -0400182
183 // Verify
Mark Renouf82a40e62019-05-23 16:16:24 -0400184 verifyUpdateReceived();
185 assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renoufba5ab512019-05-02 15:21:01 -0400186 }
187
Lyn Han72f50902019-10-25 15:55:49 -0700188 @Test
189 public void ifSuppress_hideFlyout() {
190 // Setup
191 mBubbleData.setListener(mListener);
192
193 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800194 mBubbleData.notificationEntryUpdated(mBubbleC1, /* suppressFlyout */ true, /* showInShade */
Lyn Han72f50902019-10-25 15:55:49 -0700195 true);
196
197 // Verify
198 verifyUpdateReceived();
199 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellorb8aaf972019-11-26 10:28:00 -0800200 assertThat(update.addedBubble.showFlyout()).isFalse();
Lyn Han72f50902019-10-25 15:55:49 -0700201 }
202
203 @Test
204 public void ifInterruptiveAndNotSuppressed_thenShowFlyout() {
205 // Setup
206 mBubbleData.setListener(mListener);
207
208 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800209 mBubbleData.notificationEntryUpdated(mBubbleInterruptive,
Mady Mellorb25f9062019-12-09 12:12:57 -0800210 false /* suppressFlyout */, true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700211
212 // Verify
213 verifyUpdateReceived();
214 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellorb8aaf972019-11-26 10:28:00 -0800215 assertThat(update.addedBubble.showFlyout()).isTrue();
Lyn Han72f50902019-10-25 15:55:49 -0700216 }
217
218 @Test
219 public void sameUpdate_InShade_thenHideFlyout() {
220 // Setup
221 mBubbleData.setListener(mListener);
222
223 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800224 mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
Mady Mellorb25f9062019-12-09 12:12:57 -0800225 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700226 verifyUpdateReceived();
227
Mady Mellor3df7ab02019-12-09 15:07:10 -0800228 mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
Mady Mellorb25f9062019-12-09 12:12:57 -0800229 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700230 verifyUpdateReceived();
231
232 // Verify
233 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellorb8aaf972019-11-26 10:28:00 -0800234 assertThat(update.updatedBubble.showFlyout()).isFalse();
Lyn Han72f50902019-10-25 15:55:49 -0700235 }
236
237 @Test
Mady Mellor8e97a962020-01-21 16:20:15 -0800238 public void sameUpdate_NotInShade_NotVisuallyInterruptive_dontShowFlyout() {
Lyn Han72f50902019-10-25 15:55:49 -0700239 // Setup
240 mBubbleData.setListener(mListener);
Lyn Han72f50902019-10-25 15:55:49 -0700241
242 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800243 mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
244 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700245 verifyUpdateReceived();
246
Mady Mellorf44b6832020-01-14 13:26:14 -0800247 // Suppress the notif / make it look dismissed
248 mBubbleDismissed.setSuppressNotification(true);
Mady Mellorb25f9062019-12-09 12:12:57 -0800249
Mady Mellor3df7ab02019-12-09 15:07:10 -0800250 mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
251 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700252 verifyUpdateReceived();
253
254 // Verify
255 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellor8e97a962020-01-21 16:20:15 -0800256 assertThat(update.updatedBubble.showFlyout()).isFalse();
Lyn Han72f50902019-10-25 15:55:49 -0700257 }
258
Mark Renoufba5ab512019-05-02 15:21:01 -0400259 // COLLAPSED / ADD
260
261 /**
262 * Verifies that the number of bubbles is not allowed to exceed the maximum. The limit is
263 * enforced by expiring the bubble which was least recently updated (lowest timestamp).
264 */
265 @Test
Lyn Hanb58c7562020-01-07 14:29:20 -0800266 public void test_collapsed_addBubble_atMaxBubbles_overflowsOldest() {
Mark Renoufba5ab512019-05-02 15:21:01 -0400267 // Setup
268 sendUpdatedEntryAtTime(mEntryA1, 1000);
269 sendUpdatedEntryAtTime(mEntryA2, 2000);
270 sendUpdatedEntryAtTime(mEntryA3, 3000);
271 sendUpdatedEntryAtTime(mEntryB1, 4000);
272 sendUpdatedEntryAtTime(mEntryB2, 5000);
273 mBubbleData.setListener(mListener);
274
Mark Renoufba5ab512019-05-02 15:21:01 -0400275 sendUpdatedEntryAtTime(mEntryC1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400276 verifyUpdateReceived();
277 assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED);
Lyn Hanbf1b3d62020-03-12 10:31:19 -0700278 assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
279
280 Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1);
281 bubbleA1.markUpdatedAt(7000L);
282 mBubbleData.notificationEntryUpdated(bubbleA1, false /* suppressFlyout*/,
283 true /* showInShade */);
284 verifyUpdateReceived();
285 assertBubbleRemoved(mBubbleA2, BubbleController.DISMISS_AGED);
286 assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
Mark Renoufba5ab512019-05-02 15:21:01 -0400287 }
288
Lyn Han2f6e89d2020-04-15 10:01:01 -0700289 @Test
290 public void testOverflowBubble_maxReached_bubbleRemoved() {
291 // Setup
292 sendUpdatedEntryAtTime(mEntryA1, 1000);
293 sendUpdatedEntryAtTime(mEntryA2, 2000);
294 sendUpdatedEntryAtTime(mEntryA3, 3000);
295 mBubbleData.setListener(mListener);
296
297 mBubbleData.setMaxOverflowBubbles(1);
Pinyao Ting293b83d2020-05-06 17:10:56 -0700298 mBubbleData.notificationEntryRemoved(
299 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700300 verifyUpdateReceived();
301 assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
302
303 // Overflow max of 1 is reached; A1 is oldest, so it gets removed
Pinyao Ting293b83d2020-05-06 17:10:56 -0700304 mBubbleData.notificationEntryRemoved(
305 mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700306 verifyUpdateReceived();
307 assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
308 }
309
Mark Renoufba5ab512019-05-02 15:21:01 -0400310 /**
311 * Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
312 * <p>
313 * Placement within the list is based on lastUpdate (post time of the notification), descending
314 * order (with most recent first).
315 *
316 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
317 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
318 */
319 @Test
320 public void test_collapsed_addBubble_sortAndGrouping() {
321 // Setup
322 mBubbleData.setListener(mListener);
323
324 // Test
325 sendUpdatedEntryAtTime(mEntryA1, 1000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400326 verifyUpdateReceived();
327 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400328
Mark Renoufba5ab512019-05-02 15:21:01 -0400329 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400330 verifyUpdateReceived();
331 assertOrderChangedTo(mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400332
Mark Renoufba5ab512019-05-02 15:21:01 -0400333 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400334 verifyUpdateReceived();
335 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400336
Mark Renoufba5ab512019-05-02 15:21:01 -0400337 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400338 verifyUpdateReceived();
339 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400340 }
341
342 /**
343 * Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
344 * Additionally, any bubble which is ongoing is considered "newer" than any non-ongoing bubble.
345 * <p>
346 * Because of the ongoing bubble, the new bubble cannot be placed in the first position. This
347 * causes the 'B' group to remain last, despite having a new button added.
348 *
349 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
350 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
351 */
352 @Test
353 public void test_collapsed_addBubble_sortAndGrouping_withOngoing() {
354 // Setup
355 mBubbleData.setListener(mListener);
356
357 // Test
358 setOngoing(mEntryA1, true);
359 sendUpdatedEntryAtTime(mEntryA1, 1000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400360 verifyUpdateReceived();
361 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400362
Mark Renoufba5ab512019-05-02 15:21:01 -0400363 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400364 verifyUpdateReceived();
365 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400366
Mark Renoufba5ab512019-05-02 15:21:01 -0400367 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400368 verifyUpdateReceived();
369 assertOrderChangedTo(mBubbleA1, mBubbleB2, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400370
Mark Renoufba5ab512019-05-02 15:21:01 -0400371 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400372 verifyUpdateReceived();
373 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400374 }
375
376 /**
377 * Verifies that new bubbles become the selected bubble when they appear when the stack is in
378 * the collapsed state.
379 *
380 * @see #test_collapsed_updateBubble_selectionChanges()
381 * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
382 */
383 @Test
384 public void test_collapsed_addBubble_selectionChanges() {
385 // Setup
386 mBubbleData.setListener(mListener);
387
388 // Test
389 sendUpdatedEntryAtTime(mEntryA1, 1000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400390 verifyUpdateReceived();
391 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400392
Mark Renoufba5ab512019-05-02 15:21:01 -0400393 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400394 verifyUpdateReceived();
395 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400396
Mark Renoufba5ab512019-05-02 15:21:01 -0400397 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400398 verifyUpdateReceived();
399 assertSelectionChangedTo(mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400400
Mark Renoufba5ab512019-05-02 15:21:01 -0400401 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400402 verifyUpdateReceived();
403 assertSelectionChangedTo(mBubbleA2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400404 }
Mark Renouf82a40e62019-05-23 16:16:24 -0400405
Mark Renoufba5ab512019-05-02 15:21:01 -0400406 /**
407 * Verifies that while collapsed, the selection will not change if the selected bubble is
408 * ongoing. It remains the top bubble and as such remains selected.
409 *
410 * @see #test_collapsed_addBubble_selectionChanges()
411 */
412 @Test
413 public void test_collapsed_addBubble_noSelectionChanges_withOngoing() {
414 // Setup
415 setOngoing(mEntryA1, true);
416 sendUpdatedEntryAtTime(mEntryA1, 1000);
417 assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
418 mBubbleData.setListener(mListener);
419
420 // Test
421 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400422 verifyUpdateReceived();
423 assertSelectionNotChanged();
424
Mark Renoufba5ab512019-05-02 15:21:01 -0400425 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400426 verifyUpdateReceived();
427 assertSelectionNotChanged();
428
Mark Renoufba5ab512019-05-02 15:21:01 -0400429 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400430 verifyUpdateReceived();
431 assertSelectionNotChanged();
432
Mark Renoufba5ab512019-05-02 15:21:01 -0400433 assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1); // selection unchanged
434 }
435
436 // COLLAPSED / REMOVE
437
438 /**
439 * Verifies that groups may reorder when bubbles are removed, while the stack is in the
440 * collapsed state.
441 */
442 @Test
443 public void test_collapsed_removeBubble_sortAndGrouping() {
444 // Setup
445 sendUpdatedEntryAtTime(mEntryA1, 1000);
446 sendUpdatedEntryAtTime(mEntryB1, 2000);
447 sendUpdatedEntryAtTime(mEntryB2, 3000);
448 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
449 mBubbleData.setListener(mListener);
450
451 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700452 mBubbleData.notificationEntryRemoved(
453 mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400454 verifyUpdateReceived();
455 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400456 }
457
458
459 /**
460 * Verifies that onOrderChanged is not called when a bubble is removed if the removal does not
461 * cause other bubbles to change position.
462 */
463 @Test
464 public void test_collapsed_removeOldestBubble_doesNotCallOnOrderChanged() {
465 // Setup
466 sendUpdatedEntryAtTime(mEntryA1, 1000);
467 sendUpdatedEntryAtTime(mEntryB1, 2000);
468 sendUpdatedEntryAtTime(mEntryB2, 3000);
469 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
470 mBubbleData.setListener(mListener);
471
472 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700473 mBubbleData.notificationEntryRemoved(
474 mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400475 verifyUpdateReceived();
476 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400477 }
478
479 /**
480 * Verifies that bubble ordering reverts to normal when an ongoing bubble is removed. A group
481 * which has a newer bubble may move to the front after the ongoing bubble is removed.
482 */
483 @Test
484 public void test_collapsed_removeBubble_sortAndGrouping_withOngoing() {
485 // Setup
486 setOngoing(mEntryA1, true);
487 sendUpdatedEntryAtTime(mEntryA1, 1000);
488 sendUpdatedEntryAtTime(mEntryA2, 2000);
489 sendUpdatedEntryAtTime(mEntryB1, 3000);
490 sendUpdatedEntryAtTime(mEntryB2, 4000); // [A1*, A2, B2, B1]
491 mBubbleData.setListener(mListener);
492
493 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700494 mBubbleData.notificationEntryRemoved(
495 mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
Mark Renouf82a40e62019-05-23 16:16:24 -0400496 verifyUpdateReceived();
497 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400498 }
499
500 /**
Lyn Han2f6e89d2020-04-15 10:01:01 -0700501 * Verifies that overflow bubbles are canceled on notif entry removal.
502 */
503 @Test
504 public void test_removeOverflowBubble_forCanceledNotif() {
505 // Setup
506 sendUpdatedEntryAtTime(mEntryA1, 1000);
507 sendUpdatedEntryAtTime(mEntryA2, 2000);
508 sendUpdatedEntryAtTime(mEntryA3, 3000);
509 sendUpdatedEntryAtTime(mEntryB1, 4000);
510 sendUpdatedEntryAtTime(mEntryB2, 5000);
511 sendUpdatedEntryAtTime(mEntryB3, 6000); // [A2, A3, B1, B2, B3], overflow: [A1]
512 sendUpdatedEntryAtTime(mEntryC1, 7000); // [A3, B1, B2, B3, C1], overflow: [A2, A1]
513 mBubbleData.setListener(mListener);
514
515 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700516 mBubbleData.notificationEntryRemoved(
517 mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700518 verifyUpdateReceived();
519 assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
520
521 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700522 mBubbleData.notificationEntryRemoved(
523 mEntryA2.getKey(), BubbleController.DISMISS_GROUP_CANCELLED);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700524 verifyUpdateReceived();
525 assertOverflowChangedTo(ImmutableList.of());
526 }
527
528 /**
Mark Renoufba5ab512019-05-02 15:21:01 -0400529 * Verifies that when the selected bubble is removed with the stack in the collapsed state,
530 * the selection moves to the next most-recently updated bubble.
531 */
532 @Test
533 public void test_collapsed_removeBubble_selectionChanges() {
534 // Setup
535 sendUpdatedEntryAtTime(mEntryA1, 1000);
536 sendUpdatedEntryAtTime(mEntryB1, 2000);
537 sendUpdatedEntryAtTime(mEntryB2, 3000);
538 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
539 mBubbleData.setListener(mListener);
540
541 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700542 mBubbleData.notificationEntryRemoved(
543 mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
Mark Renouf82a40e62019-05-23 16:16:24 -0400544 verifyUpdateReceived();
545 assertSelectionChangedTo(mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400546 }
547
548 // COLLAPSED / UPDATE
549
550 /**
551 * Verifies that bubble and group ordering may change with updates while the stack is in the
552 * collapsed state.
553 */
554 @Test
555 public void test_collapsed_updateBubble_orderAndGrouping() {
556 // Setup
557 sendUpdatedEntryAtTime(mEntryA1, 1000);
558 sendUpdatedEntryAtTime(mEntryB1, 2000);
559 sendUpdatedEntryAtTime(mEntryB2, 3000);
560 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
561 mBubbleData.setListener(mListener);
562
563 // Test
564 sendUpdatedEntryAtTime(mEntryB1, 5000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400565 verifyUpdateReceived();
566 assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400567
Mark Renoufba5ab512019-05-02 15:21:01 -0400568 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400569 verifyUpdateReceived();
570 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400571 }
572
573 /**
574 * Verifies that selection tracks the most recently updated bubble while in the collapsed state.
575 */
576 @Test
577 public void test_collapsed_updateBubble_selectionChanges() {
578 // Setup
579 sendUpdatedEntryAtTime(mEntryA1, 1000);
580 sendUpdatedEntryAtTime(mEntryB1, 2000);
581 sendUpdatedEntryAtTime(mEntryB2, 3000);
582 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
583 mBubbleData.setListener(mListener);
584
585 // Test
586 sendUpdatedEntryAtTime(mEntryB1, 5000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400587 verifyUpdateReceived();
588 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400589
Mark Renoufba5ab512019-05-02 15:21:01 -0400590 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400591 verifyUpdateReceived();
592 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400593 }
594
595 /**
596 * Verifies that selection does not change in response to updates when collapsed, if the
597 * selected bubble is ongoing.
598 */
599 @Test
600 public void test_collapsed_updateBubble_noSelectionChanges_withOngoing() {
601 // Setup
602 setOngoing(mEntryA1, true);
603 sendUpdatedEntryAtTime(mEntryA1, 1000);
604 sendUpdatedEntryAtTime(mEntryB1, 2000);
605 sendUpdatedEntryAtTime(mEntryB2, 3000);
606 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A1*, A2, B2, B1]
607 mBubbleData.setListener(mListener);
608
609 // Test
610 sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1]
Mark Renouf82a40e62019-05-23 16:16:24 -0400611 verifyUpdateReceived();
612 assertSelectionNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400613 }
614
615 /**
616 * Verifies that a request to expand the stack has no effect if there are no bubbles.
617 */
618 @Test
619 public void test_collapsed_expansion_whenEmpty_doesNothing() {
620 assertThat(mBubbleData.hasBubbles()).isFalse();
Mark Renouf82a40e62019-05-23 16:16:24 -0400621 mBubbleData.setListener(mListener);
Mark Renoufba5ab512019-05-02 15:21:01 -0400622
Mark Renouf82a40e62019-05-23 16:16:24 -0400623 changeExpandedStateAtTime(true, 2000L);
624 verifyZeroInteractions(mListener);
Mark Renoufba5ab512019-05-02 15:21:01 -0400625 }
626
627 @Test
628 public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
629 // Setup
630 sendUpdatedEntryAtTime(mEntryA1, 1000);
631 mBubbleData.setListener(mListener);
632
633 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700634 mBubbleData.notificationEntryRemoved(
635 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renoufba5ab512019-05-02 15:21:01 -0400636
637 // Verify the selection was cleared.
Mark Renouf82a40e62019-05-23 16:16:24 -0400638 verifyUpdateReceived();
639 assertSelectionCleared();
Mark Renoufba5ab512019-05-02 15:21:01 -0400640 }
641
642 // EXPANDED / ADD
643
644 /**
645 * Verifies that bubbles added as part of a new group insert before existing groups while
646 * expanded.
647 * <p>
648 * Placement within the list is based on lastUpdate (post time of the notification), descending
649 * order (with most recent first).
650 *
651 * @see #test_collapsed_addBubble_sortAndGrouping()
652 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
653 */
654 @Test
655 public void test_expanded_addBubble_sortAndGrouping_newGroup() {
656 // Setup
657 sendUpdatedEntryAtTime(mEntryA1, 1000);
658 sendUpdatedEntryAtTime(mEntryA2, 2000);
659 sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
Lyn Hanb9be52d2020-03-31 22:13:34 -0700660 changeExpandedStateAtTime(true, 4000L); // B1 marked updated at 4000L
Mark Renoufba5ab512019-05-02 15:21:01 -0400661 mBubbleData.setListener(mListener);
662
663 // Test
664 sendUpdatedEntryAtTime(mEntryC1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400665 verifyUpdateReceived();
Lyn Hanb9be52d2020-03-31 22:13:34 -0700666 assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400667 }
668
669 /**
670 * Verifies that bubbles added as part of a new group insert before existing groups while
671 * expanded, but not before any groups with ongoing bubbles.
672 *
673 * @see #test_collapsed_addBubble_sortAndGrouping_withOngoing()
674 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
675 */
676 @Test
677 public void test_expanded_addBubble_sortAndGrouping_newGroup_withOngoing() {
678 // Setup
679 setOngoing(mEntryA1, true);
680 sendUpdatedEntryAtTime(mEntryA1, 1000);
681 sendUpdatedEntryAtTime(mEntryA2, 2000);
682 sendUpdatedEntryAtTime(mEntryB1, 3000); // [A1*, A2, B1]
683 changeExpandedStateAtTime(true, 4000L);
684 mBubbleData.setListener(mListener);
685
686 // Test
687 sendUpdatedEntryAtTime(mEntryC1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400688 verifyUpdateReceived();
689 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400690 }
691
692 /**
693 * Verifies that bubbles added as part of an existing group insert to the beginning of that
694 * group. The order of groups within the list must not change while in the expanded state.
695 *
696 * @see #test_collapsed_addBubble_sortAndGrouping()
697 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
698 */
699 @Test
700 public void test_expanded_addBubble_sortAndGrouping_existingGroup() {
701 // Setup
702 sendUpdatedEntryAtTime(mEntryA1, 1000);
703 sendUpdatedEntryAtTime(mEntryA2, 2000);
704 sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
705 changeExpandedStateAtTime(true, 4000L);
706 mBubbleData.setListener(mListener);
707
708 // Test
709 sendUpdatedEntryAtTime(mEntryA3, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400710 verifyUpdateReceived();
711 assertOrderChangedTo(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400712 }
713
714 // EXPANDED / UPDATE
715
716 /**
717 * Verifies that updates to bubbles while expanded do not result in any change to sorting
718 * or grouping of bubbles or sorting of groups.
719 *
720 * @see #test_collapsed_addBubble_sortAndGrouping()
721 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
722 */
723 @Test
724 public void test_expanded_updateBubble_sortAndGrouping_noChanges() {
725 // Setup
726 sendUpdatedEntryAtTime(mEntryA1, 1000);
727 sendUpdatedEntryAtTime(mEntryA2, 2000);
728 sendUpdatedEntryAtTime(mEntryB1, 3000);
729 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
730 changeExpandedStateAtTime(true, 5000L);
731 mBubbleData.setListener(mListener);
732
733 // Test
734 sendUpdatedEntryAtTime(mEntryA1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400735 verifyUpdateReceived();
736 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400737 }
738
739 /**
740 * Verifies that updates to bubbles while expanded do not result in any change to selection.
741 *
742 * @see #test_collapsed_addBubble_selectionChanges()
743 * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
744 */
745 @Test
746 public void test_expanded_updateBubble_noSelectionChanges() {
747 // Setup
748 sendUpdatedEntryAtTime(mEntryA1, 1000);
749 sendUpdatedEntryAtTime(mEntryA2, 2000);
750 sendUpdatedEntryAtTime(mEntryB1, 3000);
751 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
752 changeExpandedStateAtTime(true, 5000L);
753 mBubbleData.setListener(mListener);
754
755 // Test
756 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400757 verifyUpdateReceived();
758 assertOrderNotChanged();
759
Mark Renoufba5ab512019-05-02 15:21:01 -0400760 sendUpdatedEntryAtTime(mEntryA2, 7000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400761 verifyUpdateReceived();
762 assertOrderNotChanged();
763
Mark Renoufba5ab512019-05-02 15:21:01 -0400764 sendUpdatedEntryAtTime(mEntryB1, 8000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400765 verifyUpdateReceived();
766 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400767 }
768
769 // EXPANDED / REMOVE
770
771 /**
772 * Verifies that removing a bubble while expanded does not result in reordering of groups
773 * or any of the remaining bubbles.
774 *
775 * @see #test_collapsed_addBubble_sortAndGrouping()
776 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
777 */
778 @Test
779 public void test_expanded_removeBubble_sortAndGrouping() {
780 // Setup
781 sendUpdatedEntryAtTime(mEntryA1, 1000);
782 sendUpdatedEntryAtTime(mEntryB1, 2000);
783 sendUpdatedEntryAtTime(mEntryA2, 3000);
784 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
785 changeExpandedStateAtTime(true, 5000L);
786 mBubbleData.setListener(mListener);
787
788 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700789 mBubbleData.notificationEntryRemoved(
790 mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400791 verifyUpdateReceived();
792 assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400793 }
794
795 /**
796 * Verifies that removing the selected bubble while expanded causes another bubble to become
797 * selected. The replacement selection is the bubble which appears at the same index as the
798 * previous one, or the previous index if this was the last position.
799 *
800 * @see #test_collapsed_addBubble_sortAndGrouping()
801 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
802 */
803 @Test
804 public void test_expanded_removeBubble_selectionChanges_whenSelectedRemoved() {
805 // Setup
806 sendUpdatedEntryAtTime(mEntryA1, 1000);
807 sendUpdatedEntryAtTime(mEntryB1, 2000);
808 sendUpdatedEntryAtTime(mEntryA2, 3000);
809 sendUpdatedEntryAtTime(mEntryB2, 4000);
810 changeExpandedStateAtTime(true, 5000L);
811 mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
812 mBubbleData.setListener(mListener);
813
814 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700815 mBubbleData.notificationEntryRemoved(
816 mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400817 verifyUpdateReceived();
818 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400819
Pinyao Ting293b83d2020-05-06 17:10:56 -0700820 mBubbleData.notificationEntryRemoved(
821 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400822 verifyUpdateReceived();
823 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400824 }
825
826 @Test
827 public void test_expandAndCollapse_callsOnExpandedChanged() {
828 // Setup
829 sendUpdatedEntryAtTime(mEntryA1, 1000);
830 mBubbleData.setListener(mListener);
831
832 // Test
833 changeExpandedStateAtTime(true, 3000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400834 verifyUpdateReceived();
835 assertExpandedChangedTo(true);
Mark Renoufba5ab512019-05-02 15:21:01 -0400836
Mark Renoufba5ab512019-05-02 15:21:01 -0400837 changeExpandedStateAtTime(false, 4000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400838 verifyUpdateReceived();
839 assertExpandedChangedTo(false);
Mark Renoufba5ab512019-05-02 15:21:01 -0400840 }
841
842 /**
843 * Verifies that transitions between the collapsed and expanded state maintain sorting and
844 * grouping rules.
845 * <p>
846 * While collapsing, sorting is applied since no sorting happens while expanded. The resulting
847 * state is the new expanded ordering. This state is saved and restored if possible when next
848 * expanded.
849 * <p>
850 * When the stack transitions to the collapsed state, the selected bubble is brought to the top.
851 * Bubbles within the same group should move up with it.
852 * <p>
Lyn Hanb9be52d2020-03-31 22:13:34 -0700853 * When the stack transitions back to the expanded state, this new order is kept as is.
Mark Renoufba5ab512019-05-02 15:21:01 -0400854 */
855 @Test
856 public void test_expansionChanges() {
857 // Setup
858 sendUpdatedEntryAtTime(mEntryA1, 1000);
859 sendUpdatedEntryAtTime(mEntryB1, 2000);
860 sendUpdatedEntryAtTime(mEntryA2, 3000);
861 sendUpdatedEntryAtTime(mEntryB2, 4000);
862 changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
863 sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=6000*, A2=3000, A1=1000]
864 setCurrentTime(7000);
865 mBubbleData.setSelectedBubble(mBubbleA2);
866 mBubbleData.setListener(mListener);
867 assertThat(mBubbleData.getBubbles()).isEqualTo(
Mark Renouf82a40e62019-05-23 16:16:24 -0400868 ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
Mark Renoufba5ab512019-05-02 15:21:01 -0400869
870 // Test
871
872 // At this point, B1 has been updated but sorting has not been changed because the
873 // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
874 // to moving the selected bubble to the top (first).
875 //
Lyn Hanb9be52d2020-03-31 22:13:34 -0700876 // In this case, the expected re-expand state will be: [A2, A1, B1, B2]
Mark Renoufba5ab512019-05-02 15:21:01 -0400877 //
878 // collapse -> selected bubble (A2) moves first.
879 changeExpandedStateAtTime(false, 8000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400880 verifyUpdateReceived();
881 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400882 }
883
884 /**
885 * When a change occurs while collapsed (any update, add, remove), the previous expanded
886 * order and grouping becomes invalidated, and the order and grouping when next expanded will
887 * remain the same as collapsed.
888 */
889 @Test
890 public void test_expansionChanges_withUpdatesWhileCollapsed() {
891 // Setup
892 sendUpdatedEntryAtTime(mEntryA1, 1000);
893 sendUpdatedEntryAtTime(mEntryB1, 2000);
894 sendUpdatedEntryAtTime(mEntryA2, 3000);
895 sendUpdatedEntryAtTime(mEntryB2, 4000);
896 changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
897 sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=*6000, A2=3000, A1=1000]
898 setCurrentTime(7000);
899 mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
900 mBubbleData.setListener(mListener);
901
902 // Test
903
904 // At this point, B1 has been updated but sorting has not been changed because the
905 // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
906 // to moving the selected bubble to the top (first).
907 //
908 // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
909 //
910 // That state is restored as long as no changes occur (add/remove/update) while in
911 // the collapsed state.
912 //
913 // collapse -> selected bubble (A2) moves first.
914 changeExpandedStateAtTime(false, 8000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400915 verifyUpdateReceived();
916 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400917
918 // An update occurs, which causes sorting, and this invalidates the previously saved order.
919 sendUpdatedEntryAtTime(mEntryA2, 9000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400920 verifyUpdateReceived();
Mark Renoufba5ab512019-05-02 15:21:01 -0400921
922 // No order changes when expanding because the new sorted order remains.
Mark Renoufba5ab512019-05-02 15:21:01 -0400923 changeExpandedStateAtTime(true, 10000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400924 verifyUpdateReceived();
925 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400926 }
927
928 @Test
929 public void test_expanded_removeLastBubble_collapsesStack() {
930 // Setup
931 sendUpdatedEntryAtTime(mEntryA1, 1000);
932 changeExpandedStateAtTime(true, 2000);
933 mBubbleData.setListener(mListener);
934
935 // Test
Pinyao Ting293b83d2020-05-06 17:10:56 -0700936 mBubbleData.notificationEntryRemoved(
937 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400938 verifyUpdateReceived();
939 assertExpandedChangedTo(false);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400940 }
941
Mark Renouf82a40e62019-05-23 16:16:24 -0400942 private void verifyUpdateReceived() {
943 verify(mListener).applyUpdate(mUpdateCaptor.capture());
944 reset(mListener);
945 }
946
947 private void assertBubbleAdded(Bubble expected) {
948 BubbleData.Update update = mUpdateCaptor.getValue();
949 assertThat(update.addedBubble).named("addedBubble").isEqualTo(expected);
950 }
951
952 private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) {
953 BubbleData.Update update = mUpdateCaptor.getValue();
954 assertThat(update.removedBubbles).named("removedBubbles")
955 .isEqualTo(ImmutableList.of(Pair.create(expected, reason)));
956 }
957
958 private void assertOrderNotChanged() {
959 BubbleData.Update update = mUpdateCaptor.getValue();
960 assertThat(update.orderChanged).named("orderChanged").isFalse();
961 }
962
963 private void assertOrderChangedTo(Bubble... order) {
964 BubbleData.Update update = mUpdateCaptor.getValue();
965 assertThat(update.orderChanged).named("orderChanged").isTrue();
966 assertThat(update.bubbles).named("bubble order").isEqualTo(ImmutableList.copyOf(order));
967 }
968
969 private void assertSelectionNotChanged() {
970 BubbleData.Update update = mUpdateCaptor.getValue();
971 assertThat(update.selectionChanged).named("selectionChanged").isFalse();
972 }
973
974 private void assertSelectionChangedTo(Bubble bubble) {
975 BubbleData.Update update = mUpdateCaptor.getValue();
976 assertThat(update.selectionChanged).named("selectionChanged").isTrue();
977 assertThat(update.selectedBubble).named("selectedBubble").isEqualTo(bubble);
978 }
979
980 private void assertSelectionCleared() {
981 BubbleData.Update update = mUpdateCaptor.getValue();
982 assertThat(update.selectionChanged).named("selectionChanged").isTrue();
983 assertThat(update.selectedBubble).named("selectedBubble").isNull();
984 }
985
986 private void assertExpandedChangedTo(boolean expected) {
987 BubbleData.Update update = mUpdateCaptor.getValue();
988 assertThat(update.expandedChanged).named("expandedChanged").isTrue();
989 assertThat(update.expanded).named("expanded").isEqualTo(expected);
990 }
991
Lyn Hanbf1b3d62020-03-12 10:31:19 -0700992 private void assertOverflowChangedTo(ImmutableList<Bubble> bubbles) {
993 BubbleData.Update update = mUpdateCaptor.getValue();
994 assertThat(update.overflowBubbles).isEqualTo(bubbles);
995 }
996
Mark Renouf82a40e62019-05-23 16:16:24 -0400997
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400998 private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
999 return createBubbleEntry(userId, notifKey, packageName, 1000);
1000 }
1001
1002 private void setPostTime(NotificationEntry entry, long postTime) {
Ned Burns00b4b2d2019-10-17 22:09:27 -04001003 when(entry.getSbn().getPostTime()).thenReturn(postTime);
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001004 }
1005
1006 private void setOngoing(NotificationEntry entry, boolean ongoing) {
1007 if (ongoing) {
Ned Burns00b4b2d2019-10-17 22:09:27 -04001008 entry.getSbn().getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001009 } else {
Ned Burns00b4b2d2019-10-17 22:09:27 -04001010 entry.getSbn().getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001011 }
1012 }
1013
1014 /**
1015 * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
1016 * required for BubbleData functionality and verification. NotificationTestHelper is used only
1017 * as a convenience to create a Notification w/BubbleMetadata.
1018 */
1019 private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
1020 long postTime) {
1021 // BubbleMetadata
Mady Melloraa9ce172020-03-17 10:34:20 -07001022 Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder(
1023 mExpandIntent, Icon.createWithResource("", 0))
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001024 .setDeleteIntent(mDeleteIntent)
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001025 .build();
1026 // Notification -> BubbleMetadata
1027 Notification notification = mNotificationTestHelper.createNotification(false,
1028 null /* groupKey */, bubbleMetadata);
1029
1030 // StatusBarNotification
1031 StatusBarNotification sbn = mock(StatusBarNotification.class);
1032 when(sbn.getKey()).thenReturn(notifKey);
1033 when(sbn.getUser()).thenReturn(new UserHandle(userId));
1034 when(sbn.getPackageName()).thenReturn(packageName);
1035 when(sbn.getPostTime()).thenReturn(postTime);
1036 when(sbn.getNotification()).thenReturn(notification);
1037
1038 // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
Ned Burns636c3792019-09-11 16:59:07 -04001039 return new NotificationEntryBuilder().setSbn(sbn).build();
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001040 }
1041
Mark Renoufba5ab512019-05-02 15:21:01 -04001042 private void setCurrentTime(long time) {
1043 when(mTimeSource.currentTimeMillis()).thenReturn(time);
1044 }
1045
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001046 private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
1047 setPostTime(entry, postTime);
Mady Mellor3df7ab02019-12-09 15:07:10 -08001048 // BubbleController calls this:
1049 Bubble b = mBubbleData.getOrCreateBubble(entry);
1050 // And then this
1051 mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
Mady Mellor7f234902019-10-20 12:06:29 -07001052 true /* showInShade */);
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001053 }
1054
1055 private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
Mark Renoufba5ab512019-05-02 15:21:01 -04001056 setCurrentTime(time);
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001057 mBubbleData.setExpanded(shouldBeExpanded);
1058 }
Lyn Han6c40fe72019-05-08 14:06:33 -07001059}