Loading packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt +103 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -88,6 +95,9 @@ class TopLevelWindowEffectsTest : SysuiTestCase() { appZoomOutOptional = appZoomOutOptional, keyEventInteractor = keyEventInteractor, squeezeEffectHapticPlayerFactory = squeezeEffectHapticPlayerFactory, notificationShadeWindowController = notificationShadeWindowController, topUiController = mockTopUiController, mainExecutor = fakeExecutor, ) } Loading Loading @@ -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) } } } packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt +22 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -86,6 +96,7 @@ constructor( private suspend fun startSqueeze() { delay(squeezeEffectInteractor.getInvocationEffectInitialDelayMs()) setRequestTopUi(true) animateSqueezeProgressTo( targetProgress = 1f, duration = SqueezeEffectConfig.INWARD_EFFECT_DURATION.toLong(), Loading Loading @@ -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>) { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt +103 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -88,6 +95,9 @@ class TopLevelWindowEffectsTest : SysuiTestCase() { appZoomOutOptional = appZoomOutOptional, keyEventInteractor = keyEventInteractor, squeezeEffectHapticPlayerFactory = squeezeEffectHapticPlayerFactory, notificationShadeWindowController = notificationShadeWindowController, topUiController = mockTopUiController, mainExecutor = fakeExecutor, ) } Loading Loading @@ -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) } } }
packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt +22 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -86,6 +96,7 @@ constructor( private suspend fun startSqueeze() { delay(squeezeEffectInteractor.getInvocationEffectInitialDelayMs()) setRequestTopUi(true) animateSqueezeProgressTo( targetProgress = 1f, duration = SqueezeEffectConfig.INWARD_EFFECT_DURATION.toLong(), Loading Loading @@ -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>) { Loading