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

Commit 2689f634 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Force visible while transitioning to activities" into main

parents b26f7b43 865eb36b
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
@@ -575,4 +575,50 @@ class SceneInteractorTest : SysuiTestCase() {

            assertThat(currentScene).isNotEqualTo(disabledScene)
        }

    @Test
    fun transitionAnimations() =
        kosmos.runTest {
            val isVisible by collectLastValue(underTest.isVisible)
            assertThat(isVisible).isTrue()

            underTest.setVisible(false, "test")
            assertThat(isVisible).isFalse()

            underTest.onTransitionAnimationStart()
            // One animation is active, forced visible.
            assertThat(isVisible).isTrue()

            underTest.onTransitionAnimationEnd()
            // No more active animations, not forced visible.
            assertThat(isVisible).isFalse()

            underTest.onTransitionAnimationStart()
            // One animation is active, forced visible.
            assertThat(isVisible).isTrue()

            underTest.onTransitionAnimationCancelled()
            // No more active animations, not forced visible.
            assertThat(isVisible).isFalse()

            underTest.setVisible(true, "test")
            assertThat(isVisible).isTrue()

            underTest.onTransitionAnimationStart()
            underTest.onTransitionAnimationStart()
            // Two animations are active, forced visible.
            assertThat(isVisible).isTrue()

            underTest.setVisible(false, "test")
            // Two animations are active, forced visible.
            assertThat(isVisible).isTrue()

            underTest.onTransitionAnimationEnd()
            // One animation is still active, forced visible.
            assertThat(isVisible).isTrue()

            underTest.onTransitionAnimationEnd()
            // No more active animations, not forced visible.
            assertThat(isVisible).isFalse()
        }
}
+27 −0
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import com.android.keyguard.AuthInteractionProperties
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -141,6 +143,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.whenever

@SmallTest
@@ -169,6 +172,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
    private val uiEventLoggerFake = kosmos.uiEventLoggerFake
    private val msdlPlayer = kosmos.fakeMSDLPlayer
    private val authInteractionProperties = AuthInteractionProperties()
    private val mockActivityTransitionAnimator = mock<ActivityTransitionAnimator>()

    private lateinit var underTest: SceneContainerStartable

@@ -177,6 +181,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
        MockitoAnnotations.initMocks(this)
        whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
            .thenReturn(true)
        kosmos.activityTransitionAnimator = mockActivityTransitionAnimator

        underTest = kosmos.sceneContainerStartable
    }

@@ -2716,6 +2722,27 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun hydrateActivityTransitionAnimationState() =
        kosmos.runTest {
            underTest.start()

            val isVisible by collectLastValue(sceneInteractor.isVisible)
            assertThat(isVisible).isTrue()

            sceneInteractor.setVisible(false, "reason")
            assertThat(isVisible).isFalse()

            val argumentCaptor = argumentCaptor<ActivityTransitionAnimator.Listener>()
            verify(mockActivityTransitionAnimator).addListener(argumentCaptor.capture())

            val listeners = argumentCaptor.allValues
            listeners.forEach { it.onTransitionAnimationStart() }
            assertThat(isVisible).isTrue()
            listeners.forEach { it.onTransitionAnimationEnd() }
            assertThat(isVisible).isFalse()
        }

    private fun TestScope.emulateSceneTransition(
        transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
        toScene: SceneKey,
+10 −27
Original line number Diff line number Diff line
@@ -90,22 +90,15 @@ constructor(
                initialValue = defaultTransitionState,
            )

    fun changeScene(
        toScene: SceneKey,
        transitionKey: TransitionKey? = null,
    ) {
        dataSource.changeScene(
            toScene = toScene,
            transitionKey = transitionKey,
        )
    /** Number of currently active transition animations. */
    val activeTransitionAnimationCount = MutableStateFlow(0)

    fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null) {
        dataSource.changeScene(toScene = toScene, transitionKey = transitionKey)
    }

    fun snapToScene(
        toScene: SceneKey,
    ) {
        dataSource.snapToScene(
            toScene = toScene,
        )
    fun snapToScene(toScene: SceneKey) {
        dataSource.snapToScene(toScene = toScene)
    }

    /**
@@ -116,10 +109,7 @@ constructor(
     * [overlay] is already shown.
     */
    fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey? = null) {
        dataSource.showOverlay(
            overlay = overlay,
            transitionKey = transitionKey,
        )
        dataSource.showOverlay(overlay = overlay, transitionKey = transitionKey)
    }

    /**
@@ -130,10 +120,7 @@ constructor(
     * if [overlay] is already hidden.
     */
    fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey? = null) {
        dataSource.hideOverlay(
            overlay = overlay,
            transitionKey = transitionKey,
        )
        dataSource.hideOverlay(overlay = overlay, transitionKey = transitionKey)
    }

    /**
@@ -143,11 +130,7 @@ constructor(
     * This throws if [from] is not currently shown or if [to] is already shown.
     */
    fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey? = null) {
        dataSource.replaceOverlay(
            from = from,
            to = to,
            transitionKey = transitionKey,
        )
        dataSource.replaceOverlay(from = from, to = to, transitionKey = transitionKey)
    }

    /** Sets whether the container is visible. */
