blob: 66f119a082a63d5ce397867b03c4560701257838 [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
Lyn Han2f6e89d2020-04-15 10:01:01 -070023import static org.mockito.ArgumentMatchers.anyInt;
24import static org.mockito.ArgumentMatchers.eq;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040025import static org.mockito.Mockito.mock;
Mark Renoufba5ab512019-05-02 15:21:01 -040026import static org.mockito.Mockito.reset;
Lyn Han2f6e89d2020-04-15 10:01:01 -070027import static org.mockito.Mockito.times;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040028import static org.mockito.Mockito.verify;
Mark Renouf82a40e62019-05-23 16:16:24 -040029import static org.mockito.Mockito.verifyZeroInteractions;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040030import static org.mockito.Mockito.when;
31
32import android.app.Notification;
33import android.app.PendingIntent;
34import android.graphics.drawable.Icon;
35import android.os.UserHandle;
36import android.service.notification.StatusBarNotification;
37import android.testing.AndroidTestingRunner;
38import android.testing.TestableLooper;
Mark Renouf82a40e62019-05-23 16:16:24 -040039import android.util.Pair;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040040
41import androidx.test.filters.SmallTest;
42
43import com.android.systemui.SysuiTestCase;
44import com.android.systemui.bubbles.BubbleData.TimeSource;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040045import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Beverly79c89ec2019-12-13 10:33:01 -050046import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
Mady Mellorb25f9062019-12-09 12:12:57 -080047import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Kevin Han933dc7c2020-01-29 11:17:46 -080048import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040049
50import com.google.common.collect.ImmutableList;
51
52import org.junit.Before;
53import org.junit.Test;
54import org.junit.runner.RunWith;
Mark Renouf82a40e62019-05-23 16:16:24 -040055import org.mockito.ArgumentCaptor;
56import org.mockito.Captor;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040057import org.mockito.Mock;
58import org.mockito.MockitoAnnotations;
59
Mark Renouf82a40e62019-05-23 16:16:24 -040060/**
61 * Tests operations and the resulting state managed by BubbleData.
62 * <p>
63 * After each operation to verify, {@link #verifyUpdateReceived()} ensures the listener was called
64 * and captures the Update object received there.
65 * <p>
66 * Other methods beginning with 'assert' access the captured update object and assert on specific
67 * aspects of it.
68 */
Mark Renouf9ba6cea2019-04-17 11:53:50 -040069@SmallTest
70@RunWith(AndroidTestingRunner.class)
71@TestableLooper.RunWithLooper(setAsMainLooper = true)
72public class BubbleDataTest extends SysuiTestCase {
73
74 private NotificationEntry mEntryA1;
75 private NotificationEntry mEntryA2;
76 private NotificationEntry mEntryA3;
77 private NotificationEntry mEntryB1;
78 private NotificationEntry mEntryB2;
79 private NotificationEntry mEntryB3;
80 private NotificationEntry mEntryC1;
Lyn Han72f50902019-10-25 15:55:49 -070081 private NotificationEntry mEntryInterruptive;
82 private NotificationEntry mEntryDismissed;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040083
84 private Bubble mBubbleA1;
85 private Bubble mBubbleA2;
86 private Bubble mBubbleA3;
87 private Bubble mBubbleB1;
88 private Bubble mBubbleB2;
89 private Bubble mBubbleB3;
90 private Bubble mBubbleC1;
Mady Mellor3df7ab02019-12-09 15:07:10 -080091 private Bubble mBubbleInterruptive;
92 private Bubble mBubbleDismissed;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040093
94 private BubbleData mBubbleData;
95
96 @Mock
97 private TimeSource mTimeSource;
98 @Mock
99 private BubbleData.Listener mListener;
100 @Mock
101 private PendingIntent mExpandIntent;
102 @Mock
103 private PendingIntent mDeleteIntent;
104
105 private NotificationTestHelper mNotificationTestHelper;
106
Mark Renouf82a40e62019-05-23 16:16:24 -0400107 @Captor
108 private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
109
Mady Mellorf44b6832020-01-14 13:26:14 -0800110 @Mock
111 private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
112
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400113 @Before
114 public void setUp() throws Exception {
Kevin Hana7c21be2020-04-01 17:58:35 -0700115 mNotificationTestHelper = new NotificationTestHelper(
116 mContext,
117 mDependency,
118 TestableLooper.get(this));
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400119 MockitoAnnotations.initMocks(this);
120
121 mEntryA1 = createBubbleEntry(1, "a1", "package.a");
122 mEntryA2 = createBubbleEntry(1, "a2", "package.a");
123 mEntryA3 = createBubbleEntry(1, "a3", "package.a");
124 mEntryB1 = createBubbleEntry(1, "b1", "package.b");
125 mEntryB2 = createBubbleEntry(1, "b2", "package.b");
126 mEntryB3 = createBubbleEntry(1, "b3", "package.b");
127 mEntryC1 = createBubbleEntry(1, "c1", "package.c");
128
Lyn Han72f50902019-10-25 15:55:49 -0700129 mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d");
130 modifyRanking(mEntryInterruptive)
131 .setVisuallyInterruptive(true)
132 .build();
Mady Mellorf44b6832020-01-14 13:26:14 -0800133 mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener);
Lyn Han72f50902019-10-25 15:55:49 -0700134
Mady Mellorb25f9062019-12-09 12:12:57 -0800135 ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
Lyn Han72f50902019-10-25 15:55:49 -0700136 mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
Mady Mellorb25f9062019-12-09 12:12:57 -0800137 mEntryDismissed.setRow(row);
Mady Mellorf44b6832020-01-14 13:26:14 -0800138 mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener);
Lyn Han72f50902019-10-25 15:55:49 -0700139
Mady Mellorf44b6832020-01-14 13:26:14 -0800140 mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener);
141 mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener);
142 mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener);
143 mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener);
144 mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener);
145 mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener);
146 mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400147
148 mBubbleData = new BubbleData(getContext());
149
150 // Used by BubbleData to set lastAccessedTime
151 when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
152 mBubbleData.setTimeSource(mTimeSource);
Mark Renoufba5ab512019-05-02 15:21:01 -0400153
154 // Assert baseline starting state
155 assertThat(mBubbleData.hasBubbles()).isFalse();
156 assertThat(mBubbleData.isExpanded()).isFalse();
157 assertThat(mBubbleData.getSelectedBubble()).isNull();
158 }
159
160 @Test
161 public void testAddBubble() {
162 // Setup
163 mBubbleData.setListener(mListener);
164
165 // Test
166 sendUpdatedEntryAtTime(mEntryA1, 1000);
167
168 // Verify
Mark Renouf82a40e62019-05-23 16:16:24 -0400169 verifyUpdateReceived();
170 assertBubbleAdded(mBubbleA1);
171 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400172 }
173
174 @Test
175 public void testRemoveBubble() {
176 // Setup
177 sendUpdatedEntryAtTime(mEntryA1, 1000);
178 sendUpdatedEntryAtTime(mEntryA2, 2000);
179 sendUpdatedEntryAtTime(mEntryA3, 3000);
180 mBubbleData.setListener(mListener);
181
182 // Test
183 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
184
185 // Verify
Mark Renouf82a40e62019-05-23 16:16:24 -0400186 verifyUpdateReceived();
187 assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renoufba5ab512019-05-02 15:21:01 -0400188 }
189
Lyn Han72f50902019-10-25 15:55:49 -0700190 @Test
191 public void ifSuppress_hideFlyout() {
192 // Setup
193 mBubbleData.setListener(mListener);
194
195 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800196 mBubbleData.notificationEntryUpdated(mBubbleC1, /* suppressFlyout */ true, /* showInShade */
Lyn Han72f50902019-10-25 15:55:49 -0700197 true);
198
199 // Verify
200 verifyUpdateReceived();
201 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellorb8aaf972019-11-26 10:28:00 -0800202 assertThat(update.addedBubble.showFlyout()).isFalse();
Lyn Han72f50902019-10-25 15:55:49 -0700203 }
204
205 @Test
206 public void ifInterruptiveAndNotSuppressed_thenShowFlyout() {
207 // Setup
208 mBubbleData.setListener(mListener);
209
210 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800211 mBubbleData.notificationEntryUpdated(mBubbleInterruptive,
Mady Mellorb25f9062019-12-09 12:12:57 -0800212 false /* suppressFlyout */, true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700213
214 // Verify
215 verifyUpdateReceived();
216 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellorb8aaf972019-11-26 10:28:00 -0800217 assertThat(update.addedBubble.showFlyout()).isTrue();
Lyn Han72f50902019-10-25 15:55:49 -0700218 }
219
220 @Test
221 public void sameUpdate_InShade_thenHideFlyout() {
222 // Setup
223 mBubbleData.setListener(mListener);
224
225 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800226 mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
Mady Mellorb25f9062019-12-09 12:12:57 -0800227 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700228 verifyUpdateReceived();
229
Mady Mellor3df7ab02019-12-09 15:07:10 -0800230 mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
Mady Mellorb25f9062019-12-09 12:12:57 -0800231 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700232 verifyUpdateReceived();
233
234 // Verify
235 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellorb8aaf972019-11-26 10:28:00 -0800236 assertThat(update.updatedBubble.showFlyout()).isFalse();
Lyn Han72f50902019-10-25 15:55:49 -0700237 }
238
239 @Test
Mady Mellor8e97a962020-01-21 16:20:15 -0800240 public void sameUpdate_NotInShade_NotVisuallyInterruptive_dontShowFlyout() {
Lyn Han72f50902019-10-25 15:55:49 -0700241 // Setup
242 mBubbleData.setListener(mListener);
Lyn Han72f50902019-10-25 15:55:49 -0700243
244 // Test
Mady Mellor3df7ab02019-12-09 15:07:10 -0800245 mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
246 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700247 verifyUpdateReceived();
248
Mady Mellorf44b6832020-01-14 13:26:14 -0800249 // Suppress the notif / make it look dismissed
250 mBubbleDismissed.setSuppressNotification(true);
Mady Mellorb25f9062019-12-09 12:12:57 -0800251
Mady Mellor3df7ab02019-12-09 15:07:10 -0800252 mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
253 true /* showInShade */);
Lyn Han72f50902019-10-25 15:55:49 -0700254 verifyUpdateReceived();
255
256 // Verify
257 BubbleData.Update update = mUpdateCaptor.getValue();
Mady Mellor8e97a962020-01-21 16:20:15 -0800258 assertThat(update.updatedBubble.showFlyout()).isFalse();
Lyn Han72f50902019-10-25 15:55:49 -0700259 }
260
Mark Renoufba5ab512019-05-02 15:21:01 -0400261 // COLLAPSED / ADD
262
263 /**
264 * Verifies that the number of bubbles is not allowed to exceed the maximum. The limit is
265 * enforced by expiring the bubble which was least recently updated (lowest timestamp).
266 */
267 @Test
Lyn Hanb58c7562020-01-07 14:29:20 -0800268 public void test_collapsed_addBubble_atMaxBubbles_overflowsOldest() {
Mark Renoufba5ab512019-05-02 15:21:01 -0400269 // Setup
270 sendUpdatedEntryAtTime(mEntryA1, 1000);
271 sendUpdatedEntryAtTime(mEntryA2, 2000);
272 sendUpdatedEntryAtTime(mEntryA3, 3000);
273 sendUpdatedEntryAtTime(mEntryB1, 4000);
274 sendUpdatedEntryAtTime(mEntryB2, 5000);
275 mBubbleData.setListener(mListener);
276
Mark Renoufba5ab512019-05-02 15:21:01 -0400277 sendUpdatedEntryAtTime(mEntryC1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400278 verifyUpdateReceived();
279 assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED);
Lyn Hanbf1b3d62020-03-12 10:31:19 -0700280 assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
281
282 Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1);
283 bubbleA1.markUpdatedAt(7000L);
284 mBubbleData.notificationEntryUpdated(bubbleA1, false /* suppressFlyout*/,
285 true /* showInShade */);
286 verifyUpdateReceived();
287 assertBubbleRemoved(mBubbleA2, BubbleController.DISMISS_AGED);
288 assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
Mark Renoufba5ab512019-05-02 15:21:01 -0400289 }
290
Lyn Han2f6e89d2020-04-15 10:01:01 -0700291 @Test
292 public void testOverflowBubble_maxReached_bubbleRemoved() {
293 // Setup
294 sendUpdatedEntryAtTime(mEntryA1, 1000);
295 sendUpdatedEntryAtTime(mEntryA2, 2000);
296 sendUpdatedEntryAtTime(mEntryA3, 3000);
297 mBubbleData.setListener(mListener);
298
299 mBubbleData.setMaxOverflowBubbles(1);
300 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
301 verifyUpdateReceived();
302 assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
303
304 // Overflow max of 1 is reached; A1 is oldest, so it gets removed
305 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
306 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
452 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400453 verifyUpdateReceived();
454 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400455 }
456
457
458 /**
459 * Verifies that onOrderChanged is not called when a bubble is removed if the removal does not
460 * cause other bubbles to change position.
461 */
462 @Test
463 public void test_collapsed_removeOldestBubble_doesNotCallOnOrderChanged() {
464 // Setup
465 sendUpdatedEntryAtTime(mEntryA1, 1000);
466 sendUpdatedEntryAtTime(mEntryB1, 2000);
467 sendUpdatedEntryAtTime(mEntryB2, 3000);
468 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
469 mBubbleData.setListener(mListener);
470
471 // Test
472 mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400473 verifyUpdateReceived();
474 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400475 }
476
477 /**
478 * Verifies that bubble ordering reverts to normal when an ongoing bubble is removed. A group
479 * which has a newer bubble may move to the front after the ongoing bubble is removed.
480 */
481 @Test
482 public void test_collapsed_removeBubble_sortAndGrouping_withOngoing() {
483 // Setup
484 setOngoing(mEntryA1, true);
485 sendUpdatedEntryAtTime(mEntryA1, 1000);
486 sendUpdatedEntryAtTime(mEntryA2, 2000);
487 sendUpdatedEntryAtTime(mEntryB1, 3000);
488 sendUpdatedEntryAtTime(mEntryB2, 4000); // [A1*, A2, B2, B1]
489 mBubbleData.setListener(mListener);
490
491 // Test
492 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
Mark Renouf82a40e62019-05-23 16:16:24 -0400493 verifyUpdateReceived();
494 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400495 }
496
497 /**
Lyn Han2f6e89d2020-04-15 10:01:01 -0700498 * Verifies that overflow bubbles are canceled on notif entry removal.
499 */
500 @Test
501 public void test_removeOverflowBubble_forCanceledNotif() {
502 // Setup
503 sendUpdatedEntryAtTime(mEntryA1, 1000);
504 sendUpdatedEntryAtTime(mEntryA2, 2000);
505 sendUpdatedEntryAtTime(mEntryA3, 3000);
506 sendUpdatedEntryAtTime(mEntryB1, 4000);
507 sendUpdatedEntryAtTime(mEntryB2, 5000);
508 sendUpdatedEntryAtTime(mEntryB3, 6000); // [A2, A3, B1, B2, B3], overflow: [A1]
509 sendUpdatedEntryAtTime(mEntryC1, 7000); // [A3, B1, B2, B3, C1], overflow: [A2, A1]
510 mBubbleData.setListener(mListener);
511
512 // Test
513 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
514 verifyUpdateReceived();
515 assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
516
517 // Test
518 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
519 verifyUpdateReceived();
520 assertOverflowChangedTo(ImmutableList.of());
521 }
522
523 /**
Mark Renoufba5ab512019-05-02 15:21:01 -0400524 * Verifies that when the selected bubble is removed with the stack in the collapsed state,
525 * the selection moves to the next most-recently updated bubble.
526 */
527 @Test
528 public void test_collapsed_removeBubble_selectionChanges() {
529 // Setup
530 sendUpdatedEntryAtTime(mEntryA1, 1000);
531 sendUpdatedEntryAtTime(mEntryB1, 2000);
532 sendUpdatedEntryAtTime(mEntryB2, 3000);
533 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
534 mBubbleData.setListener(mListener);
535
536 // Test
537 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL);
Mark Renouf82a40e62019-05-23 16:16:24 -0400538 verifyUpdateReceived();
539 assertSelectionChangedTo(mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400540 }
541
542 // COLLAPSED / UPDATE
543
544 /**
545 * Verifies that bubble and group ordering may change with updates while the stack is in the
546 * collapsed state.
547 */
548 @Test
549 public void test_collapsed_updateBubble_orderAndGrouping() {
550 // Setup
551 sendUpdatedEntryAtTime(mEntryA1, 1000);
552 sendUpdatedEntryAtTime(mEntryB1, 2000);
553 sendUpdatedEntryAtTime(mEntryB2, 3000);
554 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
555 mBubbleData.setListener(mListener);
556
557 // Test
558 sendUpdatedEntryAtTime(mEntryB1, 5000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400559 verifyUpdateReceived();
560 assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400561
Mark Renoufba5ab512019-05-02 15:21:01 -0400562 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400563 verifyUpdateReceived();
564 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400565 }
566
567 /**
568 * Verifies that selection tracks the most recently updated bubble while in the collapsed state.
569 */
570 @Test
571 public void test_collapsed_updateBubble_selectionChanges() {
572 // Setup
573 sendUpdatedEntryAtTime(mEntryA1, 1000);
574 sendUpdatedEntryAtTime(mEntryB1, 2000);
575 sendUpdatedEntryAtTime(mEntryB2, 3000);
576 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
577 mBubbleData.setListener(mListener);
578
579 // Test
580 sendUpdatedEntryAtTime(mEntryB1, 5000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400581 verifyUpdateReceived();
582 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400583
Mark Renoufba5ab512019-05-02 15:21:01 -0400584 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400585 verifyUpdateReceived();
586 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400587 }
588
589 /**
590 * Verifies that selection does not change in response to updates when collapsed, if the
591 * selected bubble is ongoing.
592 */
593 @Test
594 public void test_collapsed_updateBubble_noSelectionChanges_withOngoing() {
595 // Setup
596 setOngoing(mEntryA1, true);
597 sendUpdatedEntryAtTime(mEntryA1, 1000);
598 sendUpdatedEntryAtTime(mEntryB1, 2000);
599 sendUpdatedEntryAtTime(mEntryB2, 3000);
600 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A1*, A2, B2, B1]
601 mBubbleData.setListener(mListener);
602
603 // Test
604 sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1]
Mark Renouf82a40e62019-05-23 16:16:24 -0400605 verifyUpdateReceived();
606 assertSelectionNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400607 }
608
609 /**
610 * Verifies that a request to expand the stack has no effect if there are no bubbles.
611 */
612 @Test
613 public void test_collapsed_expansion_whenEmpty_doesNothing() {
614 assertThat(mBubbleData.hasBubbles()).isFalse();
Mark Renouf82a40e62019-05-23 16:16:24 -0400615 mBubbleData.setListener(mListener);
Mark Renoufba5ab512019-05-02 15:21:01 -0400616
Mark Renouf82a40e62019-05-23 16:16:24 -0400617 changeExpandedStateAtTime(true, 2000L);
618 verifyZeroInteractions(mListener);
Mark Renoufba5ab512019-05-02 15:21:01 -0400619 }
620
621 @Test
622 public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
623 // Setup
624 sendUpdatedEntryAtTime(mEntryA1, 1000);
625 mBubbleData.setListener(mListener);
626
627 // Test
628 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
629
630 // Verify the selection was cleared.
Mark Renouf82a40e62019-05-23 16:16:24 -0400631 verifyUpdateReceived();
632 assertSelectionCleared();
Mark Renoufba5ab512019-05-02 15:21:01 -0400633 }
634
635 // EXPANDED / ADD
636
637 /**
638 * Verifies that bubbles added as part of a new group insert before existing groups while
639 * expanded.
640 * <p>
641 * Placement within the list is based on lastUpdate (post time of the notification), descending
642 * order (with most recent first).
643 *
644 * @see #test_collapsed_addBubble_sortAndGrouping()
645 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
646 */
647 @Test
648 public void test_expanded_addBubble_sortAndGrouping_newGroup() {
649 // Setup
650 sendUpdatedEntryAtTime(mEntryA1, 1000);
651 sendUpdatedEntryAtTime(mEntryA2, 2000);
652 sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
Lyn Hanb9be52d2020-03-31 22:13:34 -0700653 changeExpandedStateAtTime(true, 4000L); // B1 marked updated at 4000L
Mark Renoufba5ab512019-05-02 15:21:01 -0400654 mBubbleData.setListener(mListener);
655
656 // Test
657 sendUpdatedEntryAtTime(mEntryC1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400658 verifyUpdateReceived();
Lyn Hanb9be52d2020-03-31 22:13:34 -0700659 assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400660 }
661
662 /**
663 * Verifies that bubbles added as part of a new group insert before existing groups while
664 * expanded, but not before any groups with ongoing bubbles.
665 *
666 * @see #test_collapsed_addBubble_sortAndGrouping_withOngoing()
667 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
668 */
669 @Test
670 public void test_expanded_addBubble_sortAndGrouping_newGroup_withOngoing() {
671 // Setup
672 setOngoing(mEntryA1, true);
673 sendUpdatedEntryAtTime(mEntryA1, 1000);
674 sendUpdatedEntryAtTime(mEntryA2, 2000);
675 sendUpdatedEntryAtTime(mEntryB1, 3000); // [A1*, A2, B1]
676 changeExpandedStateAtTime(true, 4000L);
677 mBubbleData.setListener(mListener);
678
679 // Test
680 sendUpdatedEntryAtTime(mEntryC1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400681 verifyUpdateReceived();
682 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400683 }
684
685 /**
686 * Verifies that bubbles added as part of an existing group insert to the beginning of that
687 * group. The order of groups within the list must not change while in the expanded state.
688 *
689 * @see #test_collapsed_addBubble_sortAndGrouping()
690 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
691 */
692 @Test
693 public void test_expanded_addBubble_sortAndGrouping_existingGroup() {
694 // Setup
695 sendUpdatedEntryAtTime(mEntryA1, 1000);
696 sendUpdatedEntryAtTime(mEntryA2, 2000);
697 sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
698 changeExpandedStateAtTime(true, 4000L);
699 mBubbleData.setListener(mListener);
700
701 // Test
702 sendUpdatedEntryAtTime(mEntryA3, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400703 verifyUpdateReceived();
704 assertOrderChangedTo(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400705 }
706
707 // EXPANDED / UPDATE
708
709 /**
710 * Verifies that updates to bubbles while expanded do not result in any change to sorting
711 * or grouping of bubbles or sorting of groups.
712 *
713 * @see #test_collapsed_addBubble_sortAndGrouping()
714 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
715 */
716 @Test
717 public void test_expanded_updateBubble_sortAndGrouping_noChanges() {
718 // Setup
719 sendUpdatedEntryAtTime(mEntryA1, 1000);
720 sendUpdatedEntryAtTime(mEntryA2, 2000);
721 sendUpdatedEntryAtTime(mEntryB1, 3000);
722 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
723 changeExpandedStateAtTime(true, 5000L);
724 mBubbleData.setListener(mListener);
725
726 // Test
727 sendUpdatedEntryAtTime(mEntryA1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400728 verifyUpdateReceived();
729 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400730 }
731
732 /**
733 * Verifies that updates to bubbles while expanded do not result in any change to selection.
734 *
735 * @see #test_collapsed_addBubble_selectionChanges()
736 * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
737 */
738 @Test
739 public void test_expanded_updateBubble_noSelectionChanges() {
740 // Setup
741 sendUpdatedEntryAtTime(mEntryA1, 1000);
742 sendUpdatedEntryAtTime(mEntryA2, 2000);
743 sendUpdatedEntryAtTime(mEntryB1, 3000);
744 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
745 changeExpandedStateAtTime(true, 5000L);
746 mBubbleData.setListener(mListener);
747
748 // Test
749 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400750 verifyUpdateReceived();
751 assertOrderNotChanged();
752
Mark Renoufba5ab512019-05-02 15:21:01 -0400753 sendUpdatedEntryAtTime(mEntryA2, 7000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400754 verifyUpdateReceived();
755 assertOrderNotChanged();
756
Mark Renoufba5ab512019-05-02 15:21:01 -0400757 sendUpdatedEntryAtTime(mEntryB1, 8000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400758 verifyUpdateReceived();
759 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400760 }
761
762 // EXPANDED / REMOVE
763
764 /**
765 * Verifies that removing a bubble while expanded does not result in reordering of groups
766 * or any of the remaining bubbles.
767 *
768 * @see #test_collapsed_addBubble_sortAndGrouping()
769 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
770 */
771 @Test
772 public void test_expanded_removeBubble_sortAndGrouping() {
773 // Setup
774 sendUpdatedEntryAtTime(mEntryA1, 1000);
775 sendUpdatedEntryAtTime(mEntryB1, 2000);
776 sendUpdatedEntryAtTime(mEntryA2, 3000);
777 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
778 changeExpandedStateAtTime(true, 5000L);
779 mBubbleData.setListener(mListener);
780
781 // Test
782 mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400783 verifyUpdateReceived();
784 assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400785 }
786
787 /**
788 * Verifies that removing the selected bubble while expanded causes another bubble to become
789 * selected. The replacement selection is the bubble which appears at the same index as the
790 * previous one, or the previous index if this was the last position.
791 *
792 * @see #test_collapsed_addBubble_sortAndGrouping()
793 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
794 */
795 @Test
796 public void test_expanded_removeBubble_selectionChanges_whenSelectedRemoved() {
797 // Setup
798 sendUpdatedEntryAtTime(mEntryA1, 1000);
799 sendUpdatedEntryAtTime(mEntryB1, 2000);
800 sendUpdatedEntryAtTime(mEntryA2, 3000);
801 sendUpdatedEntryAtTime(mEntryB2, 4000);
802 changeExpandedStateAtTime(true, 5000L);
803 mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
804 mBubbleData.setListener(mListener);
805
806 // Test
807 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400808 verifyUpdateReceived();
809 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400810
Mark Renoufba5ab512019-05-02 15:21:01 -0400811 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400812 verifyUpdateReceived();
813 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400814 }
815
816 @Test
817 public void test_expandAndCollapse_callsOnExpandedChanged() {
818 // Setup
819 sendUpdatedEntryAtTime(mEntryA1, 1000);
820 mBubbleData.setListener(mListener);
821
822 // Test
823 changeExpandedStateAtTime(true, 3000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400824 verifyUpdateReceived();
825 assertExpandedChangedTo(true);
Mark Renoufba5ab512019-05-02 15:21:01 -0400826
Mark Renoufba5ab512019-05-02 15:21:01 -0400827 changeExpandedStateAtTime(false, 4000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400828 verifyUpdateReceived();
829 assertExpandedChangedTo(false);
Mark Renoufba5ab512019-05-02 15:21:01 -0400830 }
831
832 /**
833 * Verifies that transitions between the collapsed and expanded state maintain sorting and
834 * grouping rules.
835 * <p>
836 * While collapsing, sorting is applied since no sorting happens while expanded. The resulting
837 * state is the new expanded ordering. This state is saved and restored if possible when next
838 * expanded.
839 * <p>
840 * When the stack transitions to the collapsed state, the selected bubble is brought to the top.
841 * Bubbles within the same group should move up with it.
842 * <p>
Lyn Hanb9be52d2020-03-31 22:13:34 -0700843 * When the stack transitions back to the expanded state, this new order is kept as is.
Mark Renoufba5ab512019-05-02 15:21:01 -0400844 */
845 @Test
846 public void test_expansionChanges() {
847 // Setup
848 sendUpdatedEntryAtTime(mEntryA1, 1000);
849 sendUpdatedEntryAtTime(mEntryB1, 2000);
850 sendUpdatedEntryAtTime(mEntryA2, 3000);
851 sendUpdatedEntryAtTime(mEntryB2, 4000);
852 changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
853 sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=6000*, A2=3000, A1=1000]
854 setCurrentTime(7000);
855 mBubbleData.setSelectedBubble(mBubbleA2);
856 mBubbleData.setListener(mListener);
857 assertThat(mBubbleData.getBubbles()).isEqualTo(
Mark Renouf82a40e62019-05-23 16:16:24 -0400858 ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
Mark Renoufba5ab512019-05-02 15:21:01 -0400859
860 // Test
861
862 // At this point, B1 has been updated but sorting has not been changed because the
863 // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
864 // to moving the selected bubble to the top (first).
865 //
Lyn Hanb9be52d2020-03-31 22:13:34 -0700866 // In this case, the expected re-expand state will be: [A2, A1, B1, B2]
Mark Renoufba5ab512019-05-02 15:21:01 -0400867 //
868 // collapse -> selected bubble (A2) moves first.
869 changeExpandedStateAtTime(false, 8000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400870 verifyUpdateReceived();
871 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400872 }
873
874 /**
875 * When a change occurs while collapsed (any update, add, remove), the previous expanded
876 * order and grouping becomes invalidated, and the order and grouping when next expanded will
877 * remain the same as collapsed.
878 */
879 @Test
880 public void test_expansionChanges_withUpdatesWhileCollapsed() {
881 // Setup
882 sendUpdatedEntryAtTime(mEntryA1, 1000);
883 sendUpdatedEntryAtTime(mEntryB1, 2000);
884 sendUpdatedEntryAtTime(mEntryA2, 3000);
885 sendUpdatedEntryAtTime(mEntryB2, 4000);
886 changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
887 sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=*6000, A2=3000, A1=1000]
888 setCurrentTime(7000);
889 mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
890 mBubbleData.setListener(mListener);
891
892 // Test
893
894 // At this point, B1 has been updated but sorting has not been changed because the
895 // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
896 // to moving the selected bubble to the top (first).
897 //
898 // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
899 //
900 // That state is restored as long as no changes occur (add/remove/update) while in
901 // the collapsed state.
902 //
903 // collapse -> selected bubble (A2) moves first.
904 changeExpandedStateAtTime(false, 8000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400905 verifyUpdateReceived();
906 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400907
908 // An update occurs, which causes sorting, and this invalidates the previously saved order.
909 sendUpdatedEntryAtTime(mEntryA2, 9000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400910 verifyUpdateReceived();
Mark Renoufba5ab512019-05-02 15:21:01 -0400911
912 // No order changes when expanding because the new sorted order remains.
Mark Renoufba5ab512019-05-02 15:21:01 -0400913 changeExpandedStateAtTime(true, 10000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400914 verifyUpdateReceived();
915 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400916 }
917
918 @Test
919 public void test_expanded_removeLastBubble_collapsesStack() {
920 // Setup
921 sendUpdatedEntryAtTime(mEntryA1, 1000);
922 changeExpandedStateAtTime(true, 2000);
923 mBubbleData.setListener(mListener);
924
925 // Test
926 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400927 verifyUpdateReceived();
928 assertExpandedChangedTo(false);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400929 }
930
Mark Renouf82a40e62019-05-23 16:16:24 -0400931 private void verifyUpdateReceived() {
932 verify(mListener).applyUpdate(mUpdateCaptor.capture());
933 reset(mListener);
934 }
935
936 private void assertBubbleAdded(Bubble expected) {
937 BubbleData.Update update = mUpdateCaptor.getValue();
938 assertThat(update.addedBubble).named("addedBubble").isEqualTo(expected);
939 }
940
941 private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) {
942 BubbleData.Update update = mUpdateCaptor.getValue();
943 assertThat(update.removedBubbles).named("removedBubbles")
944 .isEqualTo(ImmutableList.of(Pair.create(expected, reason)));
945 }
946
947 private void assertOrderNotChanged() {
948 BubbleData.Update update = mUpdateCaptor.getValue();
949 assertThat(update.orderChanged).named("orderChanged").isFalse();
950 }
951
952 private void assertOrderChangedTo(Bubble... order) {
953 BubbleData.Update update = mUpdateCaptor.getValue();
954 assertThat(update.orderChanged).named("orderChanged").isTrue();
955 assertThat(update.bubbles).named("bubble order").isEqualTo(ImmutableList.copyOf(order));
956 }
957
958 private void assertSelectionNotChanged() {
959 BubbleData.Update update = mUpdateCaptor.getValue();
960 assertThat(update.selectionChanged).named("selectionChanged").isFalse();
961 }
962
963 private void assertSelectionChangedTo(Bubble bubble) {
964 BubbleData.Update update = mUpdateCaptor.getValue();
965 assertThat(update.selectionChanged).named("selectionChanged").isTrue();
966 assertThat(update.selectedBubble).named("selectedBubble").isEqualTo(bubble);
967 }
968
969 private void assertSelectionCleared() {
970 BubbleData.Update update = mUpdateCaptor.getValue();
971 assertThat(update.selectionChanged).named("selectionChanged").isTrue();
972 assertThat(update.selectedBubble).named("selectedBubble").isNull();
973 }
974
975 private void assertExpandedChangedTo(boolean expected) {
976 BubbleData.Update update = mUpdateCaptor.getValue();
977 assertThat(update.expandedChanged).named("expandedChanged").isTrue();
978 assertThat(update.expanded).named("expanded").isEqualTo(expected);
979 }
980
Lyn Hanbf1b3d62020-03-12 10:31:19 -0700981 private void assertOverflowChangedTo(ImmutableList<Bubble> bubbles) {
982 BubbleData.Update update = mUpdateCaptor.getValue();
983 assertThat(update.overflowBubbles).isEqualTo(bubbles);
984 }
985
Mark Renouf82a40e62019-05-23 16:16:24 -0400986
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400987 private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
988 return createBubbleEntry(userId, notifKey, packageName, 1000);
989 }
990
991 private void setPostTime(NotificationEntry entry, long postTime) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400992 when(entry.getSbn().getPostTime()).thenReturn(postTime);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400993 }
994
995 private void setOngoing(NotificationEntry entry, boolean ongoing) {
996 if (ongoing) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400997 entry.getSbn().getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400998 } else {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400999 entry.getSbn().getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001000 }
1001 }
1002
1003 /**
1004 * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
1005 * required for BubbleData functionality and verification. NotificationTestHelper is used only
1006 * as a convenience to create a Notification w/BubbleMetadata.
1007 */
1008 private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
1009 long postTime) {
1010 // BubbleMetadata
Mady Melloraa9ce172020-03-17 10:34:20 -07001011 Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder(
1012 mExpandIntent, Icon.createWithResource("", 0))
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001013 .setDeleteIntent(mDeleteIntent)
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001014 .build();
1015 // Notification -> BubbleMetadata
1016 Notification notification = mNotificationTestHelper.createNotification(false,
1017 null /* groupKey */, bubbleMetadata);
1018
1019 // StatusBarNotification
1020 StatusBarNotification sbn = mock(StatusBarNotification.class);
1021 when(sbn.getKey()).thenReturn(notifKey);
1022 when(sbn.getUser()).thenReturn(new UserHandle(userId));
1023 when(sbn.getPackageName()).thenReturn(packageName);
1024 when(sbn.getPostTime()).thenReturn(postTime);
1025 when(sbn.getNotification()).thenReturn(notification);
1026
1027 // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
Ned Burns636c3792019-09-11 16:59:07 -04001028 return new NotificationEntryBuilder().setSbn(sbn).build();
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001029 }
1030
Mark Renoufba5ab512019-05-02 15:21:01 -04001031 private void setCurrentTime(long time) {
1032 when(mTimeSource.currentTimeMillis()).thenReturn(time);
1033 }
1034
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001035 private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
1036 setPostTime(entry, postTime);
Mady Mellor3df7ab02019-12-09 15:07:10 -08001037 // BubbleController calls this:
1038 Bubble b = mBubbleData.getOrCreateBubble(entry);
1039 // And then this
1040 mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
Mady Mellor7f234902019-10-20 12:06:29 -07001041 true /* showInShade */);
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001042 }
1043
1044 private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
Mark Renoufba5ab512019-05-02 15:21:01 -04001045 setCurrentTime(time);
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001046 mBubbleData.setExpanded(shouldBeExpanded);
1047 }
Lyn Han6c40fe72019-05-08 14:06:33 -07001048}