blob: 73b8760198630a62cddc09562561b3001d23eb4d [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;
Beverlya53fb0d2020-01-29 15:26:13 -050029import static org.mockito.ArgumentMatchers.anyInt;
30import static org.mockito.ArgumentMatchers.anyString;
31import static org.mockito.ArgumentMatchers.eq;
32import static org.mockito.Mockito.atLeastOnce;
33import static org.mockito.Mockito.mock;
34import static org.mockito.Mockito.never;
35import static org.mockito.Mockito.times;
36import static org.mockito.Mockito.verify;
37import static org.mockito.Mockito.when;
38
39import android.app.IActivityManager;
Mady Mellor9adfe6a2020-03-30 17:23:26 -070040import android.app.INotificationManager;
Beverlya53fb0d2020-01-29 15:26:13 -050041import 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;
Fabian Kozynskib00c70b2020-04-03 12:41:31 -040081import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
Beverlya53fb0d2020-01-29 15:26:13 -050082import com.android.systemui.statusbar.phone.ShadeController;
83import com.android.systemui.statusbar.policy.BatteryController;
84import com.android.systemui.statusbar.policy.ConfigurationController;
85import com.android.systemui.statusbar.policy.HeadsUpManager;
86import com.android.systemui.statusbar.policy.ZenModeController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -050087import com.android.systemui.util.FloatingContentCoordinator;
Beverlya53fb0d2020-01-29 15:26:13 -050088import com.android.systemui.util.InjectionInflationController;
89
90import org.junit.Before;
91import org.junit.Test;
92import org.junit.runner.RunWith;
93import org.mockito.ArgumentCaptor;
94import org.mockito.Captor;
95import org.mockito.Mock;
96import org.mockito.MockitoAnnotations;
97
98import java.util.List;
99
100/**
101 * Tests the NotifPipeline setup with BubbleController.
102 * The NotificationEntryManager setup with BubbleController is tested in
103 * {@link BubbleControllerTest}.
104 */
105@SmallTest
106@RunWith(AndroidTestingRunner.class)
107@TestableLooper.RunWithLooper(setAsMainLooper = true)
108public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
109 @Mock
110 private NotificationEntryManager mNotificationEntryManager;
111 @Mock
112 private NotificationGroupManager mNotificationGroupManager;
113 @Mock
114 private BubbleController.NotifCallback mNotifCallback;
115 @Mock
116 private WindowManager mWindowManager;
117 @Mock
118 private IActivityManager mActivityManager;
119 @Mock
120 private DozeParameters mDozeParameters;
121 @Mock
122 private ConfigurationController mConfigurationController;
123 @Mock
124 private ZenModeController mZenModeController;
125 @Mock
126 private ZenModeConfig mZenModeConfig;
127 @Mock
128 private FaceManager mFaceManager;
129 @Mock
130 private NotificationLockscreenUserManager mLockscreenUserManager;
131 @Mock
132 private SysuiStatusBarStateController mStatusBarStateController;
133 @Mock
134 private KeyguardBypassController mKeyguardBypassController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500135 @Mock
136 private FloatingContentCoordinator mFloatingContentCoordinator;
Fabian Kozynskib00c70b2020-04-03 12:41:31 -0400137 @Mock
Pinyao Tingee191b12020-04-29 18:35:39 -0700138 private BubbleDataRepository mDataRepository;
139 @Mock
Fabian Kozynskib00c70b2020-04-03 12:41:31 -0400140 private NotificationShadeWindowView mNotificationShadeWindowView;
Joshua Tsujibe60a582020-03-23 17:17:26 -0400141
142 private SysUiState mSysUiState = new SysUiState();
143
Beverlya53fb0d2020-01-29 15:26:13 -0500144 @Captor
145 private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
Beverlya53fb0d2020-01-29 15:26:13 -0500146 private TestableBubbleController mBubbleController;
147 private NotificationShadeWindowController mNotificationShadeWindowController;
148 private NotifCollectionListener mEntryListener;
Beverlya53fb0d2020-01-29 15:26:13 -0500149 private NotificationTestHelper mNotificationTestHelper;
150 private ExpandableNotificationRow mRow;
151 private ExpandableNotificationRow mRow2;
152 private ExpandableNotificationRow mNonBubbleNotifRow;
Beverlya53fb0d2020-01-29 15:26:13 -0500153 @Mock
Beverlya53fb0d2020-01-29 15:26:13 -0500154 private BubbleController.BubbleExpandListener mBubbleExpandListener;
155 @Mock
156 private PendingIntent mDeleteIntent;
157 @Mock
158 private SysuiColorExtractor mColorExtractor;
159 @Mock
160 ColorExtractor.GradientColors mGradientColors;
161 @Mock
162 private Resources mResources;
163 @Mock
164 private ShadeController mShadeController;
165 @Mock
166 private NotificationRowComponent mNotificationRowComponent;
167 @Mock
168 private NotifPipeline mNotifPipeline;
169 @Mock
170 private FeatureFlags mFeatureFlagsNewPipeline;
171 @Mock
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500172 private DumpManager mDumpManager;
Dave Mankoffc0d9a7d2020-02-27 17:15:52 -0500173 @Mock
174 private LockscreenLockIconController mLockIconController;
Beverlya53fb0d2020-01-29 15:26:13 -0500175
176 private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
177 private BubbleData mBubbleData;
178
179 private TestableLooper mTestableLooper;
180
181 @Before
182 public void setUp() throws Exception {
183 MockitoAnnotations.initMocks(this);
184
185 mTestableLooper = TestableLooper.get(this);
186
187 mContext.addMockSystemService(FaceManager.class, mFaceManager);
188 when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
189
190 mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
191 new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
192 new NotificationRowComponent.Builder() {
193 @Override
194 public NotificationRowComponent.Builder activatableNotificationView(
195 ActivatableNotificationView view) {
196 return this;
197 }
198
199 @Override
200 public NotificationRowComponent build() {
201 return mNotificationRowComponent;
202 }
Dave Mankoffc0d9a7d2020-02-27 17:15:52 -0500203 },
204 mLockIconController);
Beverlya53fb0d2020-01-29 15:26:13 -0500205
206 // Bubbles get added to status bar window view
207 mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
208 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
Lucas Dupind73410a2020-02-18 12:54:41 -0800209 mConfigurationController, mKeyguardBypassController, mColorExtractor,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500210 mDumpManager);
Fabian Kozynskib00c70b2020-04-03 12:41:31 -0400211 mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
Beverlya53fb0d2020-01-29 15:26:13 -0500212 mNotificationShadeWindowController.attach();
213
214 // Need notifications for bubbles
Kevin Hana7c21be2020-04-01 17:58:35 -0700215 mNotificationTestHelper = new NotificationTestHelper(
216 mContext,
217 mDependency,
218 TestableLooper.get(this));
Beverlya53fb0d2020-01-29 15:26:13 -0500219 mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
220 mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
221 mNonBubbleNotifRow = mNotificationTestHelper.createRow();
222
223 mZenModeConfig.suppressedVisualEffects = 0;
224 when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
225
Beverly Taid1e175c2020-03-10 16:37:04 +0000226 TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
227 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
228 mock(PowerManager.class),
229 mock(IDreamManager.class),
230 mock(AmbientDisplayConfiguration.class),
Beverlya53fb0d2020-01-29 15:26:13 -0500231 mock(NotificationFilter.class),
232 mock(StatusBarStateController.class),
Beverly Taid1e175c2020-03-10 16:37:04 +0000233 mock(BatteryController.class),
234 mock(HeadsUpManager.class),
235 mock(Handler.class)
236 );
Beverlya53fb0d2020-01-29 15:26:13 -0500237 mBubbleData = new BubbleData(mContext);
238 when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500239 mBubbleController = new TestableBubbleController(
240 mContext,
Beverlya53fb0d2020-01-29 15:26:13 -0500241 mNotificationShadeWindowController,
242 mStatusBarStateController,
243 mShadeController,
244 mBubbleData,
245 mConfigurationController,
246 interruptionStateProvider,
247 mZenModeController,
248 mLockscreenUserManager,
249 mNotificationGroupManager,
250 mNotificationEntryManager,
251 mNotifPipeline,
252 mFeatureFlagsNewPipeline,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500253 mDumpManager,
Joshua Tsujibe60a582020-03-23 17:17:26 -0400254 mFloatingContentCoordinator,
Pinyao Tingee191b12020-04-29 18:35:39 -0700255 mDataRepository,
Mady Mellor9adfe6a2020-03-30 17:23:26 -0700256 mSysUiState,
Mady Mellor59a7b982020-05-11 15:19:59 -0700257 mock(INotificationManager.class),
258 mWindowManager);
Beverlya53fb0d2020-01-29 15:26:13 -0500259 mBubbleController.addNotifCallback(mNotifCallback);
Beverlya53fb0d2020-01-29 15:26:13 -0500260 mBubbleController.setExpandListener(mBubbleExpandListener);
261
262 // Get a reference to the BubbleController's entry listener
263 verify(mNotifPipeline, atLeastOnce())
264 .addCollectionListener(mNotifListenerCaptor.capture());
265 mEntryListener = mNotifListenerCaptor.getValue();
266 }
267
268 @Test
269 public void testAddBubble() {
270 mBubbleController.updateBubble(mRow.getEntry());
271 assertTrue(mBubbleController.hasBubbles());
Beverlya53fb0d2020-01-29 15:26:13 -0500272 }
273
274 @Test
275 public void testHasBubbles() {
276 assertFalse(mBubbleController.hasBubbles());
277 mBubbleController.updateBubble(mRow.getEntry());
278 assertTrue(mBubbleController.hasBubbles());
279 }
280
281 @Test
282 public void testRemoveBubble() {
283 mBubbleController.updateBubble(mRow.getEntry());
Lyn Han2f6e89d2020-04-15 10:01:01 -0700284 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500285 assertTrue(mBubbleController.hasBubbles());
286 verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
Beverlya53fb0d2020-01-29 15:26:13 -0500287
288 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700289 assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500290 verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
Beverlya53fb0d2020-01-29 15:26:13 -0500291 }
292
293 @Test
Lyn Han2f6e89d2020-04-15 10:01:01 -0700294 public void testRemoveBubble_withDismissedNotif_inOverflow() {
Beverlya53fb0d2020-01-29 15:26:13 -0500295 mEntryListener.onEntryAdded(mRow.getEntry());
296 mBubbleController.updateBubble(mRow.getEntry());
297
298 assertTrue(mBubbleController.hasBubbles());
299 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
300
301 // Make it look like dismissed notif
Lyn Han2f6e89d2020-04-15 10:01:01 -0700302 mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
Beverlya53fb0d2020-01-29 15:26:13 -0500303
304 // Now remove the bubble
305 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700306 assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500307
Lyn Han2f6e89d2020-04-15 10:01:01 -0700308 // We don't remove the notification since the bubble is still in overflow.
309 verify(mNotifCallback, never()).removeNotification(eq(mRow.getEntry()), anyInt());
310 assertFalse(mBubbleController.hasBubbles());
311 }
312
313 @Test
314 public void testRemoveBubble_withDismissedNotif_notInOverflow() {
315 mEntryListener.onEntryAdded(mRow.getEntry());
316 mBubbleController.updateBubble(mRow.getEntry());
317
318 assertTrue(mBubbleController.hasBubbles());
319 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
320
321 // Make it look like dismissed notif
322 mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
323
324 // Now remove the bubble
325 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
326 assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
327
328 // Since the notif is dismissed and not in overflow, once the bubble is removed,
329 // removeNotification gets called to really remove the notif
Beverlya53fb0d2020-01-29 15:26:13 -0500330 verify(mNotifCallback, times(1)).removeNotification(eq(mRow.getEntry()), anyInt());
331 assertFalse(mBubbleController.hasBubbles());
332 }
333
334 @Test
335 public void testDismissStack() {
336 mBubbleController.updateBubble(mRow.getEntry());
337 verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
Lyn Han2f6e89d2020-04-15 10:01:01 -0700338 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500339 mBubbleController.updateBubble(mRow2.getEntry());
Lyn Han9f66c3b2020-03-05 23:59:29 -0800340 verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
Lyn Han2f6e89d2020-04-15 10:01:01 -0700341 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500342 assertTrue(mBubbleController.hasBubbles());
343
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400344 mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
Beverlya53fb0d2020-01-29 15:26:13 -0500345 verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
Lyn Han2f6e89d2020-04-15 10:01:01 -0700346 assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
347 assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500348 }
349
350 @Test
351 public void testExpandCollapseStack() {
352 assertFalse(mBubbleController.isStackExpanded());
353
354 // Mark it as a bubble and add it explicitly
355 mEntryListener.onEntryAdded(mRow.getEntry());
356 mBubbleController.updateBubble(mRow.getEntry());
357
358 // We should have bubbles & their notifs should not be suppressed
359 assertTrue(mBubbleController.hasBubbles());
360 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
361 mRow.getEntry()));
Beverlya53fb0d2020-01-29 15:26:13 -0500362
363 // Expand the stack
364 BubbleStackView stackView = mBubbleController.getStackView();
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400365 mBubbleData.setExpanded(true);
Beverlya53fb0d2020-01-29 15:26:13 -0500366 assertTrue(mBubbleController.isStackExpanded());
367 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
Beverlya53fb0d2020-01-29 15:26:13 -0500368
369 // Make sure the notif is suppressed
370 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
371
372 // Collapse
373 mBubbleController.collapseStack();
374 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
375 assertFalse(mBubbleController.isStackExpanded());
Beverlya53fb0d2020-01-29 15:26:13 -0500376 }
377
378 @Test
379 public void testCollapseAfterChangingExpandedBubble() {
380 // Mark it as a bubble and add it explicitly
381 mEntryListener.onEntryAdded(mRow.getEntry());
382 mEntryListener.onEntryAdded(mRow2.getEntry());
383 mBubbleController.updateBubble(mRow.getEntry());
384 mBubbleController.updateBubble(mRow2.getEntry());
385
386 // We should have bubbles & their notifs should not be suppressed
387 assertTrue(mBubbleController.hasBubbles());
388 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
389 mRow.getEntry()));
390 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
391 mRow2.getEntry()));
392
393 // Expand
394 BubbleStackView stackView = mBubbleController.getStackView();
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400395 mBubbleData.setExpanded(true);
Beverlya53fb0d2020-01-29 15:26:13 -0500396 assertTrue(mBubbleController.isStackExpanded());
397 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
398
399 // Last added is the one that is expanded
400 assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
401 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
402
403 // Switch which bubble is expanded
Lyn Han2f6e89d2020-04-15 10:01:01 -0700404 mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500405 mBubbleData.setExpanded(true);
Lyn Han9f66c3b2020-03-05 23:59:29 -0800406 assertEquals(mRow.getEntry(),
Lyn Han2f6e89d2020-04-15 10:01:01 -0700407 mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500408 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
409 mRow.getEntry()));
410
411 // collapse for previous bubble
412 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
413 // expand for selected bubble
414 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
415
416 // Collapse
417 mBubbleController.collapseStack();
418 assertFalse(mBubbleController.isStackExpanded());
419 }
420
421 @Test
422 public void testExpansionRemovesShowInShadeAndDot() {
423 // Mark it as a bubble and add it explicitly
424 mEntryListener.onEntryAdded(mRow.getEntry());
425 mBubbleController.updateBubble(mRow.getEntry());
426
427 // We should have bubbles & their notifs should not be suppressed
428 assertTrue(mBubbleController.hasBubbles());
429 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
430
431 mTestableLooper.processAllMessages();
Lyn Han2f6e89d2020-04-15 10:01:01 -0700432 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500433
434 // Expand
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400435 mBubbleData.setExpanded(true);
Beverlya53fb0d2020-01-29 15:26:13 -0500436 assertTrue(mBubbleController.isStackExpanded());
437 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
438
439 // Notif is suppressed after expansion
440 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
441 mRow.getEntry()));
442 // Notif shouldn't show dot after expansion
Lyn Han2f6e89d2020-04-15 10:01:01 -0700443 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500444 }
445
446 @Test
447 public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
448 // Mark it as a bubble and add it explicitly
449 mEntryListener.onEntryAdded(mRow.getEntry());
450 mBubbleController.updateBubble(mRow.getEntry());
451
452 // We should have bubbles & their notifs should not be suppressed
453 assertTrue(mBubbleController.hasBubbles());
454 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
455 mRow.getEntry()));
456
457 mTestableLooper.processAllMessages();
Lyn Han2f6e89d2020-04-15 10:01:01 -0700458 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500459
460 // Expand
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400461 mBubbleData.setExpanded(true);
Beverlya53fb0d2020-01-29 15:26:13 -0500462 assertTrue(mBubbleController.isStackExpanded());
463 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
464
465 // Notif is suppressed after expansion
466 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
467 mRow.getEntry()));
468 // Notif shouldn't show dot after expansion
Lyn Han2f6e89d2020-04-15 10:01:01 -0700469 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500470
471 // Send update
472 mEntryListener.onEntryUpdated(mRow.getEntry());
473
474 // Nothing should have changed
475 // Notif is suppressed after expansion
476 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
477 mRow.getEntry()));
478 // Notif shouldn't show dot after expansion
Lyn Han2f6e89d2020-04-15 10:01:01 -0700479 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500480 }
481
482 @Test
483 public void testRemoveLastExpandedCollapses() {
484 // Mark it as a bubble and add it explicitly
485 mEntryListener.onEntryAdded(mRow.getEntry());
486 mEntryListener.onEntryAdded(mRow2.getEntry());
487 mBubbleController.updateBubble(mRow.getEntry());
488 mBubbleController.updateBubble(mRow2.getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500489
490 // Expand
491 BubbleStackView stackView = mBubbleController.getStackView();
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400492 mBubbleData.setExpanded(true);
Beverlya53fb0d2020-01-29 15:26:13 -0500493
494 assertTrue(mBubbleController.isStackExpanded());
495 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
496
497 // Last added is the one that is expanded
Lyn Han9f66c3b2020-03-05 23:59:29 -0800498 assertEquals(mRow2.getEntry(),
Lyn Han2f6e89d2020-04-15 10:01:01 -0700499 mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500500 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
501 mRow2.getEntry()));
502
503 // Dismiss currently expanded
Lyn Han9f66c3b2020-03-05 23:59:29 -0800504 mBubbleController.removeBubble(
Lyn Han2f6e89d2020-04-15 10:01:01 -0700505 mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
Beverlya53fb0d2020-01-29 15:26:13 -0500506 BubbleController.DISMISS_USER_GESTURE);
507 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
508
509 // Make sure first bubble is selected
Lyn Han9f66c3b2020-03-05 23:59:29 -0800510 assertEquals(mRow.getEntry(),
Lyn Han2f6e89d2020-04-15 10:01:01 -0700511 mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry());
Beverlya53fb0d2020-01-29 15:26:13 -0500512 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
513
514 // Dismiss that one
Lyn Han9f66c3b2020-03-05 23:59:29 -0800515 mBubbleController.removeBubble(
Lyn Han2f6e89d2020-04-15 10:01:01 -0700516 mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
Beverlya53fb0d2020-01-29 15:26:13 -0500517 BubbleController.DISMISS_USER_GESTURE);
518
519 // Make sure state changes and collapse happens
520 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
Beverlya53fb0d2020-01-29 15:26:13 -0500521 assertFalse(mBubbleController.hasBubbles());
522 }
523
524 @Test
525 public void testAutoExpand_fails_noFlag() {
526 assertFalse(mBubbleController.isStackExpanded());
527 setMetadataFlags(mRow.getEntry(),
528 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
529
530 // Add the auto expand bubble
531 mEntryListener.onEntryAdded(mRow.getEntry());
532 mBubbleController.updateBubble(mRow.getEntry());
533
534 // Expansion shouldn't change
535 verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
536 mRow.getEntry().getKey());
537 assertFalse(mBubbleController.isStackExpanded());
Beverlya53fb0d2020-01-29 15:26:13 -0500538 }
539
540 @Test
541 public void testAutoExpand_succeeds_withFlag() {
542 setMetadataFlags(mRow.getEntry(),
543 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
544
545 // Add the auto expand bubble
546 mEntryListener.onEntryAdded(mRow.getEntry());
547 mBubbleController.updateBubble(mRow.getEntry());
548
549 // Expansion should change
550 verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
551 mRow.getEntry().getKey());
552 assertTrue(mBubbleController.isStackExpanded());
Beverlya53fb0d2020-01-29 15:26:13 -0500553 }
554
555 @Test
556 public void testSuppressNotif_onInitialNotif() {
557 setMetadataFlags(mRow.getEntry(),
558 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
559
560 // Add the suppress notif bubble
561 mEntryListener.onEntryAdded(mRow.getEntry());
562 mBubbleController.updateBubble(mRow.getEntry());
563
564 // Notif should be suppressed because we were foreground
565 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
566 mRow.getEntry()));
567 // Dot + flyout is hidden because notif is suppressed
Lyn Han2f6e89d2020-04-15 10:01:01 -0700568 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
569 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showFlyout());
Beverlya53fb0d2020-01-29 15:26:13 -0500570 }
571
572 @Test
573 public void testSuppressNotif_onUpdateNotif() {
574 mBubbleController.updateBubble(mRow.getEntry());
575
576 // Should not be suppressed
577 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
578 mRow.getEntry()));
579 // Should show dot
Lyn Han2f6e89d2020-04-15 10:01:01 -0700580 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500581
582 // Update to suppress notif
583 setMetadataFlags(mRow.getEntry(),
584 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
585 mBubbleController.updateBubble(mRow.getEntry());
586
587 // Notif should be suppressed
588 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
589 mRow.getEntry()));
590 // Dot + flyout is hidden because notif is suppressed
Lyn Han2f6e89d2020-04-15 10:01:01 -0700591 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
592 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showFlyout());
Beverlya53fb0d2020-01-29 15:26:13 -0500593 }
594
595 @Test
596 public void testMarkNewNotificationAsShowInShade() {
597 mEntryListener.onEntryAdded(mRow.getEntry());
598 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
599 mRow.getEntry()));
600
601 mTestableLooper.processAllMessages();
Lyn Han2f6e89d2020-04-15 10:01:01 -0700602 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).showDot());
Beverlya53fb0d2020-01-29 15:26:13 -0500603 }
604
605 @Test
606 public void testAddNotif_notBubble() {
607 mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
608 mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
609
Beverlya53fb0d2020-01-29 15:26:13 -0500610 assertThat(mBubbleController.hasBubbles()).isFalse();
611 }
612
613 @Test
614 public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
615 mBubbleController.updateBubble(mRow.getEntry());
616 mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
617 verify(mDeleteIntent, never()).send();
618 }
619
620 @Test
621 public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
622 mBubbleController.updateBubble(mRow.getEntry());
623 mBubbleController.removeBubble(
624 mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
625 verify(mDeleteIntent, times(1)).send();
626 }
627
628 @Test
629 public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
630 mBubbleController.updateBubble(mRow.getEntry());
631 mBubbleController.updateBubble(mRow2.getEntry());
Joshua Tsuji7dd88b02020-03-27 17:43:09 -0400632 mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
Beverlya53fb0d2020-01-29 15:26:13 -0500633 verify(mDeleteIntent, times(2)).send();
634 }
635
636 @Test
637 public void testRemoveBubble_noLongerBubbleAfterUpdate()
638 throws PendingIntent.CanceledException {
639 mBubbleController.updateBubble(mRow.getEntry());
640 assertTrue(mBubbleController.hasBubbles());
641
642 mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
643 mEntryListener.onEntryUpdated(mRow.getEntry());
644
645 assertFalse(mBubbleController.hasBubbles());
646 verify(mDeleteIntent, never()).send();
647 }
648
649 @Test
650 public void testRemoveBubble_entryListenerRemove() {
651 mEntryListener.onEntryAdded(mRow.getEntry());
652 mBubbleController.updateBubble(mRow.getEntry());
653
654 assertTrue(mBubbleController.hasBubbles());
655
656 // Removes the notification
657 mEntryListener.onEntryRemoved(mRow.getEntry(), 0);
658 assertFalse(mBubbleController.hasBubbles());
659 }
660
661 @Test
Lyn Han9f66c3b2020-03-05 23:59:29 -0800662 public void removeBubble_intercepted() {
Beverlya53fb0d2020-01-29 15:26:13 -0500663 mEntryListener.onEntryAdded(mRow.getEntry());
664 mBubbleController.updateBubble(mRow.getEntry());
665
666 assertTrue(mBubbleController.hasBubbles());
667 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
668 mRow.getEntry()));
669
670 boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
671
672 // Intercept!
673 assertTrue(intercepted);
674 // Should update show in shade state
675 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry()));
676 }
677
678 @Test
Lyn Han2f6e89d2020-04-15 10:01:01 -0700679 public void removeBubble_dismissIntoOverflow_intercepted() {
Beverlya53fb0d2020-01-29 15:26:13 -0500680 mEntryListener.onEntryAdded(mRow.getEntry());
681 mBubbleController.updateBubble(mRow.getEntry());
682
683 assertTrue(mBubbleController.hasBubbles());
684 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
685 mRow.getEntry()));
686
687 // Dismiss the bubble
688 mBubbleController.removeBubble(
689 mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
690 assertFalse(mBubbleController.hasBubbles());
691
692 // Dismiss the notification
693 boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
694
Lyn Han2f6e89d2020-04-15 10:01:01 -0700695 // Intercept dismissal since bubble is going into overflow
696 assertTrue(intercepted);
697 }
698
699 @Test
700 public void removeBubble_notIntercepted() {
701 mEntryListener.onEntryAdded(mRow.getEntry());
702 mBubbleController.updateBubble(mRow.getEntry());
703
704 assertTrue(mBubbleController.hasBubbles());
705 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
706 mRow.getEntry()));
707
708 // Dismiss the bubble
709 mBubbleController.removeBubble(
710 mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
711 assertFalse(mBubbleController.hasBubbles());
712
713 // Dismiss the notification
714 boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry());
715
716 // Not a bubble anymore so we don't intercept dismissal.
Beverlya53fb0d2020-01-29 15:26:13 -0500717 assertFalse(intercepted);
718 }
719
720 @Test
721 public void testNotifyShadeSuppressionChange_notificationDismiss() {
722 BubbleController.NotificationSuppressionChangedListener listener =
723 mock(BubbleController.NotificationSuppressionChangedListener.class);
724 mBubbleData.setSuppressionChangedListener(listener);
725
726 mEntryListener.onEntryAdded(mRow.getEntry());
727
728 assertTrue(mBubbleController.hasBubbles());
729 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
730 mRow.getEntry()));
731
732 mBubbleController.handleDismissalInterception(mRow.getEntry());
733
734 // Should update show in shade state
735 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
736 mRow.getEntry()));
737
738 // Should notify delegate that shade state changed
739 verify(listener).onBubbleNotificationSuppressionChange(
Lyn Han2f6e89d2020-04-15 10:01:01 -0700740 mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500741 }
742
743 @Test
744 public void testNotifyShadeSuppressionChange_bubbleExpanded() {
745 BubbleController.NotificationSuppressionChangedListener listener =
746 mock(BubbleController.NotificationSuppressionChangedListener.class);
747 mBubbleData.setSuppressionChangedListener(listener);
748
749 mEntryListener.onEntryAdded(mRow.getEntry());
750
751 assertTrue(mBubbleController.hasBubbles());
752 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
753 mRow.getEntry()));
754
755 mBubbleData.setExpanded(true);
756
757 // Once a bubble is expanded the notif is suppressed
758 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
759 mRow.getEntry()));
760
761 // Should notify delegate that shade state changed
762 verify(listener).onBubbleNotificationSuppressionChange(
Lyn Han2f6e89d2020-04-15 10:01:01 -0700763 mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500764 }
765
766 @Test
767 public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
768 // GIVEN a group summary with a bubble child
769 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
770 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
771 mEntryListener.onEntryAdded(groupedBubble.getEntry());
772 groupSummary.addChildNotification(groupedBubble);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700773 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500774
775 // WHEN the summary is dismissed
776 mBubbleController.handleDismissalInterception(groupSummary.getEntry());
777
778 // THEN the summary and bubbled child are suppressed from the shade
779 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
780 groupedBubble.getEntry()));
781 assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
782 }
783
784 @Test
785 public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
786 // GIVEN a group summary with a bubble child
787 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
788 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
789 mEntryListener.onEntryAdded(groupedBubble.getEntry());
790 groupSummary.addChildNotification(groupedBubble);
Lyn Han2f6e89d2020-04-15 10:01:01 -0700791 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500792
793 // GIVEN the summary is dismissed
794 mBubbleController.handleDismissalInterception(groupSummary.getEntry());
795
796 // WHEN the summary is cancelled by the app
797 mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0);
798
799 // THEN the summary and its children are removed from bubble data
Lyn Han2f6e89d2020-04-15 10:01:01 -0700800 assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500801 assertFalse(mBubbleData.isSummarySuppressed(
802 groupSummary.getEntry().getSbn().getGroupKey()));
803 }
804
805 @Test
806 public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
807 throws Exception {
808 // GIVEN a group summary with two (non-bubble) children and one bubble child
809 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
810 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
811 mEntryListener.onEntryAdded(groupedBubble.getEntry());
812 groupSummary.addChildNotification(groupedBubble);
813
814 // WHEN the summary is dismissed
815 mBubbleController.handleDismissalInterception(groupSummary.getEntry());
816
817 // THEN only the NON-bubble children are dismissed
Kevin Han43077f92020-02-28 12:51:53 -0800818 List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
Beverlya53fb0d2020-01-29 15:26:13 -0500819 verify(mNotifCallback, times(1)).removeNotification(
820 childrenRows.get(0).getEntry(), REASON_GROUP_SUMMARY_CANCELED);
821 verify(mNotifCallback, times(1)).removeNotification(
822 childrenRows.get(1).getEntry(), REASON_GROUP_SUMMARY_CANCELED);
823 verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()), anyInt());
824
825 // THEN the bubble child still exists as a bubble and is suppressed from the shade
Lyn Han2f6e89d2020-04-15 10:01:01 -0700826 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
Beverlya53fb0d2020-01-29 15:26:13 -0500827 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
828 groupedBubble.getEntry()));
829
830 // THEN the summary is also suppressed from the shade
831 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
832 groupSummary.getEntry()));
833 }
834
Beverlya53fb0d2020-01-29 15:26:13 -0500835 /**
836 * Sets the bubble metadata flags for this entry. These flags are normally set by
837 * NotificationManagerService when the notification is sent, however, these tests do not
838 * go through that path so we set them explicitly when testing.
839 */
840 private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
841 Notification.BubbleMetadata bubbleMetadata =
842 entry.getSbn().getNotification().getBubbleMetadata();
843 int flags = bubbleMetadata.getFlags();
844 if (enableFlag) {
845 flags |= flag;
846 } else {
847 flags &= ~flag;
848 }
849 bubbleMetadata.setFlags(flags);
850 }
851}