blob: 2221915a627a4bacadff95c1ace06814ac647d12 [file] [log] [blame]
Mady Mellorebdbbb92018-11-15 14:36:48 -08001/*
2 * Copyright (C) 2018 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
Mady Melloraa8fef22019-04-11 13:36:40 -070019import static android.app.Notification.FLAG_BUBBLE;
Mady Mellore80930e2019-03-21 16:00:45 -070020import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
Mady Mellorc2ff0112019-03-28 14:18:06 -070021import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
22import static android.service.notification.NotificationListenerService.REASON_CANCEL;
23import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
Mady Mellore80930e2019-03-21 16:00:45 -070024
Mady Mellor1a4e86f2019-05-03 16:07:23 -070025import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
26
Mady Mellorfc02cc32019-04-01 14:47:55 -070027import static com.google.common.truth.Truth.assertThat;
28
Mady Melloredd4ee12019-01-18 10:45:11 -080029import static org.junit.Assert.assertEquals;
Mady Mellorebdbbb92018-11-15 14:36:48 -080030import static org.junit.Assert.assertFalse;
31import static org.junit.Assert.assertTrue;
Mady Mellorc2ff0112019-03-28 14:18:06 -070032import static org.mockito.ArgumentMatchers.any;
Mady Mellorfc02cc32019-04-01 14:47:55 -070033import static org.mockito.ArgumentMatchers.anyBoolean;
Mady Mellorc2ff0112019-03-28 14:18:06 -070034import static org.mockito.ArgumentMatchers.anyInt;
Ned Burns01e38212019-01-03 16:32:52 -050035import static org.mockito.Mockito.atLeastOnce;
Mady Melloraa8fef22019-04-11 13:36:40 -070036import static org.mockito.Mockito.mock;
Mark Renouf08bc42a2019-03-07 13:01:59 -050037import static org.mockito.Mockito.never;
38import static org.mockito.Mockito.times;
Ned Burns01e38212019-01-03 16:32:52 -050039import static org.mockito.Mockito.verify;
40import static org.mockito.Mockito.when;
Mady Mellorebdbbb92018-11-15 14:36:48 -080041
42import android.app.IActivityManager;
Mady Mellore80930e2019-03-21 16:00:45 -070043import android.app.Notification;
Mark Renouf08bc42a2019-03-07 13:01:59 -050044import android.app.PendingIntent;
Mady Mellore80930e2019-03-21 16:00:45 -070045import android.content.BroadcastReceiver;
Mady Mellorebdbbb92018-11-15 14:36:48 -080046import android.content.Context;
Mady Mellore80930e2019-03-21 16:00:45 -070047import android.content.Intent;
48import android.content.IntentFilter;
49import android.graphics.drawable.Icon;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040050import android.service.notification.ZenModeConfig;
Mady Mellorebdbbb92018-11-15 14:36:48 -080051import android.testing.AndroidTestingRunner;
52import android.testing.TestableLooper;
53import android.view.WindowManager;
54import android.widget.FrameLayout;
55
Brett Chabot84151d92019-02-27 15:37:59 -080056import androidx.test.filters.SmallTest;
57
Mady Mellore80930e2019-03-21 16:00:45 -070058import com.android.systemui.R;
Mady Mellorebdbbb92018-11-15 14:36:48 -080059import com.android.systemui.SysuiTestCase;
Mady Melloraa8fef22019-04-11 13:36:40 -070060import com.android.systemui.statusbar.NotificationPresenter;
Mady Mellorc2ff0112019-03-28 14:18:06 -070061import com.android.systemui.statusbar.NotificationRemoveInterceptor;
Mady Mellorebdbbb92018-11-15 14:36:48 -080062import com.android.systemui.statusbar.NotificationTestHelper;
Lucas Dupine25c4872019-07-29 13:51:35 -070063import com.android.systemui.statusbar.SysuiStatusBarStateController;
Ned Burns01e38212019-01-03 16:32:52 -050064import com.android.systemui.statusbar.notification.NotificationEntryListener;
65import com.android.systemui.statusbar.notification.NotificationEntryManager;
Mady Melloraa8fef22019-04-11 13:36:40 -070066import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
Ned Burnsf81c4c42019-01-07 14:10:43 -050067import com.android.systemui.statusbar.notification.collection.NotificationData;
Mady Mellor9bad2242019-01-28 11:21:51 -080068import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellorebdbbb92018-11-15 14:36:48 -080069import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
70import com.android.systemui.statusbar.phone.DozeParameters;
Lucas Dupine25c4872019-07-29 13:51:35 -070071import com.android.systemui.statusbar.phone.KeyguardBypassController;
Mady Mellorebdbbb92018-11-15 14:36:48 -080072import com.android.systemui.statusbar.phone.StatusBarWindowController;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -070073import com.android.systemui.statusbar.policy.ConfigurationController;
Mady Melloraa8fef22019-04-11 13:36:40 -070074import com.android.systemui.statusbar.policy.HeadsUpManager;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040075import com.android.systemui.statusbar.policy.ZenModeController;
Mady Mellorebdbbb92018-11-15 14:36:48 -080076
77import org.junit.Before;
78import org.junit.Test;
79import org.junit.runner.RunWith;
Ned Burns01e38212019-01-03 16:32:52 -050080import org.mockito.ArgumentCaptor;
81import org.mockito.Captor;
Mady Mellorebdbbb92018-11-15 14:36:48 -080082import org.mockito.Mock;
83import org.mockito.MockitoAnnotations;
84
Mady Mellore80930e2019-03-21 16:00:45 -070085import java.util.concurrent.CountDownLatch;
86import java.util.concurrent.TimeUnit;
87
Mady Mellorebdbbb92018-11-15 14:36:48 -080088@SmallTest
89@RunWith(AndroidTestingRunner.class)
90@TestableLooper.RunWithLooper(setAsMainLooper = true)
91public class BubbleControllerTest extends SysuiTestCase {
92
Mady Mellor3ed46202019-03-26 20:22:35 -070093 // Some APIs rely on the app being foreground, check is via pkg name
94 private static final String FOREGROUND_TEST_PKG_NAME = "com.android.systemui.tests";
95
Mady Mellorebdbbb92018-11-15 14:36:48 -080096 @Mock
Ned Burns01e38212019-01-03 16:32:52 -050097 private NotificationEntryManager mNotificationEntryManager;
98 @Mock
Mady Mellorebdbbb92018-11-15 14:36:48 -080099 private WindowManager mWindowManager;
100 @Mock
101 private IActivityManager mActivityManager;
102 @Mock
103 private DozeParameters mDozeParameters;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700104 @Mock
105 private ConfigurationController mConfigurationController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400106 @Mock
107 private ZenModeController mZenModeController;
108 @Mock
109 private ZenModeConfig mZenModeConfig;
Lucas Dupine25c4872019-07-29 13:51:35 -0700110 @Mock
111 private SysuiStatusBarStateController mStatusBarStateController;
112 @Mock
113 private KeyguardBypassController mKeyguardBypassController;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700114
Mady Mellorebdbbb92018-11-15 14:36:48 -0800115 private FrameLayout mStatusBarView;
Ned Burns01e38212019-01-03 16:32:52 -0500116 @Captor
117 private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700118 @Captor
119 private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
Mady Mellorebdbbb92018-11-15 14:36:48 -0800120
121 private TestableBubbleController mBubbleController;
122 private StatusBarWindowController mStatusBarWindowController;
Ned Burns01e38212019-01-03 16:32:52 -0500123 private NotificationEntryListener mEntryListener;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700124 private NotificationRemoveInterceptor mRemoveInterceptor;
Mady Mellorebdbbb92018-11-15 14:36:48 -0800125
126 private NotificationTestHelper mNotificationTestHelper;
127 private ExpandableNotificationRow mRow;
128 private ExpandableNotificationRow mRow2;
Mady Mellore80930e2019-03-21 16:00:45 -0700129 private ExpandableNotificationRow mAutoExpandRow;
Mady Mellor3ed46202019-03-26 20:22:35 -0700130 private ExpandableNotificationRow mSuppressNotifRow;
Mady Mellorfc02cc32019-04-01 14:47:55 -0700131 private ExpandableNotificationRow mNonBubbleNotifRow;
Mady Mellorebdbbb92018-11-15 14:36:48 -0800132
Mady Mellorb4991e62019-01-10 15:14:51 -0800133 @Mock
134 private NotificationData mNotificationData;
Mady Melloracb12152019-01-29 15:24:48 -0800135 @Mock
136 private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener;
137 @Mock
138 private BubbleController.BubbleExpandListener mBubbleExpandListener;
Mark Renouf6b2331c2019-03-21 13:40:08 -0400139 @Mock
Mark Renouf08bc42a2019-03-07 13:01:59 -0500140 private PendingIntent mDeleteIntent;
141
Mady Mellorcfd06c12019-02-13 14:32:12 -0800142 private BubbleData mBubbleData;
143
Mady Mellorebdbbb92018-11-15 14:36:48 -0800144 @Before
145 public void setUp() throws Exception {
146 MockitoAnnotations.initMocks(this);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800147 mStatusBarView = new FrameLayout(mContext);
Ned Burns01e38212019-01-03 16:32:52 -0500148 mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800149
150 // Bubbles get added to status bar window view
151 mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
Lucas Dupine25c4872019-07-29 13:51:35 -0700152 mActivityManager, mDozeParameters, mStatusBarStateController,
153 mConfigurationController, mKeyguardBypassController);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800154 mStatusBarWindowController.add(mStatusBarView, 120 /* height */);
155
156 // Need notifications for bubbles
157 mNotificationTestHelper = new NotificationTestHelper(mContext);
Mark Renouf08bc42a2019-03-07 13:01:59 -0500158 mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
159 mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
Mady Mellorfc02cc32019-04-01 14:47:55 -0700160 mNonBubbleNotifRow = mNotificationTestHelper.createRow();
Mady Mellor3ed46202019-03-26 20:22:35 -0700161
162 // Some bubbles want to auto expand
163 Notification.BubbleMetadata autoExpandMetadata =
164 getBuilder().setAutoExpandBubble(true).build();
165 mAutoExpandRow = mNotificationTestHelper.createBubble(autoExpandMetadata,
166 FOREGROUND_TEST_PKG_NAME);
167
168 // Some bubbles want to suppress notifs
169 Notification.BubbleMetadata suppressNotifMetadata =
Mady Melloraa8fef22019-04-11 13:36:40 -0700170 getBuilder().setSuppressNotification(true).build();
Mady Mellor3ed46202019-03-26 20:22:35 -0700171 mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata,
172 FOREGROUND_TEST_PKG_NAME);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800173
Ned Burns01e38212019-01-03 16:32:52 -0500174 // Return non-null notification data from the NEM
175 when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
Mady Mellorb4991e62019-01-10 15:14:51 -0800176 when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
Ned Burns01e38212019-01-03 16:32:52 -0500177
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400178 mZenModeConfig.suppressedVisualEffects = 0;
179 when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
180
Mady Melloraa8fef22019-04-11 13:36:40 -0700181 TestableNotificationInterruptionStateProvider interruptionStateProvider =
182 new TestableNotificationInterruptionStateProvider(mContext);
183 interruptionStateProvider.setUpWithPresenter(
184 mock(NotificationPresenter.class),
185 mock(HeadsUpManager.class),
186 mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
Mark Renouf71a3af62019-04-08 15:02:54 -0400187 mBubbleData = new BubbleData(mContext);
Mady Mellorcfd06c12019-02-13 14:32:12 -0800188 mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400189 mBubbleData, mConfigurationController, interruptionStateProvider,
190 mZenModeController);
Mady Melloracb12152019-01-29 15:24:48 -0800191 mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
192 mBubbleController.setExpandListener(mBubbleExpandListener);
Ned Burns01e38212019-01-03 16:32:52 -0500193
194 // Get a reference to the BubbleController's entry listener
195 verify(mNotificationEntryManager, atLeastOnce())
196 .addNotificationEntryListener(mEntryListenerCaptor.capture());
197 mEntryListener = mEntryListenerCaptor.getValue();
Mady Mellorc2ff0112019-03-28 14:18:06 -0700198 // And the remove interceptor
199 verify(mNotificationEntryManager, atLeastOnce())
200 .setNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture());
201 mRemoveInterceptor = mRemoveInterceptorCaptor.getValue();
Mady Mellorebdbbb92018-11-15 14:36:48 -0800202 }
203
204 @Test
Mady Mellorebdbbb92018-11-15 14:36:48 -0800205 public void testAddBubble() {
Mark Renouff97ed462019-04-05 13:46:24 -0400206 mBubbleController.updateBubble(mRow.getEntry());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800207 assertTrue(mBubbleController.hasBubbles());
Mady Melloracb12152019-01-29 15:24:48 -0800208
209 verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800210 }
211
212 @Test
213 public void testHasBubbles() {
214 assertFalse(mBubbleController.hasBubbles());
Mark Renouff97ed462019-04-05 13:46:24 -0400215 mBubbleController.updateBubble(mRow.getEntry());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800216 assertTrue(mBubbleController.hasBubbles());
217 }
218
219 @Test
220 public void testRemoveBubble() {
Mark Renouff97ed462019-04-05 13:46:24 -0400221 mBubbleController.updateBubble(mRow.getEntry());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800222 assertTrue(mBubbleController.hasBubbles());
Mark Renouf71a3af62019-04-08 15:02:54 -0400223 verify(mNotificationEntryManager).updateNotifications();
Mady Melloracb12152019-01-29 15:24:48 -0800224 verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
225
Mark Renouf08bc42a2019-03-07 13:01:59 -0500226 mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800227 assertFalse(mStatusBarWindowController.getBubblesShowing());
Ned Burns01e38212019-01-03 16:32:52 -0500228 assertTrue(mRow.getEntry().isBubbleDismissed());
Mark Renouf71a3af62019-04-08 15:02:54 -0400229 verify(mNotificationEntryManager, times(2)).updateNotifications();
Mady Melloracb12152019-01-29 15:24:48 -0800230 verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800231 }
232
233 @Test
Mady Mellorc2ff0112019-03-28 14:18:06 -0700234 public void testRemoveBubble_withDismissedNotif() {
235 mEntryListener.onPendingEntryAdded(mRow.getEntry());
236 mBubbleController.updateBubble(mRow.getEntry());
237
238 assertTrue(mBubbleController.hasBubbles());
239 assertTrue(mRow.getEntry().showInShadeWhenBubble());
240
241 // Make it look like dismissed notif
242 mRow.getEntry().setShowInShadeWhenBubble(false);
243
244 // Now remove the bubble
245 mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
246
247 // Since the notif is dismissed, once the bubble is removed, performRemoveNotification gets
248 // called to really remove the notif
249 verify(mNotificationEntryManager, times(1)).performRemoveNotification(
Mady Mellor1a4e86f2019-05-03 16:07:23 -0700250 mRow.getEntry().notification, UNDEFINED_DISMISS_REASON);
Mady Mellorc2ff0112019-03-28 14:18:06 -0700251 assertFalse(mBubbleController.hasBubbles());
252 }
253
254 @Test
Mady Mellorebdbbb92018-11-15 14:36:48 -0800255 public void testDismissStack() {
Mark Renouff97ed462019-04-05 13:46:24 -0400256 mBubbleController.updateBubble(mRow.getEntry());
Mark Renouf71a3af62019-04-08 15:02:54 -0400257 verify(mNotificationEntryManager, times(1)).updateNotifications();
Mark Renouff97ed462019-04-05 13:46:24 -0400258 mBubbleController.updateBubble(mRow2.getEntry());
Mark Renouf71a3af62019-04-08 15:02:54 -0400259 verify(mNotificationEntryManager, times(2)).updateNotifications();
Mady Mellorebdbbb92018-11-15 14:36:48 -0800260 assertTrue(mBubbleController.hasBubbles());
261
Mark Renouf08bc42a2019-03-07 13:01:59 -0500262 mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800263 assertFalse(mStatusBarWindowController.getBubblesShowing());
Mark Renouf71a3af62019-04-08 15:02:54 -0400264 verify(mNotificationEntryManager, times(3)).updateNotifications();
Mady Melloracb12152019-01-29 15:24:48 -0800265 assertTrue(mRow.getEntry().isBubbleDismissed());
266 assertTrue(mRow2.getEntry().isBubbleDismissed());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800267 }
268
269 @Test
Mady Melloracb12152019-01-29 15:24:48 -0800270 public void testExpandCollapseStack() {
Mady Mellorebdbbb92018-11-15 14:36:48 -0800271 assertFalse(mBubbleController.isStackExpanded());
Mady Melloracb12152019-01-29 15:24:48 -0800272
273 // Mark it as a bubble and add it explicitly
274 mEntryListener.onPendingEntryAdded(mRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400275 mBubbleController.updateBubble(mRow.getEntry());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800276
Mady Melloracb12152019-01-29 15:24:48 -0800277 // We should have bubbles & their notifs should show in the shade
278 assertTrue(mBubbleController.hasBubbles());
279 assertTrue(mRow.getEntry().showInShadeWhenBubble());
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100280 assertFalse(mStatusBarWindowController.getBubbleExpanded());
Mady Melloracb12152019-01-29 15:24:48 -0800281
282 // Expand the stack
Mady Mellorebdbbb92018-11-15 14:36:48 -0800283 BubbleStackView stackView = mBubbleController.getStackView();
Mark Renouf71a3af62019-04-08 15:02:54 -0400284 mBubbleController.expandStack();
Mady Mellorebdbbb92018-11-15 14:36:48 -0800285 assertTrue(mBubbleController.isStackExpanded());
Mady Melloracb12152019-01-29 15:24:48 -0800286 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100287 assertTrue(mStatusBarWindowController.getBubbleExpanded());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800288
Mady Melloracb12152019-01-29 15:24:48 -0800289 // Make sure it's no longer in the shade
290 assertFalse(mRow.getEntry().showInShadeWhenBubble());
291
292 // Collapse
Mark Renouf71a3af62019-04-08 15:02:54 -0400293 mBubbleController.collapseStack();
Mady Melloracb12152019-01-29 15:24:48 -0800294 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800295 assertFalse(mBubbleController.isStackExpanded());
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100296 assertFalse(mStatusBarWindowController.getBubbleExpanded());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800297 }
298
299 @Test
Mady Melloracb12152019-01-29 15:24:48 -0800300 public void testCollapseAfterChangingExpandedBubble() {
301 // Mark it as a bubble and add it explicitly
302 mEntryListener.onPendingEntryAdded(mRow.getEntry());
303 mEntryListener.onPendingEntryAdded(mRow2.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400304 mBubbleController.updateBubble(mRow.getEntry());
305 mBubbleController.updateBubble(mRow2.getEntry());
Mady Mellorebdbbb92018-11-15 14:36:48 -0800306
Mady Melloracb12152019-01-29 15:24:48 -0800307 // We should have bubbles & their notifs should show in the shade
308 assertTrue(mBubbleController.hasBubbles());
309 assertTrue(mRow.getEntry().showInShadeWhenBubble());
310 assertTrue(mRow2.getEntry().showInShadeWhenBubble());
311
312 // Expand
Mady Mellorebdbbb92018-11-15 14:36:48 -0800313 BubbleStackView stackView = mBubbleController.getStackView();
Mark Renouf71a3af62019-04-08 15:02:54 -0400314 mBubbleController.expandStack();
Mady Mellorebdbbb92018-11-15 14:36:48 -0800315 assertTrue(mBubbleController.isStackExpanded());
Mark Renoufba5ab512019-05-02 15:21:01 -0400316 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800317
Mark Renoufba5ab512019-05-02 15:21:01 -0400318 // Last added is the one that is expanded
Mark Renouf71a3af62019-04-08 15:02:54 -0400319 assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
320 assertFalse(mRow2.getEntry().showInShadeWhenBubble());
321
Mark Renoufba5ab512019-05-02 15:21:01 -0400322 // Switch which bubble is expanded
323 mBubbleController.selectBubble(mRow.getEntry().key);
324 stackView.setExpandedBubble(mRow.getEntry());
325 assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
326 assertFalse(mRow.getEntry().showInShadeWhenBubble());
327
Mady Melloracb12152019-01-29 15:24:48 -0800328 // collapse for previous bubble
Mark Renoufba5ab512019-05-02 15:21:01 -0400329 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
Mady Melloracb12152019-01-29 15:24:48 -0800330 // expand for selected bubble
Mark Renoufba5ab512019-05-02 15:21:01 -0400331 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
Mady Melloredd4ee12019-01-18 10:45:11 -0800332
Mady Melloracb12152019-01-29 15:24:48 -0800333 // Collapse
Mady Mellorebdbbb92018-11-15 14:36:48 -0800334 mBubbleController.collapseStack();
335 assertFalse(mBubbleController.isStackExpanded());
336 }
337
Ned Burns01e38212019-01-03 16:32:52 -0500338 @Test
Mady Melloracb12152019-01-29 15:24:48 -0800339 public void testExpansionRemovesShowInShade() {
340 // Mark it as a bubble and add it explicitly
341 mEntryListener.onPendingEntryAdded(mRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400342 mBubbleController.updateBubble(mRow.getEntry());
Mady Melloracb12152019-01-29 15:24:48 -0800343
344 // We should have bubbles & their notifs should show in the shade
345 assertTrue(mBubbleController.hasBubbles());
346 assertTrue(mRow.getEntry().showInShadeWhenBubble());
347
348 // Expand
349 BubbleStackView stackView = mBubbleController.getStackView();
Mark Renouf71a3af62019-04-08 15:02:54 -0400350 mBubbleController.expandStack();
Mady Melloracb12152019-01-29 15:24:48 -0800351 assertTrue(mBubbleController.isStackExpanded());
352 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
353
354 // No longer show shade in notif after expansion
355 assertFalse(mRow.getEntry().showInShadeWhenBubble());
356 }
357
358 @Test
359 public void testRemoveLastExpandedCollapses() {
360 // Mark it as a bubble and add it explicitly
361 mEntryListener.onPendingEntryAdded(mRow.getEntry());
362 mEntryListener.onPendingEntryAdded(mRow2.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400363 mBubbleController.updateBubble(mRow.getEntry());
364 mBubbleController.updateBubble(mRow2.getEntry());
Mady Melloracb12152019-01-29 15:24:48 -0800365 verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
366
367 // Expand
368 BubbleStackView stackView = mBubbleController.getStackView();
Mark Renouf71a3af62019-04-08 15:02:54 -0400369 mBubbleController.expandStack();
Mady Melloracb12152019-01-29 15:24:48 -0800370
371 assertTrue(mBubbleController.isStackExpanded());
Mark Renoufba5ab512019-05-02 15:21:01 -0400372 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
Mady Melloracb12152019-01-29 15:24:48 -0800373
Mark Renoufba5ab512019-05-02 15:21:01 -0400374 // Last added is the one that is expanded
375 assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
376 assertFalse(mRow2.getEntry().showInShadeWhenBubble());
Mady Melloracb12152019-01-29 15:24:48 -0800377
378 // Dismiss currently expanded
Mark Renouf08bc42a2019-03-07 13:01:59 -0500379 mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
380 BubbleController.DISMISS_USER_GESTURE);
Mark Renoufba5ab512019-05-02 15:21:01 -0400381 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
Mady Melloracb12152019-01-29 15:24:48 -0800382
Mark Renoufba5ab512019-05-02 15:21:01 -0400383 // Make sure first bubble is selected
384 assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
385 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
Mady Melloracb12152019-01-29 15:24:48 -0800386
387 // Dismiss that one
Mark Renouf08bc42a2019-03-07 13:01:59 -0500388 mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
389 BubbleController.DISMISS_USER_GESTURE);
Mady Melloracb12152019-01-29 15:24:48 -0800390
391 // Make sure state changes and collapse happens
Mark Renoufba5ab512019-05-02 15:21:01 -0400392 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
Mady Melloracb12152019-01-29 15:24:48 -0800393 verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
394 assertFalse(mBubbleController.hasBubbles());
395 }
396
397 @Test
Mady Mellor3ed46202019-03-26 20:22:35 -0700398 public void testAutoExpand_FailsNotForeground() {
Mady Mellore80930e2019-03-21 16:00:45 -0700399 assertFalse(mBubbleController.isStackExpanded());
400
401 // Add the auto expand bubble
402 mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400403 mBubbleController.updateBubble(mAutoExpandRow.getEntry());
Mady Mellore80930e2019-03-21 16:00:45 -0700404
405 // Expansion shouldn't change
406 verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
407 mAutoExpandRow.getEntry().key);
408 assertFalse(mBubbleController.isStackExpanded());
409
410 // # of bubbles should change
411 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
412 }
413
414 @Test
Mady Mellor3ed46202019-03-26 20:22:35 -0700415 public void testAutoExpand_SucceedsForeground() {
Mady Mellore80930e2019-03-21 16:00:45 -0700416 final CountDownLatch latch = new CountDownLatch(1);
417 BroadcastReceiver receiver = new BroadcastReceiver() {
418 @Override
419 public void onReceive(Context context, Intent intent) {
420 latch.countDown();
421 }
422 };
423 IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
424 mContext.registerReceiver(receiver, filter);
425
426 assertFalse(mBubbleController.isStackExpanded());
427
428 // Make ourselves foreground
429 Intent i = new Intent(mContext, BubblesTestActivity.class);
430 i.setFlags(FLAG_ACTIVITY_NEW_TASK);
431 mContext.startActivity(i);
432
433 try {
434 latch.await(100, TimeUnit.MILLISECONDS);
435 } catch (InterruptedException e) {
436 e.printStackTrace();
437 }
438
439 // Add the auto expand bubble
440 mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400441 mBubbleController.updateBubble(mAutoExpandRow.getEntry());
Mady Mellore80930e2019-03-21 16:00:45 -0700442
443 // Expansion should change
444 verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
445 mAutoExpandRow.getEntry().key);
446 assertTrue(mBubbleController.isStackExpanded());
447
448 // # of bubbles should change
449 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
450 mContext.unregisterReceiver(receiver);
451 }
452
Mady Mellor3ed46202019-03-26 20:22:35 -0700453 @Test
454 public void testSuppressNotif_FailsNotForeground() {
455 // Add the suppress notif bubble
456 mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400457 mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
Mady Mellor3ed46202019-03-26 20:22:35 -0700458
Mady Mellorfc02cc32019-04-01 14:47:55 -0700459 // Should show in shade because we weren't forground
Mady Mellor3ed46202019-03-26 20:22:35 -0700460 assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
461
462 // # of bubbles should change
463 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
464 }
465
466 @Test
467 public void testSuppressNotif_SucceedsForeground() {
468 final CountDownLatch latch = new CountDownLatch(1);
469 BroadcastReceiver receiver = new BroadcastReceiver() {
470 @Override
471 public void onReceive(Context context, Intent intent) {
472 latch.countDown();
473 }
474 };
475 IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
476 mContext.registerReceiver(receiver, filter);
477
478 assertFalse(mBubbleController.isStackExpanded());
479
480 // Make ourselves foreground
481 Intent i = new Intent(mContext, BubblesTestActivity.class);
482 i.setFlags(FLAG_ACTIVITY_NEW_TASK);
483 mContext.startActivity(i);
484
485 try {
486 latch.await(100, TimeUnit.MILLISECONDS);
487 } catch (InterruptedException e) {
488 e.printStackTrace();
489 }
490
491 // Add the suppress notif bubble
492 mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400493 mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
Mady Mellor3ed46202019-03-26 20:22:35 -0700494
Mady Mellorfc02cc32019-04-01 14:47:55 -0700495 // Should NOT show in shade because we were foreground
Mady Mellor3ed46202019-03-26 20:22:35 -0700496 assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
497
498 // # of bubbles should change
499 verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
500 mContext.unregisterReceiver(receiver);
501 }
Mady Mellore80930e2019-03-21 16:00:45 -0700502
503 @Test
Mark Renouffec45da2019-03-13 13:24:27 -0400504 public void testExpandStackAndSelectBubble_removedFirst() {
505 final String key = mRow.getEntry().key;
506
507 mEntryListener.onPendingEntryAdded(mRow.getEntry());
Mark Renouff97ed462019-04-05 13:46:24 -0400508 mBubbleController.updateBubble(mRow.getEntry());
Mark Renouffec45da2019-03-13 13:24:27 -0400509
Mark Renouffec45da2019-03-13 13:24:27 -0400510 // Simulate notification cancellation.
Mady Mellorc2ff0112019-03-28 14:18:06 -0700511 mRemoveInterceptor.onNotificationRemoveRequested(mRow.getEntry().key, REASON_APP_CANCEL);
Mark Renouffec45da2019-03-13 13:24:27 -0400512
513 mBubbleController.expandStackAndSelectBubble(key);
514 }
515
516 @Test
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800517 public void testMarkNewNotificationAsShowInShade() {
518 mEntryListener.onPendingEntryAdded(mRow.getEntry());
519 assertTrue(mRow.getEntry().showInShadeWhenBubble());
520 }
521
Mark Renouf08bc42a2019-03-07 13:01:59 -0500522 @Test
Mady Mellorfc02cc32019-04-01 14:47:55 -0700523 public void testAddNotif_notBubble() {
524 mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
525 mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
526
527 verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
528 assertThat(mBubbleController.hasBubbles()).isFalse();
529 }
530
531 @Test
Mark Renouf08bc42a2019-03-07 13:01:59 -0500532 public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
Mark Renouff97ed462019-04-05 13:46:24 -0400533 mBubbleController.updateBubble(mRow.getEntry());
Mark Renouf08bc42a2019-03-07 13:01:59 -0500534 mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_AGED);
535 verify(mDeleteIntent, never()).send();
536 }
537
538 @Test
539 public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
Mark Renouff97ed462019-04-05 13:46:24 -0400540 mBubbleController.updateBubble(mRow.getEntry());
Mark Renouf08bc42a2019-03-07 13:01:59 -0500541 mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
542 verify(mDeleteIntent, times(1)).send();
543 }
544
545 @Test
546 public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
Mark Renouff97ed462019-04-05 13:46:24 -0400547 mBubbleController.updateBubble(mRow.getEntry());
548 mBubbleController.updateBubble(mRow2.getEntry());
Mark Renouf08bc42a2019-03-07 13:01:59 -0500549 mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
550 verify(mDeleteIntent, times(2)).send();
551 }
552
Mady Melloraa8fef22019-04-11 13:36:40 -0700553 @Test
554 public void testRemoveBubble_noLongerBubbleAfterUpdate()
555 throws PendingIntent.CanceledException {
556 mBubbleController.updateBubble(mRow.getEntry());
557 assertTrue(mBubbleController.hasBubbles());
558
559 mRow.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE;
560 mEntryListener.onPreEntryUpdated(mRow.getEntry());
561
562 assertFalse(mBubbleController.hasBubbles());
563 verify(mDeleteIntent, never()).send();
564 }
565
Mady Mellorc2ff0112019-03-28 14:18:06 -0700566 @Test
567 public void testRemoveBubble_succeeds_appCancel() {
568 mEntryListener.onPendingEntryAdded(mRow.getEntry());
569 mBubbleController.updateBubble(mRow.getEntry());
570
571 assertTrue(mBubbleController.hasBubbles());
572
573 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
574 mRow.getEntry().key, REASON_APP_CANCEL);
575
576 // Cancels always remove so no need to intercept
577 assertFalse(intercepted);
578 assertFalse(mBubbleController.hasBubbles());
579 }
580
581 @Test
582 public void removeBubble_fails_clearAll() {
583 mEntryListener.onPendingEntryAdded(mRow.getEntry());
584 mBubbleController.updateBubble(mRow.getEntry());
585
586 assertTrue(mBubbleController.hasBubbles());
587 assertTrue(mRow.getEntry().showInShadeWhenBubble());
588
589 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
590 mRow.getEntry().key, REASON_CANCEL_ALL);
591
592 // Intercept!
593 assertTrue(intercepted);
594 // Should update show in shade state
595 assertFalse(mRow.getEntry().showInShadeWhenBubble());
596
597 verify(mNotificationEntryManager, never()).performRemoveNotification(
598 any(), anyInt());
599 assertTrue(mBubbleController.hasBubbles());
600 }
601
602 @Test
603 public void removeBubble_fails_userDismissNotif() {
604 mEntryListener.onPendingEntryAdded(mRow.getEntry());
605 mBubbleController.updateBubble(mRow.getEntry());
606
607 assertTrue(mBubbleController.hasBubbles());
608 assertTrue(mRow.getEntry().showInShadeWhenBubble());
609
610 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
611 mRow.getEntry().key, REASON_CANCEL);
612
613 // Intercept!
614 assertTrue(intercepted);
615 // Should update show in shade state
616 assertFalse(mRow.getEntry().showInShadeWhenBubble());
617
618 verify(mNotificationEntryManager, never()).performRemoveNotification(
619 any(), anyInt());
620 assertTrue(mBubbleController.hasBubbles());
621 }
622
623 @Test
624 public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
625 mEntryListener.onPendingEntryAdded(mRow.getEntry());
626 mBubbleController.updateBubble(mRow.getEntry());
627
628 assertTrue(mBubbleController.hasBubbles());
629 assertTrue(mRow.getEntry().showInShadeWhenBubble());
630
631 // Dismiss the bubble
632 mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
633 assertFalse(mBubbleController.hasBubbles());
634
635 // Dismiss the notification
636 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
637 mRow.getEntry().key, REASON_CANCEL);
638
639 // It's no longer a bubble so we shouldn't intercept
640 assertFalse(intercepted);
641 }
642
Mady Mellorebdbbb92018-11-15 14:36:48 -0800643 static class TestableBubbleController extends BubbleController {
Issei Suzukic0387542019-03-08 17:31:14 +0100644 // Let's assume surfaces can be synchronized immediately.
Mady Mellorebdbbb92018-11-15 14:36:48 -0800645 TestableBubbleController(Context context,
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700646 StatusBarWindowController statusBarWindowController, BubbleData data,
Mady Melloraa8fef22019-04-11 13:36:40 -0700647 ConfigurationController configurationController,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400648 NotificationInterruptionStateProvider interruptionStateProvider,
649 ZenModeController zenModeController) {
Mady Melloraa8fef22019-04-11 13:36:40 -0700650 super(context, statusBarWindowController, data, Runnable::run,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400651 configurationController, interruptionStateProvider, zenModeController);
Mady Mellorebdbbb92018-11-15 14:36:48 -0800652 }
Mady Mellor9bad2242019-01-28 11:21:51 -0800653
654 @Override
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800655 public boolean shouldAutoBubbleForFlags(Context c, NotificationEntry entry) {
Mady Mellor9bad2242019-01-28 11:21:51 -0800656 return entry.notification.getNotification().getBubbleMetadata() != null;
657 }
Mady Mellorebdbbb92018-11-15 14:36:48 -0800658 }
Mady Mellore80930e2019-03-21 16:00:45 -0700659
Mady Melloraa8fef22019-04-11 13:36:40 -0700660 public static class TestableNotificationInterruptionStateProvider extends
661 NotificationInterruptionStateProvider {
662
663 public TestableNotificationInterruptionStateProvider(Context context) {
664 super(context);
665 mUseHeadsUp = true;
666 }
667 }
668
Mady Mellore80930e2019-03-21 16:00:45 -0700669 /**
670 * @return basic {@link android.app.Notification.BubbleMetadata.Builder}
671 */
672 private Notification.BubbleMetadata.Builder getBuilder() {
673 Intent target = new Intent(mContext, BubblesTestActivity.class);
674 PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
675 return new Notification.BubbleMetadata.Builder()
676 .setIntent(bubbleIntent)
677 .setIcon(Icon.createWithResource(mContext, R.drawable.android));
678 }
Mady Mellorebdbbb92018-11-15 14:36:48 -0800679}