blob: a31e3f8d7cc903a5d52c7e97dd82cc59fa2c900d [file] [log] [blame]
Beverlya53fb0d2020-01-29 15:26:13 -05001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.bubbles;
18
19import static android.app.Notification.FLAG_BUBBLE;
20import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
21
22import static com.google.common.truth.Truth.assertThat;
23
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertNotNull;
27import static org.junit.Assert.assertNull;
28import static org.junit.Assert.assertTrue;
29import static org.mockito.ArgumentMatchers.anyBoolean;
30import static org.mockito.ArgumentMatchers.anyInt;
31import static org.mockito.ArgumentMatchers.anyString;
32import static org.mockito.ArgumentMatchers.eq;
33import static org.mockito.Mockito.atLeastOnce;
34import static org.mockito.Mockito.mock;
35import static org.mockito.Mockito.never;
36import static org.mockito.Mockito.times;
37import static org.mockito.Mockito.verify;
38import static org.mockito.Mockito.when;
39
40import android.app.IActivityManager;
41import android.app.Notification;
42import android.app.PendingIntent;
Beverlya53fb0d2020-01-29 15:26:13 -050043import android.content.res.Resources;
Beverly Taid1e175c2020-03-10 16:37:04 +000044import android.hardware.display.AmbientDisplayConfiguration;
Beverlya53fb0d2020-01-29 15:26:13 -050045import android.hardware.face.FaceManager;
Beverly Taid1e175c2020-03-10 16:37:04 +000046import android.os.Handler;
47import android.os.PowerManager;
48import android.service.dreams.IDreamManager;
Beverlya53fb0d2020-01-29 15:26:13 -050049import android.service.notification.ZenModeConfig;
50import android.testing.AndroidTestingRunner;
51import android.testing.TestableLooper;
52import android.view.WindowManager;
53
54import androidx.test.filters.SmallTest;
55
56import com.android.internal.colorextraction.ColorExtractor;
Beverlya53fb0d2020-01-29 15:26:13 -050057import com.android.systemui.SystemUIFactory;
58import com.android.systemui.SysuiTestCase;
59import com.android.systemui.colorextraction.SysuiColorExtractor;
Ned Burnsaaeb44b2020-02-12 23:48:26 -050060import com.android.systemui.dump.DumpManager;
Joshua Tsujibe60a582020-03-23 17:17:26 -040061import com.android.systemui.model.SysUiState;
Beverlya53fb0d2020-01-29 15:26:13 -050062import com.android.systemui.plugins.statusbar.StatusBarStateController;
63import com.android.systemui.statusbar.FeatureFlags;
64import com.android.systemui.statusbar.NotificationLockscreenUserManager;
Beverlya53fb0d2020-01-29 15:26:13 -050065import com.android.systemui.statusbar.SuperStatusBarViewFactory;
66import com.android.systemui.statusbar.SysuiStatusBarStateController;
67import com.android.systemui.statusbar.notification.NotificationEntryManager;
68import com.android.systemui.statusbar.notification.NotificationFilter;
Beverlya53fb0d2020-01-29 15:26:13 -050069import com.android.systemui.statusbar.notification.collection.NotifPipeline;
70import com.android.systemui.statusbar.notification.collection.NotificationEntry;
71import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
72import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
73import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
74import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
75import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
76import com.android.systemui.statusbar.phone.DozeParameters;
77import com.android.systemui.statusbar.phone.KeyguardBypassController;
Dave Mankoffc0d9a7d2020-02-27 17:15:52 -050078import com.android.systemui.statusbar.phone.LockscreenLockIconController;
Beverlya53fb0d2020-01-29 15:26:13 -050079import com.android.systemui.statusbar.phone.NotificationGroupManager;
80import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
81import com.android.systemui.statusbar.phone.ShadeController;
82import com.android.systemui.statusbar.policy.BatteryController;
83import com.android.systemui.statusbar.policy.ConfigurationController;
84import com.android.systemui.statusbar.policy.HeadsUpManager;
85import com.android.systemui.statusbar.policy.ZenModeController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -050086import com.android.systemui.util.FloatingContentCoordinator;
Beverlya53fb0d2020-01-29 15:26:13 -050087import com.android.systemui.util.InjectionInflationController;
88
89import org.junit.Before;
90import org.junit.Test;
91import org.junit.runner.RunWith;
92import org.mockito.ArgumentCaptor;
93import org.mockito.Captor;
94import org.mockito.Mock;
95import org.mockito.MockitoAnnotations;
96
97import java.util.List;
98
99/**
100 * Tests the NotifPipeline setup with BubbleController.
101 * The NotificationEntryManager setup with BubbleController is tested in
102 * {@link BubbleControllerTest}.
103 */
104@SmallTest
105@RunWith(AndroidTestingRunner.class)
106@TestableLooper.RunWithLooper(setAsMainLooper = true)
107public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
108 @Mock
109 private NotificationEntryManager mNotificationEntryManager;
110 @Mock
111 private NotificationGroupManager mNotificationGroupManager;
112 @Mock
113 private BubbleController.NotifCallback mNotifCallback;
114 @Mock
115 private WindowManager mWindowManager;
116 @Mock
117 private IActivityManager mActivityManager;
118 @Mock
119 private DozeParameters mDozeParameters;
120 @Mock
121 private ConfigurationController mConfigurationController;
122 @Mock
123 private ZenModeController mZenModeController;
124 @Mock
125 private ZenModeConfig mZenModeConfig;
126 @Mock
127 private FaceManager mFaceManager;
128 @Mock
129 private NotificationLockscreenUserManager mLockscreenUserManager;
130 @Mock
131 private SysuiStatusBarStateController mStatusBarStateController;
132 @Mock
133 private KeyguardBypassController mKeyguardBypassController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500134 @Mock
135 private FloatingContentCoordinator mFloatingContentCoordinator;
Joshua Tsujibe60a582020-03-23 17:17:26 -0400136
137 private SysUiState mSysUiState = new SysUiState();
138
Beverlya53fb0d2020-01-29 15:26:13 -0500139 @Captor
140 private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
Beverlya53fb0d2020-01-29 15:26:13 -0500141 private TestableBubbleController mBubbleController;
142 private NotificationShadeWindowController mNotificationShadeWindowController;
143 private NotifCollectionListener mEntryListener;
Beverlya53fb0d2020-01-29 15:26:13 -0500144 private NotificationTestHelper mNotificationTestHelper;
145 private ExpandableNotificationRow mRow;
146 private ExpandableNotificationRow mRow2;
147 private ExpandableNotificationRow mNonBubbleNotifRow;
Beverlya53fb0d2020-01-29 15:26:13 -0500148 @Mock
149 private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener;
150 @Mock
151 private BubbleController.BubbleExpandListener mBubbleExpandListener;
152 @Mock
153 private PendingIntent mDeleteIntent;
154 @Mock
155 private SysuiColorExtractor mColorExtractor;
156 @Mock
157 ColorExtractor.GradientColors mGradientColors;
158 @Mock
159 private Resources mResources;
160 @Mock
161 private ShadeController mShadeController;
162 @Mock
163 private NotificationRowComponent mNotificationRowComponent;
164 @Mock
165 private NotifPipeline mNotifPipeline;
166 @Mock
167 private FeatureFlags mFeatureFlagsNewPipeline;
168 @Mock
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500169 private DumpManager mDumpManager;
Dave Mankoffc0d9a7d2020-02-27 17:15:52 -0500170 @Mock
171 private LockscreenLockIconController mLockIconController;
Beverlya53fb0d2020-01-29 15:26:13 -0500172
173 private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
174 private BubbleData mBubbleData;
175
176 private TestableLooper mTestableLooper;
177
178 @Before
179 public void setUp() throws Exception {
180 MockitoAnnotations.initMocks(this);
181
182 mTestableLooper = TestableLooper.get(this);
183
184 mContext.addMockSystemService(FaceManager.class, mFaceManager);
185 when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
186
187 mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
188 new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
189 new NotificationRowComponent.Builder() {
190 @Override
191 public NotificationRowComponent.Builder activatableNotificationView(
192 ActivatableNotificationView view) {
193 return this;
194 }
195
196 @Override
197 public NotificationRowComponent build() {
198 return mNotificationRowComponent;
199 }
Dave Mankoffc0d9a7d2020-02-27 17:15:52 -0500200 },
201 mLockIconController);
Beverlya53fb0d2020-01-29 15:26:13 -0500202
203 // Bubbles get added to status bar window view
204 mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
205 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
Lucas Dupind73410a2020-02-18 12:54:41 -0800206 mConfigurationController, mKeyguardBypassController, mColorExtractor,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500207 mDumpManager);
wilsonshihf587b632020-02-12 13:16:12 +0800208 mNotificationShadeWindowController.setNotificationShadeView(
209 mSuperStatusBarViewFactory.getNotificationShadeWindowView());
Beverlya53fb0d2020-01-29 15:26:13 -0500210 mNotificationShadeWindowController.attach();
211
212 // Need notifications for bubbles
213 mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
214 mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
215 mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
216 mNonBubbleNotifRow = mNotificationTestHelper.createRow();
217
218 mZenModeConfig.suppressedVisualEffects = 0;
219 when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
220
Beverly Taid1e175c2020-03-10 16:37:04 +0000221 TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
222 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
223 mock(PowerManager.class),
224 mock(IDreamManager.class),
225 mock(AmbientDisplayConfiguration.class),
Beverlya53fb0d2020-01-29 15:26:13 -0500226 mock(NotificationFilter.class),
227 mock(StatusBarStateController.class),
Beverly Taid1e175c2020-03-10 16:37:04 +0000228 mock(BatteryController.class),
229 mock(HeadsUpManager.class),
230 mock(Handler.class)
231 );
Beverlya53fb0d2020-01-29 15:26:13 -0500232 mBubbleData = new BubbleData(mContext);
233 when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500234 mBubbleController = new TestableBubbleController(
235 mContext,
Beverlya53fb0d2020-01-29 15:26:13 -0500236 mNotificationShadeWindowController,
237 mStatusBarStateController,
238 mShadeController,
239 mBubbleData,
240 mConfigurationController,
241 interruptionStateProvider,
242 mZenModeController,
243 mLockscreenUserManager,
244 mNotificationGroupManager,
245 mNotificationEntryManager,
246 mNotifPipeline,
247 mFeatureFlagsNewPipeline,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500248 mDumpManager,
Joshua Tsujibe60a582020-03-23 17:17:26 -0400249 mFloatingContentCoordinator,
250 mSysUiState);
Beverlya53fb0d2020-01-29 15:26:13 -0500251 mBubbleController.addNotifCallback(mNotifCallback);
252 mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
253 mBubbleController.setExpandListener(mBubbleExpandListener);
254
255 // Get a reference to the BubbleController's entry listener
256 verify(mNotifPipeline, atLeastOnce())
257 .addCollectionListener(mNotifListenerCaptor.capture());
258 mEntryListener = mNotifListenerCaptor.getValue();
259 }
260
261 @Test
262 public void testAddBubble() {
263 mBubbleController.updateBubble(mRow.getEntry());
264 assertTrue(mBubbleController.hasBubbles());
265
266 verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
267 }
268
269 @Test
270 public void testHasBubbles() {
271 assertFalse(mBubbleController.hasBubbles());
272 mBubbleController.updateBubble(mRow.getEntry());
273 assertTrue(mBubbleController.hasBubbles());
274 }
275
276 @Test
277 public void testRemoveBubble() {
278 mBubbleController.updateBubble(mRow.getEntry());
279 assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
280 assertTrue(mBubbleController.hasBubbles());
281 verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
282 verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
283
284 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
285 assertFalse(mNotificationShadeWindowController.getBubblesShowing());
286 assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
287 verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
288 verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
289 }
290
291 @Test
292 public void testRemoveBubble_withDismissedNotif() {
293 mEntryListener.onEntryAdded(mRow.getEntry());
294 mBubbleController.updateBubble(mRow.getEntry());
295
296 assertTrue(mBubbleController.hasBubbles());
297 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
298
299 // Make it look like dismissed notif
300 mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
301
302 // Now remove the bubble
303 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
304
305 // Since the notif is dismissed, once the bubble is removed, removeNotification gets
306 // called to really remove the notif
307 verify(mNotifCallback, times(1)).removeNotification(eq(mRow.getEntry()), anyInt());
308 assertFalse(mBubbleController.hasBubbles());
309 }
310
311 @Test
312 public void testDismissStack() {
313 mBubbleController.updateBubble(mRow.getEntry());
314 verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
315 assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
316 mBubbleController.updateBubble(mRow2.getEntry());
Lyn Han9f66c3b2020-03-05 23:59:29 -0800317 verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
Beverlya53fb0d2020-01-29 15:26:13 -0500318 assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
319 assertTrue(mBubbleController.hasBubbles());
320
321 mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
322 assertFalse(mNotificationShadeWindowController.getBubblesShowing());
323 verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
324 assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
325 assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
326 }
327
328 @Test
329 public void testExpandCollapseStack() {
330 assertFalse(mBubbleController.isStackExpanded());
331
332 // Mark it as a bubble and add it explicitly
333 mEntryListener.onEntryAdded(mRow.getEntry());
334 mBubbleController.updateBubble(mRow.getEntry());
335
336 // We should have bubbles & their notifs should not be suppressed
337 assertTrue(mBubbleController.hasBubbles());
338 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
339 mRow.getEntry()));
340 assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
341
342 // Expand the stack
343 BubbleStackView stackView = mBubbleController.getStackView();
344 mBubbleController.expandStack();
345 assertTrue(mBubbleController.isStackExpanded());
346 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
347 assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
348
349 // Make sure the notif is suppressed
350 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
351
352 // Collapse
353 mBubbleController.collapseStack();
354 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
355 assertFalse(mBubbleController.isStackExpanded());
356 assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
357 }
358
359 @Test
360 public void testCollapseAfterChangingExpandedBubble() {
361 // Mark it as a bubble and add it explicitly
362 mEntryListener.onEntryAdded(mRow.getEntry());
363 mEntryListener.onEntryAdded(mRow2.getEntry());
364 mBubbleController.updateBubble(mRow.getEntry());
365 mBubbleController.updateBubble(mRow2.getEntry());
366
367 // We should have bubbles & their notifs should not be suppressed
368 assertTrue(mBubbleController.hasBubbles());
369 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
370 mRow.getEntry()));
371 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
372 mRow2.getEntry()));
373
374 // Expand
375 BubbleStackView stackView = mBubbleController.getStackView();
376 mBubbleController.expandStack();
377 assertTrue(mBubbleController.isStackExpanded());
378 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
379
380 // Last added is the one that is expanded
381 assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
382 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
383
384 // Switch which bubble is expanded
385 mBubbleController.selectBubble(mRow.getEntry().getKey());
386 mBubbleData.setExpanded(true);
Lyn Han9f66c3b2020-03-05 23:59:29 -0800387 assertEquals(mRow.getEntry(),
388 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500389 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
390 mRow.getEntry()));
391
392 // collapse for previous bubble
393 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
394 // expand for selected bubble
395 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
396
397 // Collapse
398 mBubbleController.collapseStack();
399 assertFalse(mBubbleController.isStackExpanded());
400 }
401
402 @Test
403 public void testExpansionRemovesShowInShadeAndDot() {
404 // Mark it as a bubble and add it explicitly
405 mEntryListener.onEntryAdded(mRow.getEntry());
406 mBubbleController.updateBubble(mRow.getEntry());
407
408 // We should have bubbles & their notifs should not be suppressed
409 assertTrue(mBubbleController.hasBubbles());
410 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
411
412 mTestableLooper.processAllMessages();
413 assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
414
415 // Expand
416 mBubbleController.expandStack();
417 assertTrue(mBubbleController.isStackExpanded());
418 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
419
420 // Notif is suppressed after expansion
421 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
422 mRow.getEntry()));
423 // Notif shouldn't show dot after expansion
424 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
425 }
426
427 @Test
428 public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
429 // Mark it as a bubble and add it explicitly
430 mEntryListener.onEntryAdded(mRow.getEntry());
431 mBubbleController.updateBubble(mRow.getEntry());
432
433 // We should have bubbles & their notifs should not be suppressed
434 assertTrue(mBubbleController.hasBubbles());
435 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
436 mRow.getEntry()));
437
438 mTestableLooper.processAllMessages();
439 assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
440
441 // Expand
442 mBubbleController.expandStack();
443 assertTrue(mBubbleController.isStackExpanded());
444 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
445
446 // Notif is suppressed after expansion
447 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
448 mRow.getEntry()));
449 // Notif shouldn't show dot after expansion
450 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
451
452 // Send update
453 mEntryListener.onEntryUpdated(mRow.getEntry());
454
455 // Nothing should have changed
456 // Notif is suppressed after expansion
457 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
458 mRow.getEntry()));
459 // Notif shouldn't show dot after expansion
460 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
461 }
462
463 @Test
464 public void testRemoveLastExpandedCollapses() {
465 // Mark it as a bubble and add it explicitly
466 mEntryListener.onEntryAdded(mRow.getEntry());
467 mEntryListener.onEntryAdded(mRow2.getEntry());
468 mBubbleController.updateBubble(mRow.getEntry());
469 mBubbleController.updateBubble(mRow2.getEntry());
470 verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
471
472 // Expand
473 BubbleStackView stackView = mBubbleController.getStackView();
474 mBubbleController.expandStack();
475
476 assertTrue(mBubbleController.isStackExpanded());
477 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
478
479 // Last added is the one that is expanded
Lyn Han9f66c3b2020-03-05 23:59:29 -0800480 assertEquals(mRow2.getEntry(),
481 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500482 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
483 mRow2.getEntry()));
484
485 // Dismiss currently expanded
Lyn Han9f66c3b2020-03-05 23:59:29 -0800486 mBubbleController.removeBubble(
487 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
Beverlya53fb0d2020-01-29 15:26:13 -0500488 BubbleController.DISMISS_USER_GESTURE);
489 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
490
491 // Make sure first bubble is selected
Lyn Han9f66c3b2020-03-05 23:59:29 -0800492 assertEquals(mRow.getEntry(),
493 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500494 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
495
496 // Dismiss that one
Lyn Han9f66c3b2020-03-05 23:59:29 -0800497 mBubbleController.removeBubble(
498 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
Beverlya53fb0d2020-01-29 15:26:13 -0500499 BubbleController.DISMISS_USER_GESTURE);
500
501 // Make sure state changes and collapse happens
502 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
503 verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
504 assertFalse(mBubbleController.hasBubbles());
505 }
506
507 @Test
508 public void testAutoExpand_fails_noFlag() {
509 assertFalse(mBubbleController.isStackExpanded());
510 setMetadataFlags(mRow.getEntry(),
511 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
512
513 // Add the auto expand bubble
514 mEntryListener.onEntryAdded(mRow.getEntry());
515 mBubbleController.updateBubble(mRow.getEntry());
516
517 // Expansion shouldn't change
518 verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
519 mRow.getEntry().getKey());
520 assertFalse(mBubbleController.isStackExpanded());
521
522 // # of bubbles should change
523 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
524 }
525
526 @Test
527 public void testAutoExpand_succeeds_withFlag() {
528 setMetadataFlags(mRow.getEntry(),
529 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
530
531 // Add the auto expand bubble
532 mEntryListener.onEntryAdded(mRow.getEntry());
533 mBubbleController.updateBubble(mRow.getEntry());
534
535 // Expansion should change
536 verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
537 mRow.getEntry().getKey());
538 assertTrue(mBubbleController.isStackExpanded());
539
540 // # of bubbles should change
541 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
542 }
543
544 @Test
545 public void testSuppressNotif_onInitialNotif() {
546 setMetadataFlags(mRow.getEntry(),
547 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
548
549 // Add the suppress notif bubble
550 mEntryListener.onEntryAdded(mRow.getEntry());
551 mBubbleController.updateBubble(mRow.getEntry());
552
553 // Notif should be suppressed because we were foreground
554 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
555 mRow.getEntry()));
556 // Dot + flyout is hidden because notif is suppressed
557 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
558 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
559
560 // # of bubbles should change
561 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
562 }
563
564 @Test
565 public void testSuppressNotif_onUpdateNotif() {
566 mBubbleController.updateBubble(mRow.getEntry());
567
568 // Should not be suppressed
569 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
570 mRow.getEntry()));
571 // Should show dot
572 assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
573
574 // Update to suppress notif
575 setMetadataFlags(mRow.getEntry(),
576 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
577 mBubbleController.updateBubble(mRow.getEntry());
578
579 // Notif should be suppressed
580 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
581 mRow.getEntry()));
582 // Dot + flyout is hidden because notif is suppressed
583 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
584 assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
585
586 // # of bubbles should change
587 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
588 }
589
590 @Test
591 public void testMarkNewNotificationAsShowInShade() {
592 mEntryListener.onEntryAdded(mRow.getEntry());
593 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
594 mRow.getEntry()));
595
596 mTestableLooper.processAllMessages();
597 assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
598 }
599
600 @Test
601 public void testAddNotif_notBubble() {
602 mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
603 mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
604
605 verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
606 assertThat(mBubbleController.hasBubbles()).isFalse();
607 }
608
609 @Test
610 public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
611 mBubbleController.updateBubble(mRow.getEntry());
612 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
613 verify(mDeleteIntent, never()).send();
614 }
615
616 @Test
617 public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
618 mBubbleController.updateBubble(mRow.getEntry());
619 mBubbleController.removeBubble(
620 mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
621 verify(mDeleteIntent, times(1)).send();
622 }
623
624 @Test
625 public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
626 mBubbleController.updateBubble(mRow.getEntry());
627 mBubbleController.updateBubble(mRow2.getEntry());
628 mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
629 verify(mDeleteIntent, times(2)).send();
630 }
631
632 @Test
633 public void testRemoveBubble_noLongerBubbleAfterUpdate()
634 throws PendingIntent.CanceledException {
635 mBubbleController.updateBubble(mRow.getEntry());
636 assertTrue(mBubbleController.hasBubbles());
637
638 mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
639 mEntryListener.onEntryUpdated(mRow.getEntry());
640
641 assertFalse(mBubbleController.hasBubbles());
642 verify(mDeleteIntent, never()).send();
643 }
644
645 @Test
646 public void testRemoveBubble_entryListenerRemove() {
647 mEntryListener.onEntryAdded(mRow.getEntry());
648 mBubbleController.updateBubble(mRow.getEntry());
649
650 assertTrue(mBubbleController.hasBubbles());
651
652 // Removes the notification
653 mEntryListener.onEntryRemoved(mRow.getEntry(), 0);
654 assertFalse(mBubbleController.hasBubbles());
655 }
656
657 @Test
Lyn Han9f66c3b2020-03-05 23:59:29 -0800658 public void removeBubble_intercepted() {
Beverlya53fb0d2020-01-29 15:26:13 -0500659 mEntryListener.onEntryAdded(mRow.getEntry());
660 mBubbleController.updateBubble(mRow.getEntry());
661
662 assertTrue(mBubbleController.hasBubbles());
663 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
664 mRow.getEntry()));
665
666 boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
667
668 // Intercept!
669 assertTrue(intercepted);
670 // Should update show in shade state
671 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
672 }
673
674 @Test
675 public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
676 mEntryListener.onEntryAdded(mRow.getEntry());
677 mBubbleController.updateBubble(mRow.getEntry());
678
679 assertTrue(mBubbleController.hasBubbles());
680 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
681 mRow.getEntry()));
682
683 // Dismiss the bubble
684 mBubbleController.removeBubble(
685 mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
686 assertFalse(mBubbleController.hasBubbles());
687
688 // Dismiss the notification
689 boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
690
691 // It's no longer a bubble so we shouldn't intercept
692 assertFalse(intercepted);
693 }
694
695 @Test
696 public void testNotifyShadeSuppressionChange_notificationDismiss() {
697 BubbleController.NotificationSuppressionChangedListener listener =
698 mock(BubbleController.NotificationSuppressionChangedListener.class);
699 mBubbleData.setSuppressionChangedListener(listener);
700
701 mEntryListener.onEntryAdded(mRow.getEntry());
702
703 assertTrue(mBubbleController.hasBubbles());
704 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
705 mRow.getEntry()));
706
707 mBubbleController.handleDismissalInterception(mRow.getEntry());
708
709 // Should update show in shade state
710 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
711 mRow.getEntry()));
712
713 // Should notify delegate that shade state changed
714 verify(listener).onBubbleNotificationSuppressionChange(
715 mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
716 }
717
718 @Test
719 public void testNotifyShadeSuppressionChange_bubbleExpanded() {
720 BubbleController.NotificationSuppressionChangedListener listener =
721 mock(BubbleController.NotificationSuppressionChangedListener.class);
722 mBubbleData.setSuppressionChangedListener(listener);
723
724 mEntryListener.onEntryAdded(mRow.getEntry());
725
726 assertTrue(mBubbleController.hasBubbles());
727 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
728 mRow.getEntry()));
729
730 mBubbleData.setExpanded(true);
731
732 // Once a bubble is expanded the notif is suppressed
733 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
734 mRow.getEntry()));
735
736 // Should notify delegate that shade state changed
737 verify(listener).onBubbleNotificationSuppressionChange(
738 mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
739 }
740
741 @Test
742 public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
743 // GIVEN a group summary with a bubble child
744 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
745 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
746 mEntryListener.onEntryAdded(groupedBubble.getEntry());
747 groupSummary.addChildNotification(groupedBubble);
748 assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
749
750 // WHEN the summary is dismissed
751 mBubbleController.handleDismissalInterception(groupSummary.getEntry());
752
753 // THEN the summary and bubbled child are suppressed from the shade
754 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
755 groupedBubble.getEntry()));
756 assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
757 }
758
759 @Test
760 public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
761 // GIVEN a group summary with a bubble child
762 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
763 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
764 mEntryListener.onEntryAdded(groupedBubble.getEntry());
765 groupSummary.addChildNotification(groupedBubble);
766 assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
767
768 // GIVEN the summary is dismissed
769 mBubbleController.handleDismissalInterception(groupSummary.getEntry());
770
771 // WHEN the summary is cancelled by the app
772 mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0);
773
774 // THEN the summary and its children are removed from bubble data
775 assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
776 assertFalse(mBubbleData.isSummarySuppressed(
777 groupSummary.getEntry().getSbn().getGroupKey()));
778 }
779
780 @Test
781 public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
782 throws Exception {
783 // GIVEN a group summary with two (non-bubble) children and one bubble child
784 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
785 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
786 mEntryListener.onEntryAdded(groupedBubble.getEntry());
787 groupSummary.addChildNotification(groupedBubble);
788
789 // WHEN the summary is dismissed
790 mBubbleController.handleDismissalInterception(groupSummary.getEntry());
791
792 // THEN only the NON-bubble children are dismissed
793 List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren();
794 verify(mNotifCallback, times(1)).removeNotification(
795 childrenRows.get(0).getEntry(), REASON_GROUP_SUMMARY_CANCELED);
796 verify(mNotifCallback, times(1)).removeNotification(
797 childrenRows.get(1).getEntry(), REASON_GROUP_SUMMARY_CANCELED);
798 verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()), anyInt());
799
800 // THEN the bubble child still exists as a bubble and is suppressed from the shade
801 assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey()));
802 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
803 groupedBubble.getEntry()));
804
805 // THEN the summary is also suppressed from the shade
806 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
807 groupSummary.getEntry()));
808 }
809
Beverlya53fb0d2020-01-29 15:26:13 -0500810 /**
811 * Sets the bubble metadata flags for this entry. These flags are normally set by
812 * NotificationManagerService when the notification is sent, however, these tests do not
813 * go through that path so we set them explicitly when testing.
814 */
815 private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
816 Notification.BubbleMetadata bubbleMetadata =
817 entry.getSbn().getNotification().getBubbleMetadata();
818 int flags = bubbleMetadata.getFlags();
819 if (enableFlag) {
820 flags |= flag;
821 } else {
822 flags &= ~flag;
823 }
824 bubbleMetadata.setFlags(flags);
825 }
826}