blob: f7d2743d27141bb7d5e397684e3adc2054ec6530 [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
19import static com.google.common.truth.Truth.assertThat;
20
Mark Renouf9ba6cea2019-04-17 11:53:50 -040021import static org.mockito.Mockito.mock;
Mark Renoufba5ab512019-05-02 15:21:01 -040022import static org.mockito.Mockito.reset;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040023import static org.mockito.Mockito.verify;
Mark Renouf82a40e62019-05-23 16:16:24 -040024import static org.mockito.Mockito.verifyZeroInteractions;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040025import static org.mockito.Mockito.when;
26
27import android.app.Notification;
28import android.app.PendingIntent;
29import android.graphics.drawable.Icon;
30import android.os.UserHandle;
31import android.service.notification.StatusBarNotification;
32import android.testing.AndroidTestingRunner;
33import android.testing.TestableLooper;
Mark Renouf82a40e62019-05-23 16:16:24 -040034import android.util.Pair;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040035
36import androidx.test.filters.SmallTest;
37
38import com.android.systemui.SysuiTestCase;
39import com.android.systemui.bubbles.BubbleData.TimeSource;
40import com.android.systemui.statusbar.NotificationTestHelper;
41import com.android.systemui.statusbar.notification.collection.NotificationEntry;
42
43import com.google.common.collect.ImmutableList;
44
45import org.junit.Before;
46import org.junit.Test;
47import org.junit.runner.RunWith;
Mark Renouf82a40e62019-05-23 16:16:24 -040048import org.mockito.ArgumentCaptor;
49import org.mockito.Captor;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040050import org.mockito.Mock;
51import org.mockito.MockitoAnnotations;
52
Mark Renouf82a40e62019-05-23 16:16:24 -040053/**
54 * Tests operations and the resulting state managed by BubbleData.
55 * <p>
56 * After each operation to verify, {@link #verifyUpdateReceived()} ensures the listener was called
57 * and captures the Update object received there.
58 * <p>
59 * Other methods beginning with 'assert' access the captured update object and assert on specific
60 * aspects of it.
61 */
Mark Renouf9ba6cea2019-04-17 11:53:50 -040062@SmallTest
63@RunWith(AndroidTestingRunner.class)
64@TestableLooper.RunWithLooper(setAsMainLooper = true)
65public class BubbleDataTest extends SysuiTestCase {
66
67 private NotificationEntry mEntryA1;
68 private NotificationEntry mEntryA2;
69 private NotificationEntry mEntryA3;
70 private NotificationEntry mEntryB1;
71 private NotificationEntry mEntryB2;
72 private NotificationEntry mEntryB3;
73 private NotificationEntry mEntryC1;
74
75 private Bubble mBubbleA1;
76 private Bubble mBubbleA2;
77 private Bubble mBubbleA3;
78 private Bubble mBubbleB1;
79 private Bubble mBubbleB2;
80 private Bubble mBubbleB3;
81 private Bubble mBubbleC1;
82
83 private BubbleData mBubbleData;
84
85 @Mock
86 private TimeSource mTimeSource;
87 @Mock
88 private BubbleData.Listener mListener;
89 @Mock
90 private PendingIntent mExpandIntent;
91 @Mock
92 private PendingIntent mDeleteIntent;
93
94 private NotificationTestHelper mNotificationTestHelper;
95
Mark Renouf82a40e62019-05-23 16:16:24 -040096 @Captor
97 private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
98
Mark Renouf9ba6cea2019-04-17 11:53:50 -040099 @Before
100 public void setUp() throws Exception {
101 mNotificationTestHelper = new NotificationTestHelper(mContext);
102 MockitoAnnotations.initMocks(this);
103
104 mEntryA1 = createBubbleEntry(1, "a1", "package.a");
105 mEntryA2 = createBubbleEntry(1, "a2", "package.a");
106 mEntryA3 = createBubbleEntry(1, "a3", "package.a");
107 mEntryB1 = createBubbleEntry(1, "b1", "package.b");
108 mEntryB2 = createBubbleEntry(1, "b2", "package.b");
109 mEntryB3 = createBubbleEntry(1, "b3", "package.b");
110 mEntryC1 = createBubbleEntry(1, "c1", "package.c");
111
Lyn Han6c40fe72019-05-08 14:06:33 -0700112 mBubbleA1 = new Bubble(mContext, mEntryA1);
113 mBubbleA2 = new Bubble(mContext, mEntryA2);
114 mBubbleA3 = new Bubble(mContext, mEntryA3);
115 mBubbleB1 = new Bubble(mContext, mEntryB1);
116 mBubbleB2 = new Bubble(mContext, mEntryB2);
117 mBubbleB3 = new Bubble(mContext, mEntryB3);
118 mBubbleC1 = new Bubble(mContext, mEntryC1);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400119
120 mBubbleData = new BubbleData(getContext());
121
122 // Used by BubbleData to set lastAccessedTime
123 when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
124 mBubbleData.setTimeSource(mTimeSource);
Mark Renoufba5ab512019-05-02 15:21:01 -0400125
126 // Assert baseline starting state
127 assertThat(mBubbleData.hasBubbles()).isFalse();
128 assertThat(mBubbleData.isExpanded()).isFalse();
129 assertThat(mBubbleData.getSelectedBubble()).isNull();
130 }
131
132 @Test
133 public void testAddBubble() {
134 // Setup
135 mBubbleData.setListener(mListener);
136
137 // Test
138 sendUpdatedEntryAtTime(mEntryA1, 1000);
139
140 // Verify
Mark Renouf82a40e62019-05-23 16:16:24 -0400141 verifyUpdateReceived();
142 assertBubbleAdded(mBubbleA1);
143 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400144 }
145
146 @Test
147 public void testRemoveBubble() {
148 // Setup
149 sendUpdatedEntryAtTime(mEntryA1, 1000);
150 sendUpdatedEntryAtTime(mEntryA2, 2000);
151 sendUpdatedEntryAtTime(mEntryA3, 3000);
152 mBubbleData.setListener(mListener);
153
154 // Test
155 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
156
157 // Verify
Mark Renouf82a40e62019-05-23 16:16:24 -0400158 verifyUpdateReceived();
159 assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renoufba5ab512019-05-02 15:21:01 -0400160 }
161
162 // COLLAPSED / ADD
163
164 /**
165 * Verifies that the number of bubbles is not allowed to exceed the maximum. The limit is
166 * enforced by expiring the bubble which was least recently updated (lowest timestamp).
167 */
168 @Test
169 public void test_collapsed_addBubble_atMaxBubbles_expiresOldest() {
170 // Setup
171 sendUpdatedEntryAtTime(mEntryA1, 1000);
172 sendUpdatedEntryAtTime(mEntryA2, 2000);
173 sendUpdatedEntryAtTime(mEntryA3, 3000);
174 sendUpdatedEntryAtTime(mEntryB1, 4000);
175 sendUpdatedEntryAtTime(mEntryB2, 5000);
176 mBubbleData.setListener(mListener);
177
178 // Test
179 sendUpdatedEntryAtTime(mEntryC1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400180 verifyUpdateReceived();
181 assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED);
Mark Renoufba5ab512019-05-02 15:21:01 -0400182 }
183
184 /**
185 * Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
186 * <p>
187 * Placement within the list is based on lastUpdate (post time of the notification), descending
188 * order (with most recent first).
189 *
190 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
191 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
192 */
193 @Test
194 public void test_collapsed_addBubble_sortAndGrouping() {
195 // Setup
196 mBubbleData.setListener(mListener);
197
198 // Test
199 sendUpdatedEntryAtTime(mEntryA1, 1000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400200 verifyUpdateReceived();
201 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400202
Mark Renoufba5ab512019-05-02 15:21:01 -0400203 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400204 verifyUpdateReceived();
205 assertOrderChangedTo(mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400206
Mark Renoufba5ab512019-05-02 15:21:01 -0400207 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400208 verifyUpdateReceived();
209 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400210
Mark Renoufba5ab512019-05-02 15:21:01 -0400211 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400212 verifyUpdateReceived();
213 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400214 }
215
216 /**
217 * Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
218 * Additionally, any bubble which is ongoing is considered "newer" than any non-ongoing bubble.
219 * <p>
220 * Because of the ongoing bubble, the new bubble cannot be placed in the first position. This
221 * causes the 'B' group to remain last, despite having a new button added.
222 *
223 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
224 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
225 */
226 @Test
227 public void test_collapsed_addBubble_sortAndGrouping_withOngoing() {
228 // Setup
229 mBubbleData.setListener(mListener);
230
231 // Test
232 setOngoing(mEntryA1, true);
233 sendUpdatedEntryAtTime(mEntryA1, 1000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400234 verifyUpdateReceived();
235 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400236
Mark Renoufba5ab512019-05-02 15:21:01 -0400237 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400238 verifyUpdateReceived();
239 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400240
Mark Renoufba5ab512019-05-02 15:21:01 -0400241 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400242 verifyUpdateReceived();
243 assertOrderChangedTo(mBubbleA1, mBubbleB2, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400244
Mark Renoufba5ab512019-05-02 15:21:01 -0400245 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400246 verifyUpdateReceived();
247 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400248 }
249
250 /**
251 * Verifies that new bubbles become the selected bubble when they appear when the stack is in
252 * the collapsed state.
253 *
254 * @see #test_collapsed_updateBubble_selectionChanges()
255 * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
256 */
257 @Test
258 public void test_collapsed_addBubble_selectionChanges() {
259 // Setup
260 mBubbleData.setListener(mListener);
261
262 // Test
263 sendUpdatedEntryAtTime(mEntryA1, 1000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400264 verifyUpdateReceived();
265 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400266
Mark Renoufba5ab512019-05-02 15:21:01 -0400267 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400268 verifyUpdateReceived();
269 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400270
Mark Renoufba5ab512019-05-02 15:21:01 -0400271 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400272 verifyUpdateReceived();
273 assertSelectionChangedTo(mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400274
Mark Renoufba5ab512019-05-02 15:21:01 -0400275 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400276 verifyUpdateReceived();
277 assertSelectionChangedTo(mBubbleA2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400278 }
Mark Renouf82a40e62019-05-23 16:16:24 -0400279
Mark Renoufba5ab512019-05-02 15:21:01 -0400280 /**
281 * Verifies that while collapsed, the selection will not change if the selected bubble is
282 * ongoing. It remains the top bubble and as such remains selected.
283 *
284 * @see #test_collapsed_addBubble_selectionChanges()
285 */
286 @Test
287 public void test_collapsed_addBubble_noSelectionChanges_withOngoing() {
288 // Setup
289 setOngoing(mEntryA1, true);
290 sendUpdatedEntryAtTime(mEntryA1, 1000);
291 assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
292 mBubbleData.setListener(mListener);
293
294 // Test
295 sendUpdatedEntryAtTime(mEntryB1, 2000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400296 verifyUpdateReceived();
297 assertSelectionNotChanged();
298
Mark Renoufba5ab512019-05-02 15:21:01 -0400299 sendUpdatedEntryAtTime(mEntryB2, 3000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400300 verifyUpdateReceived();
301 assertSelectionNotChanged();
302
Mark Renoufba5ab512019-05-02 15:21:01 -0400303 sendUpdatedEntryAtTime(mEntryA2, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400304 verifyUpdateReceived();
305 assertSelectionNotChanged();
306
Mark Renoufba5ab512019-05-02 15:21:01 -0400307 assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1); // selection unchanged
308 }
309
310 // COLLAPSED / REMOVE
311
312 /**
313 * Verifies that groups may reorder when bubbles are removed, while the stack is in the
314 * collapsed state.
315 */
316 @Test
317 public void test_collapsed_removeBubble_sortAndGrouping() {
318 // Setup
319 sendUpdatedEntryAtTime(mEntryA1, 1000);
320 sendUpdatedEntryAtTime(mEntryB1, 2000);
321 sendUpdatedEntryAtTime(mEntryB2, 3000);
322 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
323 mBubbleData.setListener(mListener);
324
325 // Test
326 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400327 verifyUpdateReceived();
328 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400329 }
330
331
332 /**
333 * Verifies that onOrderChanged is not called when a bubble is removed if the removal does not
334 * cause other bubbles to change position.
335 */
336 @Test
337 public void test_collapsed_removeOldestBubble_doesNotCallOnOrderChanged() {
338 // Setup
339 sendUpdatedEntryAtTime(mEntryA1, 1000);
340 sendUpdatedEntryAtTime(mEntryB1, 2000);
341 sendUpdatedEntryAtTime(mEntryB2, 3000);
342 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
343 mBubbleData.setListener(mListener);
344
345 // Test
346 mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400347 verifyUpdateReceived();
348 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400349 }
350
351 /**
352 * Verifies that bubble ordering reverts to normal when an ongoing bubble is removed. A group
353 * which has a newer bubble may move to the front after the ongoing bubble is removed.
354 */
355 @Test
356 public void test_collapsed_removeBubble_sortAndGrouping_withOngoing() {
357 // Setup
358 setOngoing(mEntryA1, true);
359 sendUpdatedEntryAtTime(mEntryA1, 1000);
360 sendUpdatedEntryAtTime(mEntryA2, 2000);
361 sendUpdatedEntryAtTime(mEntryB1, 3000);
362 sendUpdatedEntryAtTime(mEntryB2, 4000); // [A1*, A2, B2, B1]
363 mBubbleData.setListener(mListener);
364
365 // Test
366 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
Mark Renouf82a40e62019-05-23 16:16:24 -0400367 verifyUpdateReceived();
368 assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400369 }
370
371 /**
372 * Verifies that when the selected bubble is removed with the stack in the collapsed state,
373 * the selection moves to the next most-recently updated bubble.
374 */
375 @Test
376 public void test_collapsed_removeBubble_selectionChanges() {
377 // Setup
378 sendUpdatedEntryAtTime(mEntryA1, 1000);
379 sendUpdatedEntryAtTime(mEntryB1, 2000);
380 sendUpdatedEntryAtTime(mEntryB2, 3000);
381 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
382 mBubbleData.setListener(mListener);
383
384 // Test
385 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL);
Mark Renouf82a40e62019-05-23 16:16:24 -0400386 verifyUpdateReceived();
387 assertSelectionChangedTo(mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400388 }
389
390 // COLLAPSED / UPDATE
391
392 /**
393 * Verifies that bubble and group ordering may change with updates while the stack is in the
394 * collapsed state.
395 */
396 @Test
397 public void test_collapsed_updateBubble_orderAndGrouping() {
398 // Setup
399 sendUpdatedEntryAtTime(mEntryA1, 1000);
400 sendUpdatedEntryAtTime(mEntryB1, 2000);
401 sendUpdatedEntryAtTime(mEntryB2, 3000);
402 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
403 mBubbleData.setListener(mListener);
404
405 // Test
406 sendUpdatedEntryAtTime(mEntryB1, 5000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400407 verifyUpdateReceived();
408 assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400409
Mark Renoufba5ab512019-05-02 15:21:01 -0400410 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400411 verifyUpdateReceived();
412 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400413 }
414
415 /**
416 * Verifies that selection tracks the most recently updated bubble while in the collapsed state.
417 */
418 @Test
419 public void test_collapsed_updateBubble_selectionChanges() {
420 // Setup
421 sendUpdatedEntryAtTime(mEntryA1, 1000);
422 sendUpdatedEntryAtTime(mEntryB1, 2000);
423 sendUpdatedEntryAtTime(mEntryB2, 3000);
424 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
425 mBubbleData.setListener(mListener);
426
427 // Test
428 sendUpdatedEntryAtTime(mEntryB1, 5000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400429 verifyUpdateReceived();
430 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400431
Mark Renoufba5ab512019-05-02 15:21:01 -0400432 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400433 verifyUpdateReceived();
434 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400435 }
436
437 /**
438 * Verifies that selection does not change in response to updates when collapsed, if the
439 * selected bubble is ongoing.
440 */
441 @Test
442 public void test_collapsed_updateBubble_noSelectionChanges_withOngoing() {
443 // Setup
444 setOngoing(mEntryA1, true);
445 sendUpdatedEntryAtTime(mEntryA1, 1000);
446 sendUpdatedEntryAtTime(mEntryB1, 2000);
447 sendUpdatedEntryAtTime(mEntryB2, 3000);
448 sendUpdatedEntryAtTime(mEntryA2, 4000); // [A1*, A2, B2, B1]
449 mBubbleData.setListener(mListener);
450
451 // Test
452 sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1]
Mark Renouf82a40e62019-05-23 16:16:24 -0400453 verifyUpdateReceived();
454 assertSelectionNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400455 }
456
457 /**
458 * Verifies that a request to expand the stack has no effect if there are no bubbles.
459 */
460 @Test
461 public void test_collapsed_expansion_whenEmpty_doesNothing() {
462 assertThat(mBubbleData.hasBubbles()).isFalse();
Mark Renouf82a40e62019-05-23 16:16:24 -0400463 mBubbleData.setListener(mListener);
Mark Renoufba5ab512019-05-02 15:21:01 -0400464
Mark Renouf82a40e62019-05-23 16:16:24 -0400465 changeExpandedStateAtTime(true, 2000L);
466 verifyZeroInteractions(mListener);
Mark Renoufba5ab512019-05-02 15:21:01 -0400467 }
468
469 @Test
470 public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
471 // Setup
472 sendUpdatedEntryAtTime(mEntryA1, 1000);
473 mBubbleData.setListener(mListener);
474
475 // Test
476 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
477
478 // Verify the selection was cleared.
Mark Renouf82a40e62019-05-23 16:16:24 -0400479 verifyUpdateReceived();
480 assertSelectionCleared();
Mark Renoufba5ab512019-05-02 15:21:01 -0400481 }
482
483 // EXPANDED / ADD
484
485 /**
486 * Verifies that bubbles added as part of a new group insert before existing groups while
487 * expanded.
488 * <p>
489 * Placement within the list is based on lastUpdate (post time of the notification), descending
490 * order (with most recent first).
491 *
492 * @see #test_collapsed_addBubble_sortAndGrouping()
493 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
494 */
495 @Test
496 public void test_expanded_addBubble_sortAndGrouping_newGroup() {
497 // Setup
498 sendUpdatedEntryAtTime(mEntryA1, 1000);
499 sendUpdatedEntryAtTime(mEntryA2, 2000);
500 sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
501 changeExpandedStateAtTime(true, 4000L);
502 mBubbleData.setListener(mListener);
503
504 // Test
505 sendUpdatedEntryAtTime(mEntryC1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400506 verifyUpdateReceived();
507 assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400508 }
509
510 /**
511 * Verifies that bubbles added as part of a new group insert before existing groups while
512 * expanded, but not before any groups with ongoing bubbles.
513 *
514 * @see #test_collapsed_addBubble_sortAndGrouping_withOngoing()
515 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
516 */
517 @Test
518 public void test_expanded_addBubble_sortAndGrouping_newGroup_withOngoing() {
519 // Setup
520 setOngoing(mEntryA1, true);
521 sendUpdatedEntryAtTime(mEntryA1, 1000);
522 sendUpdatedEntryAtTime(mEntryA2, 2000);
523 sendUpdatedEntryAtTime(mEntryB1, 3000); // [A1*, A2, B1]
524 changeExpandedStateAtTime(true, 4000L);
525 mBubbleData.setListener(mListener);
526
527 // Test
528 sendUpdatedEntryAtTime(mEntryC1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400529 verifyUpdateReceived();
530 assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400531 }
532
533 /**
534 * Verifies that bubbles added as part of an existing group insert to the beginning of that
535 * group. The order of groups within the list must not change while in the expanded state.
536 *
537 * @see #test_collapsed_addBubble_sortAndGrouping()
538 * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
539 */
540 @Test
541 public void test_expanded_addBubble_sortAndGrouping_existingGroup() {
542 // Setup
543 sendUpdatedEntryAtTime(mEntryA1, 1000);
544 sendUpdatedEntryAtTime(mEntryA2, 2000);
545 sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
546 changeExpandedStateAtTime(true, 4000L);
547 mBubbleData.setListener(mListener);
548
549 // Test
550 sendUpdatedEntryAtTime(mEntryA3, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400551 verifyUpdateReceived();
552 assertOrderChangedTo(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400553 }
554
555 // EXPANDED / UPDATE
556
557 /**
558 * Verifies that updates to bubbles while expanded do not result in any change to sorting
559 * or grouping of bubbles or sorting of groups.
560 *
561 * @see #test_collapsed_addBubble_sortAndGrouping()
562 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
563 */
564 @Test
565 public void test_expanded_updateBubble_sortAndGrouping_noChanges() {
566 // Setup
567 sendUpdatedEntryAtTime(mEntryA1, 1000);
568 sendUpdatedEntryAtTime(mEntryA2, 2000);
569 sendUpdatedEntryAtTime(mEntryB1, 3000);
570 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
571 changeExpandedStateAtTime(true, 5000L);
572 mBubbleData.setListener(mListener);
573
574 // Test
575 sendUpdatedEntryAtTime(mEntryA1, 4000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400576 verifyUpdateReceived();
577 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400578 }
579
580 /**
581 * Verifies that updates to bubbles while expanded do not result in any change to selection.
582 *
583 * @see #test_collapsed_addBubble_selectionChanges()
584 * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
585 */
586 @Test
587 public void test_expanded_updateBubble_noSelectionChanges() {
588 // Setup
589 sendUpdatedEntryAtTime(mEntryA1, 1000);
590 sendUpdatedEntryAtTime(mEntryA2, 2000);
591 sendUpdatedEntryAtTime(mEntryB1, 3000);
592 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
593 changeExpandedStateAtTime(true, 5000L);
594 mBubbleData.setListener(mListener);
595
596 // Test
597 sendUpdatedEntryAtTime(mEntryA1, 6000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400598 verifyUpdateReceived();
599 assertOrderNotChanged();
600
Mark Renoufba5ab512019-05-02 15:21:01 -0400601 sendUpdatedEntryAtTime(mEntryA2, 7000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400602 verifyUpdateReceived();
603 assertOrderNotChanged();
604
Mark Renoufba5ab512019-05-02 15:21:01 -0400605 sendUpdatedEntryAtTime(mEntryB1, 8000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400606 verifyUpdateReceived();
607 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400608 }
609
610 // EXPANDED / REMOVE
611
612 /**
613 * Verifies that removing a bubble while expanded does not result in reordering of groups
614 * or any of the remaining bubbles.
615 *
616 * @see #test_collapsed_addBubble_sortAndGrouping()
617 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
618 */
619 @Test
620 public void test_expanded_removeBubble_sortAndGrouping() {
621 // Setup
622 sendUpdatedEntryAtTime(mEntryA1, 1000);
623 sendUpdatedEntryAtTime(mEntryB1, 2000);
624 sendUpdatedEntryAtTime(mEntryA2, 3000);
625 sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
626 changeExpandedStateAtTime(true, 5000L);
627 mBubbleData.setListener(mListener);
628
629 // Test
630 mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400631 verifyUpdateReceived();
632 assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400633 }
634
635 /**
636 * Verifies that removing the selected bubble while expanded causes another bubble to become
637 * selected. The replacement selection is the bubble which appears at the same index as the
638 * previous one, or the previous index if this was the last position.
639 *
640 * @see #test_collapsed_addBubble_sortAndGrouping()
641 * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
642 */
643 @Test
644 public void test_expanded_removeBubble_selectionChanges_whenSelectedRemoved() {
645 // Setup
646 sendUpdatedEntryAtTime(mEntryA1, 1000);
647 sendUpdatedEntryAtTime(mEntryB1, 2000);
648 sendUpdatedEntryAtTime(mEntryA2, 3000);
649 sendUpdatedEntryAtTime(mEntryB2, 4000);
650 changeExpandedStateAtTime(true, 5000L);
651 mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
652 mBubbleData.setListener(mListener);
653
654 // Test
655 mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400656 verifyUpdateReceived();
657 assertSelectionChangedTo(mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400658
Mark Renoufba5ab512019-05-02 15:21:01 -0400659 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400660 verifyUpdateReceived();
661 assertSelectionChangedTo(mBubbleB1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400662 }
663
664 @Test
665 public void test_expandAndCollapse_callsOnExpandedChanged() {
666 // Setup
667 sendUpdatedEntryAtTime(mEntryA1, 1000);
668 mBubbleData.setListener(mListener);
669
670 // Test
671 changeExpandedStateAtTime(true, 3000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400672 verifyUpdateReceived();
673 assertExpandedChangedTo(true);
Mark Renoufba5ab512019-05-02 15:21:01 -0400674
Mark Renoufba5ab512019-05-02 15:21:01 -0400675 changeExpandedStateAtTime(false, 4000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400676 verifyUpdateReceived();
677 assertExpandedChangedTo(false);
Mark Renoufba5ab512019-05-02 15:21:01 -0400678 }
679
680 /**
681 * Verifies that transitions between the collapsed and expanded state maintain sorting and
682 * grouping rules.
683 * <p>
684 * While collapsing, sorting is applied since no sorting happens while expanded. The resulting
685 * state is the new expanded ordering. This state is saved and restored if possible when next
686 * expanded.
687 * <p>
688 * When the stack transitions to the collapsed state, the selected bubble is brought to the top.
689 * Bubbles within the same group should move up with it.
690 * <p>
691 * When the stack transitions back to the expanded state, the previous ordering is restored, as
692 * long as no changes have been made (adds, removes or updates) while in the collapsed state.
693 */
694 @Test
695 public void test_expansionChanges() {
696 // Setup
697 sendUpdatedEntryAtTime(mEntryA1, 1000);
698 sendUpdatedEntryAtTime(mEntryB1, 2000);
699 sendUpdatedEntryAtTime(mEntryA2, 3000);
700 sendUpdatedEntryAtTime(mEntryB2, 4000);
701 changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
702 sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=6000*, A2=3000, A1=1000]
703 setCurrentTime(7000);
704 mBubbleData.setSelectedBubble(mBubbleA2);
705 mBubbleData.setListener(mListener);
706 assertThat(mBubbleData.getBubbles()).isEqualTo(
Mark Renouf82a40e62019-05-23 16:16:24 -0400707 ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
Mark Renoufba5ab512019-05-02 15:21:01 -0400708
709 // Test
710
711 // At this point, B1 has been updated but sorting has not been changed because the
712 // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
713 // to moving the selected bubble to the top (first).
714 //
715 // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
716 //
717 // That state is restored as long as no changes occur (add/remove/update) while in
718 // the collapsed state.
719 //
720 // collapse -> selected bubble (A2) moves first.
721 changeExpandedStateAtTime(false, 8000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400722 verifyUpdateReceived();
723 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400724
725 // expand -> "original" order/grouping restored
Mark Renoufba5ab512019-05-02 15:21:01 -0400726 changeExpandedStateAtTime(true, 10000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400727 verifyUpdateReceived();
728 assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
Mark Renoufba5ab512019-05-02 15:21:01 -0400729 }
730
731 /**
732 * When a change occurs while collapsed (any update, add, remove), the previous expanded
733 * order and grouping becomes invalidated, and the order and grouping when next expanded will
734 * remain the same as collapsed.
735 */
736 @Test
737 public void test_expansionChanges_withUpdatesWhileCollapsed() {
738 // Setup
739 sendUpdatedEntryAtTime(mEntryA1, 1000);
740 sendUpdatedEntryAtTime(mEntryB1, 2000);
741 sendUpdatedEntryAtTime(mEntryA2, 3000);
742 sendUpdatedEntryAtTime(mEntryB2, 4000);
743 changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
744 sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=*6000, A2=3000, A1=1000]
745 setCurrentTime(7000);
746 mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
747 mBubbleData.setListener(mListener);
748
749 // Test
750
751 // At this point, B1 has been updated but sorting has not been changed because the
752 // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
753 // to moving the selected bubble to the top (first).
754 //
755 // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
756 //
757 // That state is restored as long as no changes occur (add/remove/update) while in
758 // the collapsed state.
759 //
760 // collapse -> selected bubble (A2) moves first.
761 changeExpandedStateAtTime(false, 8000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400762 verifyUpdateReceived();
763 assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
Mark Renoufba5ab512019-05-02 15:21:01 -0400764
765 // An update occurs, which causes sorting, and this invalidates the previously saved order.
766 sendUpdatedEntryAtTime(mEntryA2, 9000);
Mark Renouf82a40e62019-05-23 16:16:24 -0400767 verifyUpdateReceived();
Mark Renoufba5ab512019-05-02 15:21:01 -0400768
769 // No order changes when expanding because the new sorted order remains.
Mark Renoufba5ab512019-05-02 15:21:01 -0400770 changeExpandedStateAtTime(true, 10000L);
Mark Renouf82a40e62019-05-23 16:16:24 -0400771 verifyUpdateReceived();
772 assertOrderNotChanged();
Mark Renoufba5ab512019-05-02 15:21:01 -0400773 }
774
775 @Test
776 public void test_expanded_removeLastBubble_collapsesStack() {
777 // Setup
778 sendUpdatedEntryAtTime(mEntryA1, 1000);
779 changeExpandedStateAtTime(true, 2000);
780 mBubbleData.setListener(mListener);
781
782 // Test
783 mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
Mark Renouf82a40e62019-05-23 16:16:24 -0400784 verifyUpdateReceived();
785 assertExpandedChangedTo(false);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400786 }
787
Mark Renouf82a40e62019-05-23 16:16:24 -0400788 private void verifyUpdateReceived() {
789 verify(mListener).applyUpdate(mUpdateCaptor.capture());
790 reset(mListener);
791 }
792
793 private void assertBubbleAdded(Bubble expected) {
794 BubbleData.Update update = mUpdateCaptor.getValue();
795 assertThat(update.addedBubble).named("addedBubble").isEqualTo(expected);
796 }
797
798 private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) {
799 BubbleData.Update update = mUpdateCaptor.getValue();
800 assertThat(update.removedBubbles).named("removedBubbles")
801 .isEqualTo(ImmutableList.of(Pair.create(expected, reason)));
802 }
803
804 private void assertOrderNotChanged() {
805 BubbleData.Update update = mUpdateCaptor.getValue();
806 assertThat(update.orderChanged).named("orderChanged").isFalse();
807 }
808
809 private void assertOrderChangedTo(Bubble... order) {
810 BubbleData.Update update = mUpdateCaptor.getValue();
811 assertThat(update.orderChanged).named("orderChanged").isTrue();
812 assertThat(update.bubbles).named("bubble order").isEqualTo(ImmutableList.copyOf(order));
813 }
814
815 private void assertSelectionNotChanged() {
816 BubbleData.Update update = mUpdateCaptor.getValue();
817 assertThat(update.selectionChanged).named("selectionChanged").isFalse();
818 }
819
820 private void assertSelectionChangedTo(Bubble bubble) {
821 BubbleData.Update update = mUpdateCaptor.getValue();
822 assertThat(update.selectionChanged).named("selectionChanged").isTrue();
823 assertThat(update.selectedBubble).named("selectedBubble").isEqualTo(bubble);
824 }
825
826 private void assertSelectionCleared() {
827 BubbleData.Update update = mUpdateCaptor.getValue();
828 assertThat(update.selectionChanged).named("selectionChanged").isTrue();
829 assertThat(update.selectedBubble).named("selectedBubble").isNull();
830 }
831
832 private void assertExpandedChangedTo(boolean expected) {
833 BubbleData.Update update = mUpdateCaptor.getValue();
834 assertThat(update.expandedChanged).named("expandedChanged").isTrue();
835 assertThat(update.expanded).named("expanded").isEqualTo(expected);
836 }
837
838
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400839 private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
840 return createBubbleEntry(userId, notifKey, packageName, 1000);
841 }
842
843 private void setPostTime(NotificationEntry entry, long postTime) {
844 when(entry.notification.getPostTime()).thenReturn(postTime);
845 }
846
847 private void setOngoing(NotificationEntry entry, boolean ongoing) {
848 if (ongoing) {
849 entry.notification.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
850 } else {
851 entry.notification.getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
852 }
853 }
854
855 /**
856 * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
857 * required for BubbleData functionality and verification. NotificationTestHelper is used only
858 * as a convenience to create a Notification w/BubbleMetadata.
859 */
860 private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
861 long postTime) {
862 // BubbleMetadata
863 Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder()
864 .setIntent(mExpandIntent)
865 .setDeleteIntent(mDeleteIntent)
866 .setIcon(Icon.createWithResource("", 0))
867 .build();
868 // Notification -> BubbleMetadata
869 Notification notification = mNotificationTestHelper.createNotification(false,
870 null /* groupKey */, bubbleMetadata);
871
872 // StatusBarNotification
873 StatusBarNotification sbn = mock(StatusBarNotification.class);
874 when(sbn.getKey()).thenReturn(notifKey);
875 when(sbn.getUser()).thenReturn(new UserHandle(userId));
876 when(sbn.getPackageName()).thenReturn(packageName);
877 when(sbn.getPostTime()).thenReturn(postTime);
878 when(sbn.getNotification()).thenReturn(notification);
879
880 // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
881 return new NotificationEntry(sbn);
882 }
883
Mark Renoufba5ab512019-05-02 15:21:01 -0400884 private void setCurrentTime(long time) {
885 when(mTimeSource.currentTimeMillis()).thenReturn(time);
886 }
887
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400888 private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
889 setPostTime(entry, postTime);
Mark Renoufc19b4732019-06-26 12:08:33 -0400890 mBubbleData.notificationEntryUpdated(entry, /* suppressFlyout=*/ false);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400891 }
892
893 private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
Mark Renoufba5ab512019-05-02 15:21:01 -0400894 setCurrentTime(time);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400895 mBubbleData.setExpanded(shouldBeExpanded);
896 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700897}