package com.android.systemui.statusbar.notification.stack import android.os.UserHandle import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.service.notification.StatusBarNotification import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.andSceneContainer import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.shared.NotificationMinimalism import com.android.systemui.statusbar.notification.shelf.NotificationShelfIconContainer import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters /** Tests for {@link NotificationShelf}. */ @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper open class NotificationShelfTest(flags: FlagsParameterization) : SysuiTestCase() { private val flags = FakeFeatureFlags() @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var hostLayoutController: NotificationStackScrollLayoutController @Mock private lateinit var hostLayout: NotificationStackScrollLayout @Mock private lateinit var roundnessManager: NotificationRoundnessManager private lateinit var shelf: NotificationShelf companion object { @JvmStatic @Parameters(name = "{0}") fun getParams(): List { return FlagsParameterization.allCombinationsOf().andSceneContainer() } } init { mSetFlagsRule.setFlagsParameterization(flags) } @Before fun setUp() { MockitoAnnotations.initMocks(this) mDependency.injectTestDependency(FeatureFlags::class.java, flags) val root = FrameLayout(context) shelf = LayoutInflater.from(root.context) .inflate( /* resource = */ R.layout.status_bar_notification_shelf, /* root = */ root, /* attachToRoot = */ false, ) as NotificationShelf whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator) whenever(ambientState.isSmallScreen).thenReturn(true) shelf.bind(ambientState, hostLayout, roundnessManager) shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */ 5) } @Test fun testX_inViewForClick() { val isXInView = shelf.isXInView(/* localX */ 5f, /* slop */ 5f, /* left */ 0f, /* right */ 10f) assertTrue(isXInView) } @Test fun testXSlop_inViewForClick() { val isLeftXSlopInView = shelf.isXInView(/* localX */ -3f, /* slop */ 5f, /* left */ 0f, /* right */ 10f) assertTrue(isLeftXSlopInView) val isRightXSlopInView = shelf.isXInView(/* localX */ 13f, /* slop */ 5f, /* left */ 0f, /* right */ 10f) assertTrue(isRightXSlopInView) } @Test fun testX_notInViewForClick() { val isXLeftOfShelfInView = shelf.isXInView(/* localX */ -10f, /* slop */ 5f, /* left */ 0f, /* right */ 10f) assertFalse(isXLeftOfShelfInView) val isXRightOfShelfInView = shelf.isXInView(/* localX */ 20f, /* slop */ 5f, /* left */ 0f, /* right */ 10f) assertFalse(isXRightOfShelfInView) } @Test fun testY_inViewForClick() { val isYInView = shelf.isYInView(/* localY */ 5f, /* slop */ 5f, /* top */ 0f, /* bottom */ 10f) assertTrue(isYInView) } @Test fun testYSlop_inViewForClick() { val isTopYSlopInView = shelf.isYInView(/* localY */ -3f, /* slop */ 5f, /* top */ 0f, /* bottom */ 10f) assertTrue(isTopYSlopInView) val isBottomYSlopInView = shelf.isYInView(/* localY */ 13f, /* slop */ 5f, /* top */ 0f, /* bottom */ 10f) assertTrue(isBottomYSlopInView) } @Test fun testY_notInViewForClick() { val isYAboveShelfInView = shelf.isYInView(/* localY */ -10f, /* slop */ 5f, /* top */ 0f, /* bottom */ 5f) assertFalse(isYAboveShelfInView) val isYBelowShelfInView = shelf.isYInView(/* localY */ 15f, /* slop */ 5f, /* top */ 0f, /* bottom */ 5f) assertFalse(isYBelowShelfInView) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testAlignment_splitShade_LTR() { // Given: LTR mode, split shade val width = 100 val actualWidth = 40 val iconContainerPadding = 16f val shelfSpy = prepareShelfSpy( shelf, rtl = false, splitShade = true, width = width, actualWidth = actualWidth, iconContainerPadding = iconContainerPadding, ) // Then: shelf should align to end assertTrue(shelfSpy.isAlignedToEnd) assertTrue(shelfSpy.isAlignedToRight) assertTrue(shelfSpy.mBackgroundNormal.alignToEnd) // Then: icon container should align to end, right val iconContainer = shelfSpy.shelfIcons as NotificationShelfIconContainer assertTrue(iconContainer.alignToEnd) assertTrue(iconContainer.isAlignedToRight) // Then: icon container bounds are updated based on the widths and paddings val actualPaddingStart = iconContainerPadding val actualPaddingEnd = iconContainerPadding val expectedLeftBound = width - actualWidth + actualPaddingStart val expectedRightBound = width - actualPaddingEnd assertEquals(expectedLeftBound, iconContainer.leftBound) assertEquals(expectedRightBound, iconContainer.rightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testAlignment_nonSplitShade_LTR() { // Given: LTR mode, non split shade val width = 100 val actualWidth = 40 val iconContainerPadding = 16f val shelfSpy = prepareShelfSpy( shelf, rtl = false, splitShade = false, width = width, actualWidth = actualWidth, iconContainerPadding = iconContainerPadding, ) // Then: shelf should not align to end // left bound of icon container should be 16f (actualPaddingStart) // right bound of icon container should be 24f (actualWidth - actualPaddingEnd) assertFalse(shelfSpy.isAlignedToEnd) assertFalse(shelfSpy.isAlignedToRight) assertFalse(shelfSpy.mBackgroundNormal.alignToEnd) // Then: icon container should align to start, left val iconContainer = shelfSpy.shelfIcons as NotificationShelfIconContainer assertFalse(iconContainer.alignToEnd) assertFalse(iconContainer.isAlignedToRight) // Then: icon container bounds are updated based on the widths and paddings val actualPaddingStart = iconContainerPadding val actualPaddingEnd = iconContainerPadding val expectedLeftBound = actualPaddingStart val expectedRightBound = actualWidth - actualPaddingEnd assertEquals(expectedLeftBound, iconContainer.leftBound) assertEquals(expectedRightBound, iconContainer.rightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testAlignment_splitShade_RTL() { // Given: RTL mode, split shade val width = 100 val actualWidth = 40 val iconContainerPadding = 16f val shelfSpy = prepareShelfSpy( shelf, rtl = true, splitShade = true, width = width, actualWidth = actualWidth, iconContainerPadding = iconContainerPadding, ) // Then: shelf should align to end, but to left due to RTL // left bound of icon container should be 16f (actualPaddingStart) // right bound of icon container should be 24f (actualWidth - actualPaddingEnd) assertTrue(shelfSpy.isAlignedToEnd) assertFalse(shelfSpy.isAlignedToRight) assertTrue(shelfSpy.mBackgroundNormal.alignToEnd) // Then: icon container should align to end, left val iconContainer = shelfSpy.shelfIcons as NotificationShelfIconContainer assertTrue(iconContainer.alignToEnd) assertFalse(iconContainer.isAlignedToRight) // Then: icon container bounds are updated based on the widths and paddings val actualPaddingStart = iconContainerPadding val actualPaddingEnd = iconContainerPadding val expectedLeftBound = actualPaddingStart val expectedRightBound = actualWidth - actualPaddingEnd assertEquals(expectedLeftBound, iconContainer.leftBound) assertEquals(expectedRightBound, iconContainer.rightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testAlignment_nonSplitShade_RTL() { // Given: RTL mode, non split shade val width = 100 val actualWidth = 40 val iconContainerPadding = 16f val shelfSpy = prepareShelfSpy( shelf, rtl = true, splitShade = false, width = width, actualWidth = actualWidth, iconContainerPadding = iconContainerPadding, ) // Then: shelf should not align to end, but to right due to RTL assertFalse(shelfSpy.isAlignedToEnd) assertTrue(shelfSpy.isAlignedToRight) assertFalse(shelfSpy.mBackgroundNormal.alignToEnd) // Then: icon container should align to start, right val iconContainer = shelfSpy.shelfIcons as NotificationShelfIconContainer assertFalse(iconContainer.alignToEnd) assertTrue(iconContainer.isAlignedToRight) // Then: icon container bounds are updated based on the widths and paddings val actualPaddingStart = iconContainerPadding val actualPaddingEnd = iconContainerPadding val expectedLeftBound = width - actualWidth + actualPaddingStart val expectedRightBound = width - actualPaddingEnd assertEquals(expectedLeftBound, iconContainer.leftBound) assertEquals(expectedRightBound, iconContainer.rightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfLeftBound_splitShade_LTR() { // Given: LTR mode, split shade val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = false, splitShade = true, width = width, actualWidth = actualWidth, ) // When: get the left bound of the shelf val shelfLeftBound = shelfSpy.shelfLeftBound // Then: should be equal to shelf's width - actual width val expectedLeftBound = (width - actualWidth).toFloat() assertEquals(expectedLeftBound, shelfLeftBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfRightBound_splitShade_LTR() { // Given: LTR mode, split shade, width 100, actual width 40 val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = false, splitShade = true, width = width, actualWidth = actualWidth, ) // Then: the right bound of the shelf should be equal to shelf's width val expectedRightBound = width.toFloat() assertEquals(expectedRightBound, shelfSpy.shelfRightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfLeftBound_nonSplitShade_LTR() { // Given: LTR mode, non split shade val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = false, splitShade = false, width = width, actualWidth = actualWidth, ) // When: get the left bound of the shelf val shelfLeftBound = shelfSpy.shelfLeftBound // Then: should be equal to 0f assertEquals(0f, shelfLeftBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfRightBound_nonSplitShade_LTR() { // Given: LTR mode, non split shade, width 100, actual width 40 val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = false, splitShade = false, width = width, actualWidth = actualWidth, ) // Then: the right bound of the shelf should be equal to shelf's actual width assertEquals(actualWidth.toFloat(), shelfSpy.shelfRightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfLeftBound_splitShade_RTL() { // Given: RTL mode, split shade val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = true, splitShade = true, width = width, actualWidth = actualWidth, ) // When: get the left bound of the shelf val shelfLeftBound = shelfSpy.shelfLeftBound // Then: should be equal to 0f assertEquals(0f, shelfLeftBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfRightBound_splitShade_RTL() { // Given: RTL mode, split shade, width 100, actual width 40 val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = true, splitShade = true, width = width, actualWidth = actualWidth, ) // Then: the right bound of the shelf should be equal to shelf's actual width assertEquals(actualWidth.toFloat(), shelfSpy.shelfRightBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfLeftBound_nonSplitShade_RTL() { // Given: RTL mode, non split shade val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = true, splitShade = false, width = width, actualWidth = actualWidth, ) // When: get the left bound of the shelf val shelfLeftBound = shelfSpy.shelfLeftBound // Then: should be equal to shelf's width - actual width val expectedLeftBound = (width - actualWidth).toFloat() assertEquals(expectedLeftBound, shelfLeftBound) } @Test @EnableFlags(NotificationMinimalism.FLAG_NAME) fun testGetShelfRightBound_nonSplitShade_RTL() { // Given: LTR mode, non split shade, width 100, actual width 40 val width = 100 val actualWidth = 40 val shelfSpy = prepareShelfSpy( shelf, rtl = true, splitShade = false, width = width, actualWidth = actualWidth, ) // Then: the right bound of the shelf should be equal to shelf's width assertEquals(width.toFloat(), shelfSpy.shelfRightBound) } private fun prepareShelfSpy( shelf: NotificationShelf, rtl: Boolean, splitShade: Boolean, width: Int, actualWidth: Int, iconContainerPadding: Float? = null, ): NotificationShelf { val shelfSpy = spy(shelf) whenever(shelfSpy.isLayoutRtl).thenReturn(rtl) whenever(ambientState.useSplitShade).thenReturn(splitShade) // Ensure mAlignedToEnd is set when SceneContainerFlag is enabled. if (SceneContainerFlag.isEnabled) { shelfSpy.setAlignedToEnd(splitShade) } shelfSpy.layout(0, 0, width, 5) shelfSpy.mShelfIcons.layout(0, 0, width, 5) iconContainerPadding?.let { shelfSpy.mShelfIcons.actualPaddingStart = it shelfSpy.mShelfIcons.setActualPaddingEnd(it) } shelfSpy.setActualWidth(actualWidth.toFloat()) val iconContainerSpy = spy(shelf.mShelfIcons) whenever(iconContainerSpy.isLayoutRtl).thenReturn(rtl) whenever(shelfSpy.shelfIcons).thenReturn(iconContainerSpy) return shelfSpy } @Test fun getAmountInShelf_lastViewBelowShelf_completelyInShelf() { val shelfClipStart = 0f val viewStart = 1f val expandableView = mock(ExpandableView::class.java) whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(expandableView.translationY).thenReturn(viewStart) whenever(expandableView.actualHeight).thenReturn(20) whenever(expandableView.minHeight).thenReturn(20) whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY whenever(expandableView.isInShelf).thenReturn(true) whenever(ambientState.isOnKeyguard).thenReturn(true) whenever(ambientState.isExpansionChanging).thenReturn(false) whenever(ambientState.isShadeExpanded).thenReturn(true) val amountInShelf = shelf.getAmountInShelf( /* i= */ 0, /* view= */ expandableView, /* scrollingFast= */ false, /* expandingAnimated= */ false, /* isLastChild= */ true, shelfClipStart, ) assertEquals(1f, amountInShelf) } @Test fun getAmountInShelf_lastViewAlmostBelowShelf_completelyInShelf() { val viewStart = 0f val shelfClipStart = 0.001f val expandableView = mock(ExpandableView::class.java) whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(expandableView.translationY).thenReturn(viewStart) whenever(expandableView.actualHeight).thenReturn(20) whenever(expandableView.minHeight).thenReturn(20) whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY whenever(expandableView.isInShelf).thenReturn(true) whenever(ambientState.isOnKeyguard).thenReturn(true) whenever(ambientState.isExpansionChanging).thenReturn(false) whenever(ambientState.isShadeExpanded).thenReturn(true) val amountInShelf = shelf.getAmountInShelf( /* i= */ 0, /* view= */ expandableView, /* scrollingFast= */ false, /* expandingAnimated= */ false, /* isLastChild= */ true, shelfClipStart, ) assertEquals(1f, amountInShelf) } @Test fun getAmountInShelf_lastViewHalfClippedByShelf_halfInShelf() { val viewStart = 0f val shelfClipStart = 10f val expandableView = mock(ExpandableView::class.java) whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(expandableView.translationY).thenReturn(viewStart) whenever(expandableView.actualHeight).thenReturn(25) whenever(expandableView.minHeight).thenReturn(25) whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY whenever(expandableView.isInShelf).thenReturn(false) whenever(ambientState.isOnKeyguard).thenReturn(true) whenever(ambientState.isExpansionChanging).thenReturn(false) whenever(ambientState.isShadeExpanded).thenReturn(true) val amountInShelf = shelf.getAmountInShelf( /* i= */ 0, /* view= */ expandableView, /* scrollingFast= */ false, /* expandingAnimated= */ false, /* isLastChild= */ true, shelfClipStart, ) assertEquals(0.5f, amountInShelf) } @Test fun getAmountInShelf_lastViewAboveShelf_notInShelf() { val viewStart = 0f val shelfClipStart = 15f val expandableView = mock(ExpandableView::class.java) whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(expandableView.translationY).thenReturn(viewStart) whenever(expandableView.actualHeight).thenReturn(10) whenever(expandableView.minHeight).thenReturn(10) whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY whenever(expandableView.isInShelf).thenReturn(false) whenever(ambientState.isExpansionChanging).thenReturn(false) whenever(ambientState.isOnKeyguard).thenReturn(true) val amountInShelf = shelf.getAmountInShelf( /* i= */ 0, /* view= */ expandableView, /* scrollingFast= */ false, /* expandingAnimated= */ false, /* isLastChild= */ true, shelfClipStart, ) assertEquals(0f, amountInShelf) } @Test fun updateState_expansionChanging_shelfTransparent() { updateState_expansionChanging_shelfAlphaUpdated( expansionFraction = 0.25f, expectedAlpha = 0.0f, ) } @Test fun updateState_expansionChangingWhileBouncerInTransit_shelfTransparent() { whenever(ambientState.isBouncerInTransit).thenReturn(true) updateState_expansionChanging_shelfAlphaUpdated( expansionFraction = 0.85f, expectedAlpha = 0.0f, ) } @Test fun updateState_expansionChanging_shelfAlphaUpdated() { updateState_expansionChanging_shelfAlphaUpdated( expansionFraction = 0.6f, expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f), ) } @Test fun updateState_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() { val expansionFraction = 0.6f whenever(ambientState.isSmallScreen).thenReturn(false) whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction)) .thenReturn(0.123f) updateState_expansionChanging_shelfAlphaUpdated( expansionFraction = expansionFraction, expectedAlpha = 0.123f, ) } @Test fun updateState_expansionChangingWhileBouncerInTransit_shelfAlphaUpdated() { whenever(ambientState.isBouncerInTransit).thenReturn(true) updateState_expansionChanging_shelfAlphaUpdated( expansionFraction = 0.95f, expectedAlpha = aboutToShowBouncerProgress(0.95f), ) } @Test fun updateState_largeScreen_expansionChangingWhileBouncerInTransit_bouncerInterpolatorUsed() { whenever(ambientState.isBouncerInTransit).thenReturn(true) updateState_expansionChanging_shelfAlphaUpdated( expansionFraction = 0.95f, expectedAlpha = aboutToShowBouncerProgress(0.95f), ) } @Test fun updateState_lastViewAlmostBelowShelf_completelyInShelf() { val viewStart = 0f val shelfClipStart = 0.001f val expandableView = mock(ExpandableView::class.java) whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(expandableView.translationY).thenReturn(viewStart) whenever(expandableView.actualHeight).thenReturn(20) whenever(expandableView.minHeight).thenReturn(20) whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY whenever(expandableView.isInShelf).thenReturn(true) whenever(ambientState.isOnKeyguard).thenReturn(true) whenever(ambientState.isExpansionChanging).thenReturn(false) whenever(ambientState.isShadeExpanded).thenReturn(true) val amountInShelf = shelf.getAmountInShelf( /* i= */ 0, /* view= */ expandableView, /* scrollingFast= */ false, /* expandingAnimated= */ false, /* isLastChild= */ true, shelfClipStart, ) assertEquals(1f, amountInShelf) } @Test @EnableSceneContainer fun updateState_withViewInShelf_notPulsing_showShelf() { // GIVEN a view is scrolled into the shelf, no pulsing currently val stackTop = 200f val stackHeight = 800f whenever(ambientState.stackTop).thenReturn(stackTop) whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight) val shelfTop = stackTop + stackHeight - shelf.height val stackScrollAlgorithmState = StackScrollAlgorithmState() val viewInShelf = mock(ExpandableView::class.java) whenever(ambientState.isShadeExpanded).thenReturn(true) whenever(ambientState.lastVisibleBackgroundChild).thenReturn(viewInShelf) whenever(ambientState.isPulsing).thenReturn(false) whenever(viewInShelf.viewState).thenReturn(ExpandableViewState()) whenever(viewInShelf.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(viewInShelf.translationY).thenReturn(shelfTop) whenever(viewInShelf.actualHeight).thenReturn(10) whenever(viewInShelf.isInShelf).thenReturn(true) whenever(viewInShelf.minHeight).thenReturn(10) whenever(viewInShelf.shelfTransformationTarget).thenReturn(null) // use translationY whenever(viewInShelf.isInShelf).thenReturn(true) stackScrollAlgorithmState.visibleChildren.add(viewInShelf) stackScrollAlgorithmState.firstViewInShelf = viewInShelf // WHEN Shelf's ViewState is updated shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN the shelf is visible, and positioned correctly val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(false, shelfState.hidden) assertEquals(shelf.height, shelfState.height) assertEquals(shelfTop, shelfState.yTranslation) } @Test @EnableSceneContainer fun updateState_withViewInShelfAndPulsing_hideShelf() { // GIVEN a view is scrolled into the shelf, and a notification is pulsing val stackTop = 200f val stackHeight = 800f whenever(ambientState.stackTop).thenReturn(stackTop) whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight) val shelfTop = stackTop + stackHeight - shelf.height val stackScrollAlgorithmState = StackScrollAlgorithmState() val viewInShelf = mock(ExpandableView::class.java) whenever(ambientState.isShadeExpanded).thenReturn(true) whenever(ambientState.lastVisibleBackgroundChild).thenReturn(viewInShelf) whenever(ambientState.isPulsing).thenReturn(true) whenever(viewInShelf.viewState).thenReturn(ExpandableViewState()) whenever(viewInShelf.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) whenever(viewInShelf.translationY).thenReturn(shelfTop) whenever(viewInShelf.actualHeight).thenReturn(10) whenever(viewInShelf.isInShelf).thenReturn(true) whenever(viewInShelf.minHeight).thenReturn(10) whenever(viewInShelf.shelfTransformationTarget).thenReturn(null) // use translationY whenever(viewInShelf.isInShelf).thenReturn(true) stackScrollAlgorithmState.visibleChildren.add(viewInShelf) stackScrollAlgorithmState.firstViewInShelf = viewInShelf // WHEN Shelf's ViewState is updated shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN the shelf is hidden val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) } @Test @EnableSceneContainer fun updateState_withNullLastVisibleBackgroundChild_hideShelf_withSceneContainer() { // GIVEN val stackTop = 200f val stackHeight = 800f whenever(ambientState.stackTop).thenReturn(stackTop) whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) whenever(ambientState.isShadeExpanded).thenReturn(true) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) val stackScrollAlgorithmState = StackScrollAlgorithmState() stackScrollAlgorithmState.firstViewInShelf = mock() whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null) // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) } @Test @DisableSceneContainer fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.interpolatedStackHeight).thenReturn(100f) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) val endOfStack = 200f + paddingBetweenElements whenever(ambientState.isShadeExpanded).thenReturn(true) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) val stackScrollAlgorithmState = StackScrollAlgorithmState() stackScrollAlgorithmState.firstViewInShelf = mock() whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null) // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(endOfStack, shelfState.yTranslation) } @Test @EnableSceneContainer fun updateState_withNullFirstViewInShelf_hideShelf_withSceneContainer() { // GIVEN val stackTop = 200f val stackHeight = 800f whenever(ambientState.stackTop).thenReturn(stackTop) whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) whenever(ambientState.isShadeExpanded).thenReturn(true) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) val stackScrollAlgorithmState = StackScrollAlgorithmState() stackScrollAlgorithmState.firstViewInShelf = null // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) } @Test @DisableSceneContainer fun updateState_withNullFirstViewInShelf_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.interpolatedStackHeight).thenReturn(100f) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) val endOfStack = 200f + paddingBetweenElements whenever(ambientState.isShadeExpanded).thenReturn(true) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) val stackScrollAlgorithmState = StackScrollAlgorithmState() stackScrollAlgorithmState.firstViewInShelf = null // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(endOfStack, shelfState.yTranslation) } @Test @EnableSceneContainer fun updateState_withCollapsedShade_hideShelf_withSceneContainer() { // GIVEN val stackTop = 200f val stackHeight = 800f whenever(ambientState.stackTop).thenReturn(stackTop) whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) val stackScrollAlgorithmState = StackScrollAlgorithmState() stackScrollAlgorithmState.firstViewInShelf = mock() whenever(ambientState.isShadeExpanded).thenReturn(false) // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) } @Test @DisableSceneContainer fun updateState_withCollapsedShade_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.interpolatedStackHeight).thenReturn(100f) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) val endOfStack = 200f + paddingBetweenElements val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) val stackScrollAlgorithmState = StackScrollAlgorithmState() stackScrollAlgorithmState.firstViewInShelf = mock() whenever(ambientState.isShadeExpanded).thenReturn(false) // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(endOfStack, shelfState.yTranslation) } @Test @EnableSceneContainer fun updateState_withHiddenSectionBeforeShelf_hideShelf_withSceneContainer() { // GIVEN val stackTop = 200f val stackHeight = 800f whenever(ambientState.stackTop).thenReturn(stackTop) whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) whenever(ambientState.isShadeExpanded).thenReturn(true) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) val stackScrollAlgorithmState = StackScrollAlgorithmState() whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) val ssaVisibleChild = mock() val ssaVisibleChildState = ExpandableViewState() ssaVisibleChildState.hidden = true whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState) val ssaVisibleChild1 = mock() val ssaVisibleChildState1 = ExpandableViewState() ssaVisibleChildState1.hidden = true whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1) stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild) stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1) whenever(ambientState.isExpansionChanging).thenReturn(true) whenever(ambientState.expansionFraction).thenReturn(1f) stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1 // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(stackTop + stackHeight + paddingBetweenElements, shelfState.yTranslation) } @Test @DisableSceneContainer fun updateState_withHiddenSectionBeforeShelf_hideShelf() { // GIVEN whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.interpolatedStackHeight).thenReturn(100f) val paddingBetweenElements = context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) val endOfStack = 200f + paddingBetweenElements whenever(ambientState.isShadeExpanded).thenReturn(true) val lastVisibleBackgroundChild = mock() val expandableViewState = ExpandableViewState() whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) val stackScrollAlgorithmState = StackScrollAlgorithmState() whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) val ssaVisibleChild = mock() val ssaVisibleChildState = ExpandableViewState() ssaVisibleChildState.hidden = true whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState) val ssaVisibleChild1 = mock() val ssaVisibleChildState1 = ExpandableViewState() ssaVisibleChildState1.hidden = true whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1) stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild) stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1) whenever(ambientState.isExpansionChanging).thenReturn(true) stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1 // WHEN shelf.updateState(stackScrollAlgorithmState, ambientState) // THEN val shelfState = shelf.viewState as NotificationShelf.ShelfState assertEquals(true, shelfState.hidden) assertEquals(endOfStack, shelfState.yTranslation) } private fun setFractionToShade(fraction: Float) { whenever(ambientState.fractionToShade).thenReturn(fraction) } private fun setOnLockscreen(isOnLockscreen: Boolean) { whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen) } private fun updateState_expansionChanging_shelfAlphaUpdated( expansionFraction: Float, expectedAlpha: Float, ) { val sbnMock: StatusBarNotification = mock() val mockEntry = mock().apply { whenever(this.sbn).thenReturn(sbnMock) } val row = when (NotificationBundleUi.isEnabled) { true -> ExpandableNotificationRow(mContext, null, UserHandle.CURRENT) false -> ExpandableNotificationRow(mContext, null, mockEntry) } whenever(ambientState.lastVisibleBackgroundChild).thenReturn(row) whenever(ambientState.isExpansionChanging).thenReturn(true) whenever(ambientState.expansionFraction).thenReturn(expansionFraction) whenever(hostLayoutController.speedBumpIndex).thenReturn(0) shelf.updateState(StackScrollAlgorithmState(), ambientState) assertEquals(expectedAlpha, shelf.viewState.alpha) } }