blob: f4b5a5b7f3cf87eb7f211336991f906619e4ca3f [file] [log] [blame]
Ned Burns9eb06332019-04-23 16:02:12 -04001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar.notification.stack;
18
19import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
20
Evan Laird25f02752019-08-14 19:25:06 -040021import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
Steve Elliott35956b42020-02-25 17:12:09 -050022import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
23import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
Evan Laird25f02752019-08-14 19:25:06 -040024import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
25
Ned Burns9eb06332019-04-23 16:02:12 -040026import static org.mockito.ArgumentMatchers.any;
27import static org.mockito.ArgumentMatchers.anyInt;
28import static org.mockito.ArgumentMatchers.eq;
29import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
30import static org.mockito.Mockito.clearInvocations;
31import static org.mockito.Mockito.mock;
32import static org.mockito.Mockito.never;
33import static org.mockito.Mockito.verify;
34import static org.mockito.Mockito.when;
35
36import android.testing.AndroidTestingRunner;
37import android.testing.TestableLooper;
38import android.util.AttributeSet;
Ned Burns2c74c2a2019-06-13 19:06:47 -040039import android.view.LayoutInflater;
Ned Burns9eb06332019-04-23 16:02:12 -040040import android.view.View;
41import android.view.ViewGroup;
42
43import androidx.test.filters.SmallTest;
44
45import com.android.systemui.ActivityStarterDelegate;
46import com.android.systemui.SysuiTestCase;
Selim Cinekb52642b2020-04-17 14:30:29 -070047import com.android.systemui.media.KeyguardMediaController;
Ned Burns7eeccdd2019-05-15 14:50:11 -040048import com.android.systemui.plugins.statusbar.StatusBarStateController;
49import com.android.systemui.statusbar.StatusBarState;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050050import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
Steve Elliottb0940382020-02-20 14:24:02 -050051import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050052import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
Ned Burns9eb06332019-04-23 16:02:12 -040053import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050054import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
Ned Burns2c74c2a2019-06-13 19:06:47 -040055import com.android.systemui.statusbar.policy.ConfigurationController;
Ned Burns9eb06332019-04-23 16:02:12 -040056
57import org.junit.Before;
58import org.junit.Rule;
59import org.junit.Test;
60import org.junit.runner.RunWith;
61import org.mockito.Mock;
62import org.mockito.junit.MockitoJUnit;
63import org.mockito.junit.MockitoRule;
64
65@SmallTest
66@RunWith(AndroidTestingRunner.class)
Dave Mankoff56fe9e42020-01-08 15:42:06 -050067@TestableLooper.RunWithLooper(setAsMainLooper = true)
Ned Burns9eb06332019-04-23 16:02:12 -040068public class NotificationSectionsManagerTest extends SysuiTestCase {
69
70 @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule();
71
72 @Mock private NotificationStackScrollLayout mNssl;
73 @Mock private ActivityStarterDelegate mActivityStarterDelegate;
Ned Burns7eeccdd2019-05-15 14:50:11 -040074 @Mock private StatusBarStateController mStatusBarStateController;
Ned Burns2c74c2a2019-06-13 19:06:47 -040075 @Mock private ConfigurationController mConfigurationController;
Steve Elliottb0940382020-02-20 14:24:02 -050076 @Mock private PeopleHubViewAdapter mPeopleHubAdapter;
Selim Cinekb52642b2020-04-17 14:30:29 -070077 @Mock private KeyguardMediaController mKeyguardMediaController;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050078 @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
79 @Mock private NotificationRowComponent mNotificationRowComponent;
80 @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
Ned Burns9eb06332019-04-23 16:02:12 -040081
82 private NotificationSectionsManager mSectionsManager;
83
84 @Before
85 public void setUp() {
Dave Mankoff56fe9e42020-01-08 15:42:06 -050086 when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(2);
87 when(mNotificationRowComponent.getActivatableNotificationViewController()).thenReturn(
88 mActivatableNotificationViewController
89 );
Ned Burns7eeccdd2019-05-15 14:50:11 -040090 mSectionsManager =
91 new NotificationSectionsManager(
Ned Burns7eeccdd2019-05-15 14:50:11 -040092 mActivityStarterDelegate,
93 mStatusBarStateController,
Ned Burns2c74c2a2019-06-13 19:06:47 -040094 mConfigurationController,
Steve Elliott58adc212019-10-15 11:07:54 -040095 mPeopleHubAdapter,
Selim Cinekb52642b2020-04-17 14:30:29 -070096 mKeyguardMediaController,
Steve Elliottb0940382020-02-20 14:24:02 -050097 mSectionsFeatureManager
98 );
Ned Burns9eb06332019-04-23 16:02:12 -040099 // Required in order for the header inflation to work properly
100 when(mNssl.generateLayoutParams(any(AttributeSet.class)))
101 .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500102 mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
Ned Burns9eb06332019-04-23 16:02:12 -0400103 when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
Ned Burns7eeccdd2019-05-15 14:50:11 -0400104 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
Ned Burns9eb06332019-04-23 16:02:12 -0400105 }
106
Ned Burns2c74c2a2019-06-13 19:06:47 -0400107 @Test(expected = IllegalStateException.class)
108 public void testDuplicateInitializeThrows() {
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500109 mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
Ned Burns2c74c2a2019-06-13 19:06:47 -0400110 }
111
Ned Burns9eb06332019-04-23 16:02:12 -0400112 @Test
113 public void testInsertHeader() {
114 // GIVEN a stack with HI and LO rows but no section headers
Steve Elliott35956b42020-02-25 17:12:09 -0500115 setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400116
117 // WHEN we update the section headers
118 mSectionsManager.updateSectionBoundaries();
119
120 // THEN a LO section header is added
121 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
122 }
123
124 @Test
125 public void testRemoveHeader() {
126 // GIVEN a stack that originally had a header between the HI and LO sections
Steve Elliott35956b42020-02-25 17:12:09 -0500127 setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400128 mSectionsManager.updateSectionBoundaries();
129
130 // WHEN the last LO row is replaced with a HI row
Steve Elliott35956b42020-02-25 17:12:09 -0500131 setStackState(
132 ChildType.ALERTING,
133 ChildType.ALERTING,
134 ChildType.GENTLE_HEADER,
135 ChildType.ALERTING);
Ned Burns9eb06332019-04-23 16:02:12 -0400136 clearInvocations(mNssl);
137 mSectionsManager.updateSectionBoundaries();
138
139 // THEN the LO section header is removed
140 verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
141 }
142
143 @Test
144 public void testDoNothingIfHeaderAlreadyRemoved() {
145 // GIVEN a stack with only HI rows
Steve Elliott35956b42020-02-25 17:12:09 -0500146 setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING);
Ned Burns9eb06332019-04-23 16:02:12 -0400147
148 // WHEN we update the sections headers
149 mSectionsManager.updateSectionBoundaries();
150
151 // THEN we don't add any section headers
152 verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
153 }
154
155 @Test
156 public void testMoveHeaderForward() {
157 // GIVEN a stack that originally had a header between the HI and LO sections
158 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500159 ChildType.ALERTING,
160 ChildType.ALERTING,
161 ChildType.ALERTING,
162 ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400163 mSectionsManager.updateSectionBoundaries();
164
165 // WHEN the LO section moves forward
166 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500167 ChildType.ALERTING,
168 ChildType.ALERTING,
169 ChildType.GENTLE,
170 ChildType.GENTLE_HEADER,
171 ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400172 mSectionsManager.updateSectionBoundaries();
173
174 // THEN the LO section header is also moved forward
175 verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
176 }
177
178 @Test
179 public void testMoveHeaderBackward() {
180 // GIVEN a stack that originally had a header between the HI and LO sections
181 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500182 ChildType.ALERTING,
183 ChildType.GENTLE,
184 ChildType.GENTLE,
185 ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400186 mSectionsManager.updateSectionBoundaries();
187
188 // WHEN the LO section moves backward
189 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500190 ChildType.ALERTING,
191 ChildType.GENTLE_HEADER,
192 ChildType.ALERTING,
193 ChildType.ALERTING,
194 ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400195 mSectionsManager.updateSectionBoundaries();
196
197 // THEN the LO section header is also moved backward (with appropriate index shifting)
198 verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 3);
199 }
200
201 @Test
202 public void testHeaderRemovedFromTransientParent() {
203 // GIVEN a stack where the header is animating away
204 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500205 ChildType.ALERTING,
206 ChildType.GENTLE,
207 ChildType.GENTLE,
208 ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400209 mSectionsManager.updateSectionBoundaries();
210 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500211 ChildType.ALERTING,
212 ChildType.GENTLE_HEADER);
Ned Burns9eb06332019-04-23 16:02:12 -0400213 mSectionsManager.updateSectionBoundaries();
214 clearInvocations(mNssl);
215
216 ViewGroup transientParent = mock(ViewGroup.class);
217 mSectionsManager.getGentleHeaderView().setTransientContainer(transientParent);
218
219 // WHEN the LO section reappears
220 setStackState(
Steve Elliott35956b42020-02-25 17:12:09 -0500221 ChildType.ALERTING,
222 ChildType.GENTLE);
Ned Burns9eb06332019-04-23 16:02:12 -0400223 mSectionsManager.updateSectionBoundaries();
224
225 // THEN the header is first removed from the transient parent before being added to the
226 // NSSL.
227 verify(transientParent).removeTransientView(mSectionsManager.getGentleHeaderView());
228 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 1);
229 }
230
Ned Burns7eeccdd2019-05-15 14:50:11 -0400231 @Test
232 public void testHeaderNotShownOnLockscreen() {
233 // GIVEN a stack of HI and LO notifs on the lockscreen
234 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
Steve Elliott35956b42020-02-25 17:12:09 -0500235 setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
Ned Burns7eeccdd2019-05-15 14:50:11 -0400236
237 // WHEN we update the section headers
238 mSectionsManager.updateSectionBoundaries();
239
240 // Then the section header is not added
241 verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
242 }
243
244 @Test
245 public void testHeaderShownWhenEnterLockscreen() {
246 // GIVEN a stack of HI and LO notifs on the lockscreen
247 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
Steve Elliott35956b42020-02-25 17:12:09 -0500248 setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
Ned Burns7eeccdd2019-05-15 14:50:11 -0400249 mSectionsManager.updateSectionBoundaries();
250
251 // WHEN we unlock
252 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
253 mSectionsManager.updateSectionBoundaries();
254
255 // Then the section header is added
256 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
257 }
258
259 @Test
260 public void testHeaderHiddenWhenEnterLockscreen() {
261 // GIVEN a stack of HI and LO notifs on the shade
Steve Elliott35956b42020-02-25 17:12:09 -0500262 setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
Ned Burns7eeccdd2019-05-15 14:50:11 -0400263
264 // WHEN we go back to the keyguard
265 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
266 mSectionsManager.updateSectionBoundaries();
267
268 // Then the section header is removed
Steve Elliott35956b42020-02-25 17:12:09 -0500269 verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
Ned Burns7eeccdd2019-05-15 14:50:11 -0400270 }
271
Steve Elliott35956b42020-02-25 17:12:09 -0500272 @Test
273 public void testPeopleFiltering_addHeadersFromShowingOnlyGentle() {
274 enablePeopleFiltering();
275
276 setStackState(
277 ChildType.GENTLE_HEADER,
278 ChildType.PERSON,
279 ChildType.ALERTING,
280 ChildType.GENTLE);
281 mSectionsManager.updateSectionBoundaries();
282
283 verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
284 verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
285 verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
286 }
287
288 @Test
289 public void testPeopleFiltering_addAllHeaders() {
290 enablePeopleFiltering();
291
292 setStackState(
293 ChildType.PERSON,
294 ChildType.ALERTING,
295 ChildType.GENTLE);
296 mSectionsManager.updateSectionBoundaries();
297
298 verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 2);
299 verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
300 verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
301 }
302
303 @Test
304 public void testPeopleFiltering_moveAllHeaders() {
305 enablePeopleFiltering();
306
307 setStackState(
308 ChildType.PEOPLE_HEADER,
309 ChildType.ALERTING_HEADER,
310 ChildType.GENTLE_HEADER,
311 ChildType.PERSON,
312 ChildType.ALERTING,
313 ChildType.GENTLE);
314 mSectionsManager.updateSectionBoundaries();
315
316 verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 4);
317 verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
318 verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
319 }
320
Steve Elliott6de51a72020-03-05 11:00:47 -0500321 @Test
322 public void testPeopleFiltering_keepPeopleHeaderWhenSectionEmpty() {
323 mSectionsManager.setPeopleHubVisible(true);
324 enablePeopleFiltering();
325
326 setStackState(
327 ChildType.PEOPLE_HEADER,
328 ChildType.ALERTING_HEADER,
329 ChildType.ALERTING,
330 ChildType.GENTLE_HEADER,
331 ChildType.GENTLE
332 );
333 mSectionsManager.updateSectionBoundaries();
334
335 verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
336 verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
337 }
338
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400339 @Test
340 public void testMediaControls_AddWhenEnterKeyguard() {
341 enableMediaControls();
342
343 // GIVEN a stack that doesn't include media controls
344 setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
345
346 // WHEN we go back to the keyguard
347 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
348 mSectionsManager.updateSectionBoundaries();
349
350 // Then the media controls are added
351 verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0);
352 }
353
354 @Test
355 public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() {
356 enableMediaControls();
357
358 // GIVEN a stack that doesn't include media controls but includes HEADS_UP
359 setStackState(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
360 ChildType.GENTLE);
361
362 // WHEN we go back to the keyguard
363 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
364 mSectionsManager.updateSectionBoundaries();
365
366 // Then the media controls are added after HEADS_UP
367 verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1);
368 }
369
Steve Elliott35956b42020-02-25 17:12:09 -0500370 private void enablePeopleFiltering() {
371 when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
372 when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
373 }
374
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400375 private void enableMediaControls() {
376 when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
377 when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
378 }
379
Steve Elliott35956b42020-02-25 17:12:09 -0500380 private enum ChildType {
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400381 MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING,
382 GENTLE, OTHER
Steve Elliott35956b42020-02-25 17:12:09 -0500383 }
Ned Burns9eb06332019-04-23 16:02:12 -0400384
385 private void setStackState(ChildType... children) {
386 when(mNssl.getChildCount()).thenReturn(children.length);
387 for (int i = 0; i < children.length; i++) {
388 View child;
389 switch (children[i]) {
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400390 case MEDIA_CONTROLS:
391 child = mSectionsManager.getMediaControlsView();
392 break;
Steve Elliott35956b42020-02-25 17:12:09 -0500393 case PEOPLE_HEADER:
394 child = mSectionsManager.getPeopleHeaderView();
395 break;
396 case ALERTING_HEADER:
397 child = mSectionsManager.getAlertingHeaderView();
398 break;
399 case GENTLE_HEADER:
Ned Burns9eb06332019-04-23 16:02:12 -0400400 child = mSectionsManager.getGentleHeaderView();
401 break;
Steve Elliott35956b42020-02-25 17:12:09 -0500402 case HEADS_UP:
403 child = mockNotification(BUCKET_HEADS_UP);
404 break;
405 case PERSON:
406 child = mockNotification(BUCKET_PEOPLE);
407 break;
408 case ALERTING:
409 child = mockNotification(BUCKET_ALERTING);
410 break;
411 case GENTLE:
412 child = mockNotification(BUCKET_SILENT);
413 break;
414 case OTHER:
415 child = mock(View.class);
416 when(child.getVisibility()).thenReturn(View.VISIBLE);
417 when(child.getParent()).thenReturn(mNssl);
Ned Burns9eb06332019-04-23 16:02:12 -0400418 break;
419 default:
420 throw new RuntimeException("Unknown ChildType: " + children[i]);
421 }
422 when(mNssl.getChildAt(i)).thenReturn(child);
423 when(mNssl.indexOfChild(child)).thenReturn(i);
424 }
425 }
Steve Elliott35956b42020-02-25 17:12:09 -0500426
427 private View mockNotification(int bucket) {
428 ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
429 RETURNS_DEEP_STUBS);
430 when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
431 when(notifRow.getEntry().getBucket()).thenReturn(bucket);
432 when(notifRow.getParent()).thenReturn(mNssl);
433 return notifRow;
434 }
Ned Burns9eb06332019-04-23 16:02:12 -0400435}