diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index ea28452d63c4b..ce465bcfb42d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -23,6 +23,8 @@ import android.annotation.Nullable; import android.content.Context; import android.util.MathUtils; +import androidx.annotation.VisibleForTesting; + import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; @@ -137,7 +139,17 @@ public class AmbientState implements Dumpable { * True right after we swipe up on lockscreen and have not finished the fling down that follows. * False when we stop flinging or leave lockscreen. */ - private boolean mNeedFlingAfterLockscreenSwipeUp = false; + private boolean mIsFlingRequiredAfterLockScreenSwipeUp = false; + + @VisibleForTesting + public boolean isFlingRequiredAfterLockScreenSwipeUp() { + return mIsFlingRequiredAfterLockScreenSwipeUp; + } + + @VisibleForTesting + public void setFlingRequiredAfterLockScreenSwipeUp(boolean value) { + mIsFlingRequiredAfterLockScreenSwipeUp = value; + } /** * @return Height of the notifications panel without top padding when expansion completes. @@ -181,7 +193,7 @@ public class AmbientState implements Dumpable { public void setSwipingUp(boolean isSwipingUp) { if (!isSwipingUp && mIsSwipingUp) { // Just stopped swiping up. - mNeedFlingAfterLockscreenSwipeUp = true; + mIsFlingRequiredAfterLockScreenSwipeUp = true; } mIsSwipingUp = isSwipingUp; } @@ -196,10 +208,10 @@ public class AmbientState implements Dumpable { /** * @param isFlinging Whether we are flinging the shade open or closed. */ - public void setIsFlinging(boolean isFlinging) { + public void setFlinging(boolean isFlinging) { if (isOnKeyguard() && !isFlinging && mIsFlinging) { // Just stopped flinging. - mNeedFlingAfterLockscreenSwipeUp = false; + mIsFlingRequiredAfterLockScreenSwipeUp = false; } mIsFlinging = isFlinging; } @@ -508,7 +520,7 @@ public class AmbientState implements Dumpable { public void setStatusBarState(int statusBarState) { if (mStatusBarState != StatusBarState.KEYGUARD) { - mNeedFlingAfterLockscreenSwipeUp = false; + mIsFlingRequiredAfterLockScreenSwipeUp = false; } mStatusBarState = statusBarState; } @@ -576,7 +588,7 @@ public class AmbientState implements Dumpable { * @return Whether we need to do a fling down after swiping up on lockscreen. */ public boolean isFlingingAfterSwipeUpOnLockscreen() { - return mIsFlinging && mNeedFlingAfterLockscreenSwipeUp; + return mIsFlinging && mIsFlingRequiredAfterLockScreenSwipeUp; } /** @@ -744,7 +756,8 @@ public class AmbientState implements Dumpable { pw.println("mIsSwipingUp=" + mIsSwipingUp); pw.println("mPanelTracking=" + mPanelTracking); pw.println("mIsFlinging=" + mIsFlinging); - pw.println("mNeedFlingAfterLockscreenSwipeUp=" + mNeedFlingAfterLockscreenSwipeUp); + pw.println("mIsFlingRequiredAfterLockScreenSwipeUp=" + + mIsFlingRequiredAfterLockScreenSwipeUp); pw.println("mZDistanceBetweenElements=" + mZDistanceBetweenElements); pw.println("mBaseZHeight=" + mBaseZHeight); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 1fb265fea0b11..b4aa9fafa2be6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -5042,7 +5042,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setPanelFlinging(boolean flinging) { - mAmbientState.setIsFlinging(flinging); + mAmbientState.setFlinging(flinging); if (!flinging) { // re-calculate the stack height which was frozen while flinging updateStackPosition(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt new file mode 100644 index 0000000000000..11798a7a4f964 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification.stack + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val MAX_PULSE_HEIGHT = 100000f + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class AmbientStateTest : SysuiTestCase() { + + private val dumpManager = mock() + private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false } + private val bypassController = StackScrollAlgorithm.BypassController { false } + private val statusBarKeyguardViewManager = mock() + + private lateinit var sut: AmbientState + + @Before + fun setUp() { + sut = + AmbientState( + context, + dumpManager, + sectionProvider, + bypassController, + statusBarKeyguardViewManager, + ) + } + + // region isDimmed + @Test + fun isDimmed_whenTrue_shouldReturnTrue() { + sut.arrangeDimmed(true) + + assertThat(sut.isDimmed).isTrue() + } + + @Test + fun isDimmed_whenFalse_shouldReturnFalse() { + sut.arrangeDimmed(false) + + assertThat(sut.isDimmed).isFalse() + } + + @Test + fun isDimmed_whenDozeAmountIsEmpty_shouldReturnTrue() { + sut.arrangeDimmed(true) + sut.dozeAmount = 0f + + assertThat(sut.isDimmed).isTrue() + } + + @Test + fun isDimmed_whenPulseExpandingIsFalse_shouldReturnTrue() { + sut.arrangeDimmed(true) + sut.arrangePulseExpanding(false) + sut.dozeAmount = 1f // arrangePulseExpanding changes dozeAmount + + assertThat(sut.isDimmed).isTrue() + } + // endregion + + // region pulseHeight + @Test + fun pulseHeight_whenValueChanged_shouldCallListener() { + var listenerCalledCount = 0 + sut.pulseHeight = MAX_PULSE_HEIGHT + sut.setOnPulseHeightChangedListener { listenerCalledCount++ } + + sut.pulseHeight = 0f + + assertThat(listenerCalledCount).isEqualTo(1) + } + + @Test + fun pulseHeight_whenSetSameValue_shouldDoNothing() { + var listenerCalledCount = 0 + sut.pulseHeight = MAX_PULSE_HEIGHT + sut.setOnPulseHeightChangedListener { listenerCalledCount++ } + + sut.pulseHeight = MAX_PULSE_HEIGHT + + assertThat(listenerCalledCount).isEqualTo(0) + } + + @Test + fun pulseHeight_whenValueIsFull_shouldReturn0() { + sut.pulseHeight = MAX_PULSE_HEIGHT + + assertThat(sut.pulseHeight).isEqualTo(0f) + } + + @Test + fun pulseHeight_whenValueIsNotFull_shouldReturnValue() { + val expected = MAX_PULSE_HEIGHT - 0.1f + sut.pulseHeight = expected + + assertThat(sut.pulseHeight).isEqualTo(expected) + } + // endregion + + // region statusBarState + @Test + fun statusBarState_whenPreviousStateIsNotKeyguardAndChange_shouldSetIsFlingRequiredToFalse() { + sut.setStatusBarState(StatusBarState.SHADE) + sut.isFlingRequiredAfterLockScreenSwipeUp = true + + sut.setStatusBarState(StatusBarState.KEYGUARD) + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isFalse() + } + + @Test + fun statusBarState_whenPreviousStateIsKeyguardAndChange_shouldDoNothing() { + sut.setStatusBarState(StatusBarState.KEYGUARD) + sut.isFlingRequiredAfterLockScreenSwipeUp = true + + sut.setStatusBarState(StatusBarState.SHADE) + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue() + } + // endregion + + // region hideAmount + @Test + fun hideAmount_whenSetToFullValue_shouldReturnZeroFromPulseHeight() { + sut.hideAmount = 0f + sut.pulseHeight = 1f + + sut.hideAmount = 1f + + assertThat(sut.pulseHeight).isEqualTo(0f) + } + + @Test + fun hideAmount_whenSetToAnyNotFullValue_shouldDoNothing() { + sut.hideAmount = 1f + sut.pulseHeight = 1f + + sut.hideAmount = 0f + + assertThat(sut.pulseHeight).isEqualTo(1f) + } + // endregion + + // region dozeAmount + @Test + fun dozeAmount_whenDozeAmountIsSetToFullDozing_shouldReturnZeroFromPulseHeight() { + sut.dozeAmount = 0f + sut.pulseHeight = 1f + + sut.dozeAmount = 1f + + assertThat(sut.pulseHeight).isEqualTo(0f) + } + + @Test + fun dozeAmount_whenDozeAmountIsSetToFullAwake_shouldReturnZeroFromPulseHeight() { + sut.dozeAmount = 1f + sut.pulseHeight = 1f + + sut.dozeAmount = 0f + + assertThat(sut.pulseHeight).isEqualTo(0f) + } + + @Test + fun dozeAmount_whenDozeAmountIsSetAnyValueNotFullAwakeOrDozing_shouldDoNothing() { + sut.dozeAmount = 1f + sut.pulseHeight = 1f + + sut.dozeAmount = 0.5f + + assertThat(sut.pulseHeight).isEqualTo(1f) + } + // endregion + + // region trackedHeadsUpRow + @Test + fun trackedHeadsUpRow_whenIsAboveTheShelf_shouldReturnInstance() { + sut.trackedHeadsUpRow = mock { whenever(isAboveShelf).thenReturn(true) } + + assertThat(sut.trackedHeadsUpRow).isNotNull() + } + + @Test + fun trackedHeadsUpRow_whenIsNotAboveTheShelf_shouldReturnNull() { + sut.trackedHeadsUpRow = mock { whenever(isAboveShelf).thenReturn(false) } + + assertThat(sut.trackedHeadsUpRow).isNull() + } + // endregion + + // region isSwipingUp + @Test + fun isSwipingUp_whenValueChangedToTrue_shouldRequireFling() { + sut.isSwipingUp = false + sut.isFlingRequiredAfterLockScreenSwipeUp = false + + sut.isSwipingUp = true + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isFalse() + } + + @Test + fun isSwipingUp_whenValueChangedToFalse_shouldRequireFling() { + sut.isSwipingUp = true + sut.isFlingRequiredAfterLockScreenSwipeUp = false + + sut.isSwipingUp = false + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue() + } + // endregion + + // region isFlinging + @Test + fun isFlinging_shouldNotNeedFling() { + sut.arrangeFlinging(true) + + sut.setFlinging(false) + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isFalse() + } + + @Test + fun isFlinging_whenNotOnLockScreen_shouldDoNothing() { + sut.arrangeFlinging(true) + sut.setStatusBarState(StatusBarState.SHADE) + sut.isFlingRequiredAfterLockScreenSwipeUp = true + + sut.setFlinging(false) + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue() + } + + @Test + fun isFlinging_whenValueChangedToTrue_shouldDoNothing() { + sut.arrangeFlinging(false) + + sut.setFlinging(true) + + assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue() + } + // endregion + + // region scrollY + @Test + fun scrollY_shouldSetValueGreaterThanZero() { + sut.scrollY = 0 + + sut.scrollY = 1 + + assertThat(sut.scrollY).isEqualTo(1) + } + + @Test + fun scrollY_shouldNotSetValueLessThanZero() { + sut.scrollY = 0 + + sut.scrollY = -1 + + assertThat(sut.scrollY).isEqualTo(0) + } + // endregion + + // region setOverScrollAmount + fun setOverScrollAmount_shouldSetValueOnTop() { + sut.setOverScrollAmount(/* amount = */ 10f, /* onTop = */ true) + + val resultOnTop = sut.getOverScrollAmount(/* top = */ true) + val resultOnBottom = sut.getOverScrollAmount(/* top = */ false) + + assertThat(resultOnTop).isEqualTo(10f) + assertThat(resultOnBottom).isEqualTo(0f) + } + + fun setOverScrollAmount_shouldSetValueOnBottom() { + sut.setOverScrollAmount(/* amount = */ 10f, /* onTop = */ false) + + val resultOnTop = sut.getOverScrollAmount(/* top */ true) + val resultOnBottom = sut.getOverScrollAmount(/* top */ false) + + assertThat(resultOnTop).isEqualTo(0f) + assertThat(resultOnBottom).isEqualTo(10f) + } + // endregion + + // region IsPulseExpanding + @Test + fun isPulseExpanding_shouldReturnTrue() { + sut.arrangePulseExpanding(true) + + assertThat(sut.isPulseExpanding).isTrue() + } + + @Test + fun isPulseExpanding_whenPulseHeightIsMax_shouldReturnFalse() { + sut.arrangePulseExpanding(true) + sut.pulseHeight = MAX_PULSE_HEIGHT + + assertThat(sut.isPulseExpanding).isFalse() + } + + @Test + fun isPulseExpanding_whenDozeAmountIsZero_shouldReturnFalse() { + sut.arrangePulseExpanding(true) + sut.dozeAmount = 0f + + assertThat(sut.isPulseExpanding).isFalse() + } + + @Test + fun isPulseExpanding_whenHideAmountIsFull_shouldReturnFalse() { + sut.arrangePulseExpanding(true) + sut.hideAmount = 1f + + assertThat(sut.isPulseExpanding).isFalse() + } + // endregion + + // region isOnKeyguard + @Test + fun isOnKeyguard_whenStatusBarStateIsKeyguard_shouldReturnTrue() { + sut.setStatusBarState(StatusBarState.KEYGUARD) + + assertThat(sut.isOnKeyguard).isTrue() + } + + @Test + fun isOnKeyguard_whenStatusBarStateIsNotKeyguard_shouldReturnFalse() { + sut.setStatusBarState(StatusBarState.SHADE) + + assertThat(sut.isOnKeyguard).isFalse() + } + // endregion +} + +// region Arrange helper methods. +private fun AmbientState.arrangeDimmed(value: Boolean) { + isDimmed = value + dozeAmount = if (value) 0f else 1f + arrangePulseExpanding(!value) +} + +private fun AmbientState.arrangePulseExpanding(value: Boolean) { + if (value) { + dozeAmount = 1f + hideAmount = 0f + pulseHeight = 0f + } else { + dozeAmount = 0f + hideAmount = 1f + pulseHeight = MAX_PULSE_HEIGHT + } +} + +private fun AmbientState.arrangeFlinging(value: Boolean) { + setStatusBarState(StatusBarState.KEYGUARD) + setFlinging(value) + isFlingRequiredAfterLockScreenSwipeUp = true +} +// endregion diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 3c22edc0fcf2b..6ae021b48f66c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -250,7 +250,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // Validate that when the animation ends the stackEndHeight is recalculated immediately clearInvocations(mAmbientState); mStackScroller.setPanelFlinging(false); - verify(mAmbientState).setIsFlinging(eq(false)); + verify(mAmbientState).setFlinging(eq(false)); verify(mAmbientState).setStackEndHeight(anyFloat()); verify(mAmbientState).setStackHeight(anyFloat()); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index f539dbdf76a00..f7ad68f15d2f7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -26,6 +26,7 @@ package com.android.systemui.util.mockito import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatcher import org.mockito.Mockito +import org.mockito.stubbing.OngoingStubbing /** * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when @@ -77,8 +78,18 @@ inline fun argumentCaptor(): ArgumentCaptor = * Helper function for creating new mocks, without the need to pass in a [Class] instance. * * Generic T is nullable because implicitly bounded by Any?. + * + * @param apply builder function to simplify stub configuration by improving type inference. */ -inline fun mock(): T = Mockito.mock(T::class.java) +inline fun mock(apply: T.() -> Unit = {}): T = Mockito.mock(T::class.java) + .apply(apply) + +/** + * Helper function for stubbing methods without the need to use backticks. + * + * @see Mockito.when + */ +fun whenever(methodCall: T): OngoingStubbing = Mockito.`when`(methodCall) /** * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when