1 /* 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 17 package com.android.systemui.statusbar.notification.stack; 18 19 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 20 21 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; 22 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE; 23 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; 24 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; 25 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; 26 27 import static com.google.common.truth.Truth.assertThat; 28 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.anyInt; 31 import static org.mockito.ArgumentMatchers.eq; 32 import static org.mockito.Mockito.RETURNS_DEEP_STUBS; 33 import static org.mockito.Mockito.clearInvocations; 34 import static org.mockito.Mockito.doAnswer; 35 import static org.mockito.Mockito.mock; 36 import static org.mockito.Mockito.never; 37 import static org.mockito.Mockito.verify; 38 import static org.mockito.Mockito.when; 39 40 import android.testing.AndroidTestingRunner; 41 import android.testing.TestableLooper; 42 import android.util.AttributeSet; 43 import android.view.LayoutInflater; 44 import android.view.View; 45 import android.view.ViewGroup; 46 47 import androidx.test.filters.SmallTest; 48 49 import com.android.systemui.ActivityStarterDelegate; 50 import com.android.systemui.SysuiTestCase; 51 import com.android.systemui.media.KeyguardMediaController; 52 import com.android.systemui.plugins.statusbar.StatusBarStateController; 53 import com.android.systemui.statusbar.StatusBarState; 54 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; 55 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 56 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; 57 import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter; 58 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; 59 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 60 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; 61 import com.android.systemui.statusbar.policy.ConfigurationController; 62 63 import org.junit.Before; 64 import org.junit.Rule; 65 import org.junit.Test; 66 import org.junit.runner.RunWith; 67 import org.mockito.Mock; 68 import org.mockito.junit.MockitoJUnit; 69 import org.mockito.junit.MockitoRule; 70 71 import java.util.ArrayList; 72 import java.util.List; 73 74 @SmallTest 75 @RunWith(AndroidTestingRunner.class) 76 @TestableLooper.RunWithLooper(setAsMainLooper = true) 77 public class NotificationSectionsManagerTest extends SysuiTestCase { 78 79 @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); 80 81 @Mock private NotificationStackScrollLayout mNssl; 82 @Mock private ActivityStarterDelegate mActivityStarterDelegate; 83 @Mock private StatusBarStateController mStatusBarStateController; 84 @Mock private ConfigurationController mConfigurationController; 85 @Mock private PeopleHubViewAdapter mPeopleHubAdapter; 86 @Mock private KeyguardMediaController mKeyguardMediaController; 87 @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager; 88 @Mock private NotificationRowComponent mNotificationRowComponent; 89 @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; 90 @Mock private NotificationSectionsLogger mLogger; 91 @Mock private SectionHeaderController mIncomingHeaderController; 92 @Mock private SectionHeaderController mPeopleHeaderController; 93 @Mock private SectionHeaderController mAlertingHeaderController; 94 @Mock private SectionHeaderController mSilentHeaderController; 95 96 private NotificationSectionsManager mSectionsManager; 97 98 @Before setUp()99 public void setUp() { 100 when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer( 101 invocation -> { 102 int count = 2; 103 if (mSectionsFeatureManager.isFilteringEnabled()) { 104 count = 5; 105 } 106 if (mSectionsFeatureManager.isMediaControlsEnabled()) { 107 if (!mSectionsFeatureManager.isFilteringEnabled()) { 108 count = 5; 109 } else { 110 count += 1; 111 } 112 } 113 return count; 114 }); 115 when(mNotificationRowComponent.getActivatableNotificationViewController()) 116 .thenReturn(mActivatableNotificationViewController); 117 when(mIncomingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); 118 when(mPeopleHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); 119 when(mAlertingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); 120 when(mSilentHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); 121 mSectionsManager = 122 new NotificationSectionsManager( 123 mStatusBarStateController, 124 mConfigurationController, 125 mKeyguardMediaController, 126 mSectionsFeatureManager, 127 mLogger, 128 mIncomingHeaderController, 129 mPeopleHeaderController, 130 mAlertingHeaderController, 131 mSilentHeaderController 132 ); 133 // Required in order for the header inflation to work properly 134 when(mNssl.generateLayoutParams(any(AttributeSet.class))) 135 .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); 136 mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext)); 137 when(mNssl.indexOfChild(any(View.class))).thenReturn(-1); 138 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); 139 140 } 141 142 @Test(expected = IllegalStateException.class) testDuplicateInitializeThrows()143 public void testDuplicateInitializeThrows() { 144 mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext)); 145 } 146 147 @Test testInsertHeader()148 public void testInsertHeader() { 149 // GIVEN a stack with HI and LO rows but no section headers 150 setStackState( 151 ALERTING, 152 ALERTING, 153 ALERTING, 154 GENTLE); 155 156 // WHEN we update the section headers 157 mSectionsManager.updateSectionBoundaries(); 158 159 // THEN a LO section header is added 160 verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3); 161 } 162 163 @Test testRemoveHeader()164 public void testRemoveHeader() { 165 // GIVEN a stack that originally had a header between the HI and LO sections 166 setStackState( 167 ALERTING, 168 ALERTING, 169 GENTLE); 170 mSectionsManager.updateSectionBoundaries(); 171 172 // WHEN the last LO row is replaced with a HI row 173 setStackState( 174 ALERTING, 175 ALERTING, 176 GENTLE_HEADER, 177 ALERTING); 178 clearInvocations(mNssl); 179 mSectionsManager.updateSectionBoundaries(); 180 181 // THEN the LO section header is removed 182 verify(mNssl).removeView(mSectionsManager.getSilentHeaderView()); 183 } 184 185 @Test testDoNothingIfHeaderAlreadyRemoved()186 public void testDoNothingIfHeaderAlreadyRemoved() { 187 // GIVEN a stack with only HI rows 188 setStackState( 189 ALERTING, 190 ALERTING, 191 ALERTING); 192 193 // WHEN we update the sections headers 194 mSectionsManager.updateSectionBoundaries(); 195 196 // THEN we don't add any section headers 197 verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt()); 198 } 199 200 @Test testMoveHeaderForward()201 public void testMoveHeaderForward() { 202 // GIVEN a stack that originally had a header between the HI and LO sections 203 setStackState( 204 ALERTING, 205 ALERTING, 206 ALERTING, 207 GENTLE); 208 mSectionsManager.updateSectionBoundaries(); 209 210 // WHEN the LO section moves forward 211 setStackState( 212 ALERTING, 213 ALERTING, 214 GENTLE, 215 GENTLE_HEADER, 216 GENTLE); 217 mSectionsManager.updateSectionBoundaries(); 218 219 // THEN the LO section header is also moved forward 220 verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2); 221 } 222 223 @Test testMoveHeaderBackward()224 public void testMoveHeaderBackward() { 225 // GIVEN a stack that originally had a header between the HI and LO sections 226 setStackState( 227 ALERTING, 228 GENTLE, 229 GENTLE, 230 GENTLE); 231 mSectionsManager.updateSectionBoundaries(); 232 233 // WHEN the LO section moves backward 234 setStackState( 235 ALERTING, 236 GENTLE_HEADER, 237 ALERTING, 238 ALERTING, 239 GENTLE); 240 mSectionsManager.updateSectionBoundaries(); 241 242 // THEN the LO section header is also moved backward (with appropriate index shifting) 243 verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3); 244 } 245 246 @Test testHeaderRemovedFromTransientParent()247 public void testHeaderRemovedFromTransientParent() { 248 // GIVEN a stack where the header is animating away 249 setStackState( 250 ALERTING, 251 GENTLE_HEADER); 252 mSectionsManager.updateSectionBoundaries(); 253 clearInvocations(mNssl); 254 255 SectionHeaderView silentHeaderView = mSectionsManager.getSilentHeaderView(); 256 ViewGroup transientParent = mock(ViewGroup.class); 257 when(silentHeaderView.getTransientContainer()).thenReturn(transientParent); 258 259 // WHEN the LO section reappears 260 setStackState( 261 ALERTING, 262 GENTLE); 263 mSectionsManager.updateSectionBoundaries(); 264 265 // THEN the header is first removed from the transient parent before being added to the 266 // NSSL. 267 verify(transientParent).removeTransientView(silentHeaderView); 268 verify(mNssl).addView(silentHeaderView, 1); 269 } 270 271 @Test testHeaderNotShownOnLockscreen()272 public void testHeaderNotShownOnLockscreen() { 273 // GIVEN a stack of HI and LO notifs on the lockscreen 274 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 275 setStackState( 276 ALERTING, 277 ALERTING, 278 ALERTING, 279 GENTLE); 280 281 // WHEN we update the section headers 282 mSectionsManager.updateSectionBoundaries(); 283 284 // Then the section header is not added 285 verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt()); 286 } 287 288 @Test testHeaderShownWhenEnterLockscreen()289 public void testHeaderShownWhenEnterLockscreen() { 290 // GIVEN a stack of HI and LO notifs on the lockscreen 291 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 292 setStackState( 293 ALERTING, 294 ALERTING, 295 ALERTING, 296 GENTLE); 297 mSectionsManager.updateSectionBoundaries(); 298 299 // WHEN we unlock 300 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); 301 mSectionsManager.updateSectionBoundaries(); 302 303 // Then the section header is added 304 verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3); 305 } 306 307 @Test testHeaderHiddenWhenEnterLockscreen()308 public void testHeaderHiddenWhenEnterLockscreen() { 309 // GIVEN a stack of HI and LO notifs on the shade 310 setStackState( 311 ALERTING, 312 GENTLE_HEADER, 313 GENTLE); 314 315 // WHEN we go back to the keyguard 316 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 317 mSectionsManager.updateSectionBoundaries(); 318 319 // Then the section header is removed 320 verify(mNssl).removeView(mSectionsManager.getSilentHeaderView()); 321 } 322 323 @Test testPeopleFiltering_onlyAddSilentHeader()324 public void testPeopleFiltering_onlyAddSilentHeader() { 325 enablePeopleFiltering(); 326 327 setStackState( 328 PERSON, 329 ALERTING, 330 GENTLE); 331 mSectionsManager.updateSectionBoundaries(); 332 333 verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2); 334 } 335 336 @Test testPeopleFiltering_AlertingHunWhilePeopleVisible()337 public void testPeopleFiltering_AlertingHunWhilePeopleVisible() { 338 enablePeopleFiltering(); 339 340 setupMockStack( 341 PEOPLE_HEADER, 342 ALERTING, 343 PERSON, 344 ALERTING_HEADER, 345 GENTLE_HEADER, 346 GENTLE 347 ); 348 mSectionsManager.updateSectionBoundaries(); 349 350 verifyMockStack( 351 ChildType.HEADS_UP, 352 ChildType.PERSON, 353 ChildType.GENTLE_HEADER, 354 ChildType.GENTLE 355 ); 356 } 357 358 @Test testPeopleFiltering_PersonHunWhileAlertingHunVisible()359 public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() { 360 enablePeopleFiltering(); 361 362 setupMockStack( 363 PERSON, 364 INCOMING_HEADER, 365 ALERTING, 366 PEOPLE_HEADER, 367 PERSON 368 ); 369 mSectionsManager.updateSectionBoundaries(); 370 371 verifyMockStack( 372 ChildType.HEADS_UP, 373 ChildType.HEADS_UP, 374 ChildType.PERSON 375 ); 376 } 377 378 @Test testPeopleFiltering_PersonHun()379 public void testPeopleFiltering_PersonHun() { 380 enablePeopleFiltering(); 381 382 setupMockStack( 383 PERSON, 384 PEOPLE_HEADER, 385 PERSON 386 ); 387 mSectionsManager.updateSectionBoundaries(); 388 389 verifyMockStack( 390 ChildType.PERSON, 391 ChildType.PERSON 392 ); 393 } 394 395 @Test testPeopleFiltering_AlertingHunWhilePersonHunning()396 public void testPeopleFiltering_AlertingHunWhilePersonHunning() { 397 enablePeopleFiltering(); 398 399 setupMockStack( 400 ALERTING, 401 PERSON 402 ); 403 mSectionsManager.updateSectionBoundaries(); 404 verifyMockStack( 405 ChildType.HEADS_UP, 406 ChildType.PERSON 407 ); 408 } 409 410 @Test testPeopleFiltering_Fsn()411 public void testPeopleFiltering_Fsn() { 412 enablePeopleFiltering(); 413 414 setupMockStack( 415 INCOMING_HEADER, 416 ALERTING, 417 PEOPLE_HEADER, 418 FSN, 419 PERSON, 420 ALERTING, 421 GENTLE 422 ); 423 mSectionsManager.updateSectionBoundaries(); 424 425 verifyMockStack( 426 ChildType.HEADS_UP, 427 ChildType.FSN, 428 ChildType.PERSON, 429 ChildType.ALERTING, 430 ChildType.GENTLE_HEADER, 431 ChildType.GENTLE 432 ); 433 } 434 435 @Test testMediaControls_AddWhenEnterKeyguard()436 public void testMediaControls_AddWhenEnterKeyguard() { 437 enableMediaControls(); 438 439 // GIVEN a stack that doesn't include media controls 440 setStackState(ALERTING, GENTLE_HEADER, GENTLE); 441 442 // WHEN we go back to the keyguard 443 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 444 mSectionsManager.updateSectionBoundaries(); 445 446 // Then the media controls are added 447 verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0); 448 } 449 450 @Test testMediaControls_AddWhenEnterKeyguardWithHeadsUp()451 public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() { 452 enableMediaControls(); 453 454 // GIVEN a stack that doesn't include media 455 setupMockStack( 456 ALERTING, 457 ALERTING, 458 GENTLE_HEADER, 459 GENTLE); 460 461 // WHEN we go back to the keyguard 462 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); 463 mSectionsManager.updateSectionBoundaries(); 464 465 verifyMockStack( 466 ChildType.MEDIA_CONTROLS, 467 ChildType.ALERTING, 468 ChildType.ALERTING, 469 ChildType.GENTLE); 470 } 471 472 @Test testRemoveNonSilentHeader()473 public void testRemoveNonSilentHeader() { 474 enablePeopleFiltering(); 475 enableMediaControls(); 476 477 setupMockStack( 478 MEDIA_CONTROLS, 479 INCOMING_HEADER, 480 PERSON, 481 ALERTING, 482 PEOPLE_HEADER, 483 ALERTING_HEADER, 484 ALERTING, 485 ALERTING, 486 GENTLE_HEADER, 487 GENTLE, 488 GENTLE 489 ); 490 491 mSectionsManager.updateSectionBoundaries(); 492 493 verifyMockStack( 494 ChildType.MEDIA_CONTROLS, 495 ChildType.PERSON, 496 ChildType.ALERTING, 497 ChildType.ALERTING, 498 ChildType.ALERTING, 499 ChildType.GENTLE_HEADER, 500 ChildType.GENTLE, 501 ChildType.GENTLE 502 ); 503 } 504 505 @Test testExpandIncomingSection()506 public void testExpandIncomingSection() { 507 enablePeopleFiltering(); 508 509 setupMockStack( 510 INCOMING_HEADER, 511 PERSON, 512 ALERTING, 513 PEOPLE_HEADER, 514 ALERTING, 515 PERSON, 516 ALERTING_HEADER, 517 ALERTING 518 ); 519 520 mSectionsManager.updateSectionBoundaries(); 521 522 verifyMockStack( 523 ChildType.HEADS_UP, 524 ChildType.HEADS_UP, 525 ChildType.HEADS_UP, 526 ChildType.PERSON, 527 ChildType.ALERTING 528 ); 529 } 530 531 @Test testIgnoreGoneView()532 public void testIgnoreGoneView() { 533 enablePeopleFiltering(); 534 535 setupMockStack( 536 PERSON.gone(), 537 ALERTING, 538 GENTLE 539 ); 540 541 mSectionsManager.updateSectionBoundaries(); 542 543 verifyMockStack( 544 ChildType.PERSON, 545 ChildType.ALERTING, 546 ChildType.GENTLE_HEADER, 547 ChildType.GENTLE 548 ); 549 } 550 enablePeopleFiltering()551 private void enablePeopleFiltering() { 552 when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true); 553 } 554 enableMediaControls()555 private void enableMediaControls() { 556 when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true); 557 } 558 559 private enum ChildType { 560 INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, 561 FSN, PERSON, ALERTING, GENTLE, OTHER 562 } 563 setStackState(StackEntry... children)564 private void setStackState(StackEntry... children) { 565 when(mNssl.getChildCount()).thenReturn(children.length); 566 for (int i = 0; i < children.length; i++) { 567 View child; 568 StackEntry entry = children[i]; 569 switch (entry.mChildType) { 570 case INCOMING_HEADER: 571 child = mSectionsManager.getIncomingHeaderView(); 572 break; 573 case MEDIA_CONTROLS: 574 child = mSectionsManager.getMediaControlsView(); 575 break; 576 case PEOPLE_HEADER: 577 child = mSectionsManager.getPeopleHeaderView(); 578 break; 579 case ALERTING_HEADER: 580 child = mSectionsManager.getAlertingHeaderView(); 581 break; 582 case GENTLE_HEADER: 583 child = mSectionsManager.getSilentHeaderView(); 584 break; 585 case FSN: 586 child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone); 587 break; 588 case PERSON: 589 child = mockNotification(BUCKET_PEOPLE, entry.mIsGone); 590 break; 591 case ALERTING: 592 child = mockNotification(BUCKET_ALERTING, entry.mIsGone); 593 break; 594 case GENTLE: 595 child = mockNotification(BUCKET_SILENT, entry.mIsGone); 596 break; 597 case OTHER: 598 child = mock(View.class); 599 when(child.getVisibility()).thenReturn(View.VISIBLE); 600 when(child.getParent()).thenReturn(mNssl); 601 break; 602 default: 603 throw new RuntimeException("Unknown ChildType: " + children[i]); 604 } 605 when(mNssl.getChildAt(i)).thenReturn(child); 606 when(mNssl.indexOfChild(child)).thenReturn(i); 607 } 608 } 609 mockNotification(@riorityBucket int bucket, boolean isGone)610 private View mockNotification(@PriorityBucket int bucket, boolean isGone) { 611 ExpandableNotificationRow notifRow = 612 mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); 613 when(notifRow.getVisibility()).thenReturn(View.VISIBLE); 614 when(notifRow.getParent()).thenReturn(mNssl); 615 616 NotificationEntry mockEntry = mock(NotificationEntry.class); 617 when(notifRow.getEntry()).thenReturn(mockEntry); 618 619 int[] bucketRef = new int[] { bucket }; 620 when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]); 621 doAnswer(invocation -> { 622 bucketRef[0] = invocation.getArgument(0); 623 return null; 624 }).when(mockEntry).setBucket(anyInt()); 625 626 when(notifRow.getVisibility()).thenReturn(isGone ? View.GONE : View.VISIBLE); 627 return notifRow; 628 } 629 verifyMockStack(ChildType... expected)630 private void verifyMockStack(ChildType... expected) { 631 final List<ChildType> actual = new ArrayList<>(); 632 int childCount = mNssl.getChildCount(); 633 for (int i = 0; i < childCount; i++) { 634 View child = mNssl.getChildAt(i); 635 if (child == mSectionsManager.getIncomingHeaderView()) { 636 actual.add(ChildType.INCOMING_HEADER); 637 continue; 638 } 639 if (child == mSectionsManager.getMediaControlsView()) { 640 actual.add(ChildType.MEDIA_CONTROLS); 641 continue; 642 } 643 if (child == mSectionsManager.getPeopleHeaderView()) { 644 actual.add(ChildType.PEOPLE_HEADER); 645 continue; 646 } 647 if (child == mSectionsManager.getAlertingHeaderView()) { 648 actual.add(ChildType.ALERTING_HEADER); 649 continue; 650 } 651 if (child == mSectionsManager.getSilentHeaderView()) { 652 actual.add(ChildType.GENTLE_HEADER); 653 continue; 654 } 655 if (child instanceof ExpandableNotificationRow) { 656 switch (((ExpandableNotificationRow) child).getEntry().getBucket()) { 657 case BUCKET_HEADS_UP: 658 actual.add(ChildType.HEADS_UP); 659 break; 660 case BUCKET_FOREGROUND_SERVICE: 661 actual.add(ChildType.FSN); 662 break; 663 case BUCKET_PEOPLE: 664 actual.add(ChildType.PERSON); 665 break; 666 case BUCKET_ALERTING: 667 actual.add(ChildType.ALERTING); 668 break; 669 case BUCKET_SILENT: 670 actual.add(ChildType.GENTLE); 671 break; 672 default: 673 actual.add(ChildType.OTHER); 674 break; 675 } 676 continue; 677 } 678 actual.add(ChildType.OTHER); 679 } 680 assertThat(actual).containsExactly((Object[]) expected).inOrder(); 681 } 682 setupMockStack(StackEntry... entries)683 private void setupMockStack(StackEntry... entries) { 684 final List<View> children = new ArrayList<>(); 685 when(mNssl.getChildCount()).thenAnswer(invocation -> children.size()); 686 when(mNssl.getChildAt(anyInt())) 687 .thenAnswer(invocation -> { 688 Integer index = invocation.getArgument(0); 689 if (index == null || index < 0 || index >= children.size()) { 690 return null; 691 } 692 return children.get(index); 693 }); 694 when(mNssl.indexOfChild(any())) 695 .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0))); 696 doAnswer(invocation -> { 697 View child = invocation.getArgument(0); 698 int index = invocation.getArgument(1); 699 children.add(index, child); 700 return null; 701 }).when(mNssl).addView(any(), anyInt()); 702 doAnswer(invocation -> { 703 View child = invocation.getArgument(0); 704 children.remove(child); 705 return null; 706 }).when(mNssl).removeView(any()); 707 doAnswer(invocation -> { 708 View child = invocation.getArgument(0); 709 int newIndex = invocation.getArgument(1); 710 children.remove(child); 711 children.add(newIndex, child); 712 return null; 713 }).when(mNssl).changeViewPosition(any(), anyInt()); 714 for (StackEntry entry : entries) { 715 View child; 716 switch (entry.mChildType) { 717 case INCOMING_HEADER: 718 child = mSectionsManager.getIncomingHeaderView(); 719 break; 720 case MEDIA_CONTROLS: 721 child = mSectionsManager.getMediaControlsView(); 722 break; 723 case PEOPLE_HEADER: 724 child = mSectionsManager.getPeopleHeaderView(); 725 break; 726 case ALERTING_HEADER: 727 child = mSectionsManager.getAlertingHeaderView(); 728 break; 729 case GENTLE_HEADER: 730 child = mSectionsManager.getSilentHeaderView(); 731 break; 732 case FSN: 733 child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone); 734 break; 735 case PERSON: 736 child = mockNotification(BUCKET_PEOPLE, entry.mIsGone); 737 break; 738 case ALERTING: 739 child = mockNotification(BUCKET_ALERTING, entry.mIsGone); 740 break; 741 case GENTLE: 742 child = mockNotification(BUCKET_SILENT, entry.mIsGone); 743 break; 744 case OTHER: 745 child = mock(View.class); 746 when(child.getVisibility()).thenReturn(View.VISIBLE); 747 when(child.getParent()).thenReturn(mNssl); 748 break; 749 default: 750 throw new RuntimeException("Unknown ChildType: " + entry.mChildType); 751 } 752 children.add(child); 753 } 754 } 755 756 private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER); 757 private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS); 758 private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER); 759 private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER); 760 private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER); 761 private static final StackEntry FSN = new StackEntry(ChildType.FSN); 762 private static final StackEntry PERSON = new StackEntry(ChildType.PERSON); 763 private static final StackEntry ALERTING = new StackEntry(ChildType.ALERTING); 764 private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE); 765 766 private static class StackEntry { 767 final ChildType mChildType; 768 final boolean mIsGone; 769 StackEntry(ChildType childType)770 StackEntry(ChildType childType) { 771 this(childType, false); 772 } 773 StackEntry(ChildType childType, boolean isGone)774 StackEntry(ChildType childType, boolean isGone) { 775 mChildType = childType; 776 mIsGone = isGone; 777 } 778 gone()779 public StackEntry gone() { 780 return new StackEntry(mChildType, true); 781 } 782 } 783 } 784