Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94368cdd authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "[Floaty] Request top-ui for squeeze effect" into main

parents 29f11679 862cdb86
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import androidx.core.animation.AnimatorTestRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
@@ -32,7 +33,10 @@ import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.testKosmos
import com.android.systemui.topui.TopUiControllerRefactor
import com.android.systemui.topui.mockTopUiController
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl.Companion.DEFAULT_INITIAL_DELAY_MILLIS
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl.Companion.DEFAULT_LONG_PRESS_POWER_DURATION_MILLIS
import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository
@@ -51,6 +55,9 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@@ -88,6 +95,9 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {
                appZoomOutOptional = appZoomOutOptional,
                keyEventInteractor = keyEventInteractor,
                squeezeEffectHapticPlayerFactory = squeezeEffectHapticPlayerFactory,
                notificationShadeWindowController = notificationShadeWindowController,
                topUiController = mockTopUiController,
                mainExecutor = fakeExecutor,
            )
        }

@@ -434,4 +444,97 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {

            assertThat(fakeAppZoomOut.lastTopLevelProgress).isEqualTo(0f)
        }

    @Test
    fun topUiRequested_whenAnimationStarts() =
        kosmos.runTest {
            // Setup: Enable effect and trigger power button down
            val initialDelay = 50L
            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
            fakeSqueezeEffectRepository.invocationEffectInitialDelayMs = initialDelay
            fakeKeyEventRepository.setPowerButtonDown(true)
            fakeSqueezeEffectRepository.isPowerButtonDownInKeyCombination.value = false

            // Action: Start the effect and advance time past initial delay to start animation
            underTest.start()
            advanceTime((initialDelay + 1).milliseconds)
            animatorTestRule.advanceTimeBy(1L) // Ensure animator starts processing

            // Verification: setRequestTopUi(true) should be called
            verifySetRequestTopUi(true)
        }

    @Test
    fun topUiCleared_whenAnimationFinishesNormally() =
        kosmos.runTest {
            // Setup: Enable effect and trigger power button down
            val initialDelay = 50L
            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
            fakeSqueezeEffectRepository.invocationEffectInitialDelayMs = initialDelay
            fakeKeyEventRepository.setPowerButtonDown(true)
            fakeSqueezeEffectRepository.isPowerButtonDownInKeyCombination.value = false

            // Action: Start the effect
            underTest.start()
            advanceTime((initialDelay + 1).milliseconds) // Pass initial delay
            animatorTestRule.advanceTimeBy(1L) // Ensure animator starts

            // Verification: Ensure TopUI was requested initially
            verifySetRequestTopUi(true)
            // Reset for next verification
            reset(kosmos.mockTopUiController, kosmos.notificationShadeWindowController)

            // Action: Complete the full animation cycle (inward + outward)
            animatorTestRule.advanceTimeBy(SqueezeEffectConfig.INWARD_EFFECT_DURATION.toLong() - 1L)
            runCurrent()
            animatorTestRule.advanceTimeBy(SqueezeEffectConfig.OUTWARD_EFFECT_DURATION.toLong())
            runCurrent()

            // Verification: setRequestTopUi(false) should be called upon completion
            verifySetRequestTopUi(false)
        }

    @Test
    fun topUiCleared_whenAnimationIsCancelled() =
        kosmos.runTest {
            // Setup: Enable effect and trigger power button down
            val initialDelay = 50L
            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
            fakeSqueezeEffectRepository.invocationEffectInitialDelayMs = initialDelay
            fakeKeyEventRepository.setPowerButtonDown(true)
            fakeSqueezeEffectRepository.isPowerButtonDownInKeyCombination.value = false

            // Action: Start the effect
            underTest.start()
            advanceTime((initialDelay + 1).milliseconds) // Pass initial delay
            // Progress animation part way
            animatorTestRule.advanceTimeBy(SqueezeEffectConfig.INWARD_EFFECT_DURATION.toLong() / 2)
            runCurrent()

            // Verification: Ensure TopUI was requested initially
            verifySetRequestTopUi(true)
            // Reset for next verification
            reset(kosmos.mockTopUiController, kosmos.notificationShadeWindowController)

            // Action: Release power button to cancel the animation
            fakeKeyEventRepository.setPowerButtonDown(false)
            runCurrent()
            // Allow cancellation animation to complete
            animatorTestRule.advanceTimeBy(SqueezeEffectConfig.OUTWARD_EFFECT_DURATION.toLong())
            runCurrent()

            // Verification: setRequestTopUi(false) should be called upon cancellation
            verifySetRequestTopUi(false)
        }

    private fun verifySetRequestTopUi(isRequested: Boolean) {
        if (TopUiControllerRefactor.isEnabled) {
            verify(kosmos.mockTopUiController, times(1))
                .setRequestTopUi(isRequested, TopLevelWindowEffects.TAG)
        } else {
            kosmos.fakeExecutor.runAllReady()
            verify(kosmos.notificationShadeWindowController, times(1))
                .setRequestTopUi(isRequested, TopLevelWindowEffects.TAG)
        }
    }
}
+22 −0
Original line number Diff line number Diff line
@@ -25,13 +25,18 @@ import com.android.app.animation.InterpolatorsAndroidX
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.topui.TopUiController
import com.android.systemui.topui.TopUiControllerRefactor
import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectConfig
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectHapticPlayer
import com.android.wm.shell.appzoomout.AppZoomOut
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -48,6 +53,11 @@ constructor(
    // TODO(b/409930584): make AppZoomOut non-optional
    private val appZoomOutOptional: Optional<AppZoomOut>,
    squeezeEffectHapticPlayerFactory: SqueezeEffectHapticPlayer.Factory,
    private val topUiController: TopUiController,
    // TODO(b/411061512): Remove notificationShadeWindowController and mainExecutor once
    // TopUiControllerRefactor made it to nextfood
    private val notificationShadeWindowController: NotificationShadeWindowController,
    @Main private val mainExecutor: Executor,
) : CoreStartable {

    // The main animation is interruptible until power button long press has been detected. At this
@@ -86,6 +96,7 @@ constructor(

    private suspend fun startSqueeze() {
        delay(squeezeEffectInteractor.getInvocationEffectInitialDelayMs())
        setRequestTopUi(true)
        animateSqueezeProgressTo(
            targetProgress = 1f,
            duration = SqueezeEffectConfig.INWARD_EFFECT_DURATION.toLong(),
@@ -142,6 +153,17 @@ constructor(

    private fun finishAnimation() {
        isAnimationInterruptible = true
        setRequestTopUi(false)
    }

    private fun setRequestTopUi(requestTopUi: Boolean) {
        if (TopUiControllerRefactor.isEnabled) {
            topUiController.setRequestTopUi(requestTopUi, TAG)
        } else {
            mainExecutor.execute {
                notificationShadeWindowController.setRequestTopUi(requestTopUi, TAG)
            }
        }
    }

    override fun dump(pw: PrintWriter, args: Array<out String>) {