+55 −4
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update

/**
 * Generic business logic and app state accessors for the scene framework.
@@ -165,12 +166,15 @@ constructor(

    /** Whether the scene container is visible. */
    val isVisible: StateFlow<Boolean> =
        combine(repository.isVisible, repository.isRemoteUserInputOngoing) {
                isVisible,
                isRemoteUserInteractionOngoing ->
        combine(
                repository.isVisible,
                repository.isRemoteUserInputOngoing,
                repository.activeTransitionAnimationCount,
            ) { isVisible, isRemoteUserInteractionOngoing, activeTransitionAnimationCount ->
                isVisibleInternal(
                    raw = isVisible,
                    isRemoteUserInputOngoing = isRemoteUserInteractionOngoing,
                    activeTransitionAnimationCount = activeTransitionAnimationCount,
                )
            }
            .stateIn(
@@ -436,8 +440,9 @@ constructor(
    private fun isVisibleInternal(
        raw: Boolean = repository.isVisible.value,
        isRemoteUserInputOngoing: Boolean = repository.isRemoteUserInputOngoing.value,
        activeTransitionAnimationCount: Int = repository.activeTransitionAnimationCount.value,
    ): Boolean {
        return raw || isRemoteUserInputOngoing
        return raw || isRemoteUserInputOngoing || activeTransitionAnimationCount > 0
    }

    /**
@@ -525,4 +530,50 @@ constructor(
    ): Flow<Map<UserAction, UserActionResult>> {
        return disabledContentInteractor.filteredUserActions(unfiltered)
    }

    /**
     * Notifies that a transition animation has started.
     *
     * The scene container will remain visible while any transition animation is running within it.
     */
    fun onTransitionAnimationStart() {
        repository.activeTransitionAnimationCount.update { current ->
            (current + 1).also {
                check(it < 10) {
                    "Number of active transition animations is too high. Something must be" +
                        " calling onTransitionAnimationStart too many times!"
                }
            }
        }
    }

    /**
     * Notifies that a transition animation has ended.
     *
     * The scene container will remain visible while any transition animation is running within it.
     */
    fun onTransitionAnimationEnd() {
        decrementActiveTransitionAnimationCount()
    }

    /**
     * Notifies that a transition animation has been canceled.
     *
     * The scene container will remain visible while any transition animation is running within it.
     */
    fun onTransitionAnimationCancelled() {
        decrementActiveTransitionAnimationCount()
    }

    private fun decrementActiveTransitionAnimationCount() {
        repository.activeTransitionAnimationCount.update { current ->
            (current - 1).also {
                check(it >= 0) {
                    "Number of active transition animations is negative. Something must be" +
                        " calling onTransitionAnimationEnd or onTransitionAnimationCancelled too" +
                        " many times!"
                }
            }
        }
    }
}
+32 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.AuthInteractionProperties
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -146,6 +147,7 @@ constructor(
    private val vibratorHelper: VibratorHelper,
    private val msdlPlayer: MSDLPlayer,
    private val disabledContentInteractor: DisabledContentInteractor,
    private val activityTransitionAnimator: ActivityTransitionAnimator,
) : CoreStartable {
    private val centralSurfaces: CentralSurfaces?
        get() = centralSurfacesOptLazy.get().getOrNull()
@@ -169,6 +171,7 @@ constructor(
            handleKeyguardEnabledness()
            notifyKeyguardDismissCancelledCallbacks()
            refreshLockscreenEnabled()
            hydrateActivityTransitionAnimationState()
        } else {
            sceneLogger.logFrameworkEnabled(
                isEnabled = false,
@@ -929,6 +932,35 @@ constructor(
        }
    }

    /**
     * Wires the scene framework to activity transition animations that originate from anywhere. A
     * subset of these may actually originate from UI inside one of the scenes in the framework.
     *
     * Telling the scene framework about ongoing activity transition animations is critical so the
     * scene framework doesn't make its scene container invisible during a transition.
     *
     * As it turns out, making the scene container view invisible during a transition animation
     * disrupts the animation and causes interaction jank CUJ tracking to ignore reports of the CUJ
     * ending or being canceled.
     */
    private fun hydrateActivityTransitionAnimationState() {
        activityTransitionAnimator.addListener(
            object : ActivityTransitionAnimator.Listener {
                override fun onTransitionAnimationStart() {
                    sceneInteractor.onTransitionAnimationStart()
                }

                override fun onTransitionAnimationEnd() {
                    sceneInteractor.onTransitionAnimationEnd()
                }

                override fun onTransitionAnimationCancelled() {
                    sceneInteractor.onTransitionAnimationCancelled()
                }
            }
        )
    }

    companion object {
        private const val TAG = "SceneContainerStartable"
    }
Loading