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

Commit 98f8de33 authored by Josh Tsuji's avatar Josh Tsuji
Browse files

Cache isLauncherUnderneath and update when finished in not-GONE.

This requires a binder call to ATMS#getTasks, which was happening 1-2 times during unlock and adding several ms to startTransition.

Since it's very unlikely that the app underneath will change while we're locked (something would have to crash, or be launched via alarm/adb), we can simply update this whenever we finish a transition away from GONE. Worst case, if we're wrong, then either launcher will just play the normal app-window animation, or launcher will play the in-window animation when it's not visible, neither of which are a big deal.

Bug: 278086361
Flag: com.android.systemui.keyguard_wm_state_refactor
Test: perfetto trace, see only one call to getTasks() and it's not during unlock CUJ
Test: atest InWindowLauncherUnlockAnimationInteractorTest
Change-Id: I36684fc32d8396f3bd9d0c088f544b4209317595
parent 780acdef
Loading
Loading
Loading
Loading
+57 −118
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
        InWindowLauncherUnlockAnimationInteractor(
            kosmos.inWindowLauncherUnlockAnimationRepository,
            kosmos.applicationCoroutineScope,
            kosmos.applicationCoroutineScope,
            kosmos.keyguardTransitionInteractor,
            { kosmos.keyguardSurfaceBehindRepository },
            kosmos.activityManagerWrapper,
@@ -73,29 +74,24 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
    @Test
    fun testTransitioningToGoneWithInWindowAnimation_trueIfTopActivityIsLauncher_andTransitioningToGone() =
        testScope.runTest {
            transitionToGoneThenLockscreen(withLauncherUnderneath = true)

            val values by collectValues(underTest.transitioningToGoneWithInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false, // False by default.
                    false // False by default.
                ),
                values
            )

            // Put launcher on top
            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                launcherClassName
                values,
            )
            activityManagerWrapper.mockTopActivityClassName(launcherClassName)
            runCurrent()

            // Should still be false since we're not transitioning to GONE.
            assertEquals(
                listOf(
                    false, // False by default.
                    false // False by default.
                ),
                values
                values,
            )

            transitionRepository.sendTransitionStep(
@@ -112,7 +108,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
                    false,
                    true, // -> GONE + launcher is behind
                ),
                values
                values,
            )

            activityManagerWrapper.mockTopActivityClassName("not_launcher")
@@ -131,7 +127,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
                    true, // Top activity should be sampled, if it changes midway it should not
                    // matter.
                ),
                values
                values,
            )

            transitionRepository.sendTransitionStep(
@@ -149,35 +145,23 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
                    true,
                    false, // False once we're not transitioning anymore.
                ),
                values
                values,
            )
        }

    @Test
    fun testTransitioningToGoneWithInWindowAnimation_falseIfTopActivityIsLauncherPartwayThrough() =
        testScope.runTest {
            val values by collectValues(underTest.transitioningToGoneWithInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false, // False by default.
                ),
                values
            )
            transitionToGoneThenLockscreen(withLauncherUnderneath = false)

            // Put not launcher on top
            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                launcherClassName
            )
            activityManagerWrapper.mockTopActivityClassName("not_launcher")
            val values by collectValues(underTest.transitioningToGoneWithInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false,
                    false // False by default.
                ),
                values
                values,
            )

            transitionRepository.sendTransitionStep(
@@ -189,12 +173,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            )
            assertEquals(listOf(false), values)

            activityManagerWrapper.mockTopActivityClassName(launcherClassName)
            transitionRepository.sendTransitionStep(
@@ -206,12 +185,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            )
            assertEquals(listOf(false), values)

            transitionRepository.sendTransitionStep(
                TransitionStep(
@@ -222,39 +196,21 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            )
            assertEquals(listOf(false), values)
        }

    @Test
    fun testTransitioningToGoneWithInWindowAnimation_falseIfTopActivityIsLauncherWhileNotTransitioningToGone() =
        testScope.runTest {
            transitionToGoneThenLockscreen(withLauncherUnderneath = true)
            val values by collectValues(underTest.transitioningToGoneWithInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false, // False by default.
                    false // False by default.
                ),
                values
            )

            // Put launcher on top
            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                launcherClassName
            )
            activityManagerWrapper.mockTopActivityClassName(launcherClassName)
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
                values,
            )

            transitionRepository.sendTransitionStep(
@@ -266,32 +222,24 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            )
            assertEquals(listOf(false), values)
        }

    @Test
    fun testShouldStartInWindowAnimation_trueOnceSurfaceAvailable_falseWhenTransitionEnds() =
        testScope.runTest {
            transitionToGoneThenLockscreen(withLauncherUnderneath = true)

            val values by collectValues(underTest.shouldStartInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false, // False by default.
                    false // False by default.
                ),
                values
                values,
            )

            // Put Launcher on top and begin transitioning to GONE.
            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                launcherClassName
            )
            activityManagerWrapper.mockTopActivityClassName(launcherClassName)
            transitionRepository.sendTransitionStep(
                TransitionStep(
                    transitionState = TransitionState.STARTED,
@@ -301,12 +249,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            )
            assertEquals(listOf(false), values)

            kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
            runCurrent()
@@ -316,7 +259,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
                    false,
                    true, // The surface is now available, so we should start the animation.
                ),
                values
                values,
            )

            transitionRepository.sendTransitionStep(
@@ -328,34 +271,24 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                    true,
                    false,
                ),
                values
            )
            assertEquals(listOf(false, true, false), values)
        }

    @Test
    fun testShouldStartInWindowAnimation_neverTrueIfSurfaceNotAvailable() =
        testScope.runTest {
            transitionToGoneThenLockscreen(withLauncherUnderneath = true)

            val values by collectValues(underTest.shouldStartInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false, // False by default.
                    false // False by default.
                ),
                values
                values,
            )

            // Put Launcher on top and begin transitioning to GONE.
            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                launcherClassName
            )
            activityManagerWrapper.mockTopActivityClassName(launcherClassName)
            transitionRepository.sendTransitionStep(
                TransitionStep(
                    transitionState = TransitionState.STARTED,
@@ -372,32 +305,23 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            )
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            )
            assertEquals(listOf(false), values)
        }

    @Test
    fun testShouldStartInWindowAnimation_falseIfSurfaceAvailable_afterTransitionInterrupted() =
        testScope.runTest {
            transitionToGoneThenLockscreen(withLauncherUnderneath = true)
            val values by collectValues(underTest.shouldStartInWindowAnimation)
            runCurrent()

            assertEquals(
                listOf(
                    false, // False by default.
                    false // False by default.
                ),
                values
                values,
            )

            // Put Launcher on top and begin transitioning to GONE.
            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                launcherClassName
            )
            activityManagerWrapper.mockTopActivityClassName(launcherClassName)
            transitionRepository.sendTransitionStep(
                TransitionStep(
                    transitionState = TransitionState.STARTED,
@@ -422,11 +346,26 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
            kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
            runCurrent()

            assertEquals(
                listOf(
                    false,
                ),
                values
            assertEquals(listOf(false), values)
        }

    /** Transitions to GONE from LOCKSCREEN after setting launcher underneath (or not). */
    private suspend fun transitionToGoneThenLockscreen(withLauncherUnderneath: Boolean) {
        transitionRepository.sendTransitionSteps(
            from = KeyguardState.LOCKSCREEN,
            to = KeyguardState.GONE,
            testScope,
        )

        kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(launcherClassName)
        activityManagerWrapper.mockTopActivityClassName(
            if (withLauncherUnderneath) launcherClassName else "not_launcher"
        )

        transitionRepository.sendTransitionSteps(
            from = KeyguardState.GONE,
            to = KeyguardState.LOCKSCREEN,
            testScope,
        )
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -71,6 +71,8 @@ class InWindowLauncherUnlockAnimationRepository @Inject constructor() {
     */
    val launcherSmartspaceState: MutableStateFlow<SmartspaceState?> = MutableStateFlow(null)

    val isLauncherUnderneath: MutableStateFlow<Boolean> = MutableStateFlow(false)

    fun setStartedUnlockAnimation(started: Boolean) {
        startedUnlockAnimation.value = started
    }
@@ -86,4 +88,8 @@ class InWindowLauncherUnlockAnimationRepository @Inject constructor() {
    fun setLauncherSmartspaceState(state: SmartspaceState?) {
        launcherSmartspaceState.value = state
    }

    fun setIsLauncherUnderneath(isUnderneath: Boolean) {
        isLauncherUnderneath.value = isUnderneath
    }
}
+40 −6
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.shared.model.Edge
@@ -31,8 +32,10 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

@SysUISingleton
class InWindowLauncherUnlockAnimationInteractor
@@ -40,6 +43,7 @@ class InWindowLauncherUnlockAnimationInteractor
constructor(
    private val repository: InWindowLauncherUnlockAnimationRepository,
    @Application scope: CoroutineScope,
    @Background val backgroundScope: CoroutineScope,
    transitionInteractor: KeyguardTransitionInteractor,
    surfaceBehindRepository: dagger.Lazy<KeyguardSurfaceBehindRepository>,
    private val activityManager: ActivityManagerWrapper,
@@ -54,7 +58,7 @@ constructor(
        transitionInteractor
            .isInTransition(
                edge = Edge.create(to = Scenes.Gone),
                edgeWithoutSceneContainer = Edge.create(to = GONE)
                edgeWithoutSceneContainer = Edge.create(to = GONE),
            )
            .map { transitioningToGone -> transitioningToGone && isLauncherUnderneath() }
            .stateIn(scope, SharingStarted.Eagerly, false)
@@ -74,6 +78,25 @@ constructor(
            }
            .stateIn(scope, SharingStarted.Eagerly, false)

    init {
        backgroundScope.launch {
            // Whenever we're not GONE, update whether Launcher was the app in front. We need to do
            // this before the next unlock, but it triggers a binder call, so this is the best time
            // to do it. In edge cases where this changes while we're locked (the foreground app
            // crashes, etc.) the worst case is that we fall back to the normal unlock animation (or
            // unnecessarily play the animation on Launcher when there's an app over it), which is
            // not a big deal.
            transitionInteractor.currentKeyguardState
                .map { it != GONE }
                .distinctUntilChanged()
                .collect { notOnGone ->
                    if (notOnGone) {
                        updateIsLauncherUnderneath()
                    }
                }
        }
    }

    /** Sets whether we've started */
    fun setStartedUnlockAnimation(started: Boolean) {
        repository.setStartedUnlockAnimation(started)
@@ -92,13 +115,24 @@ constructor(
    }

    /**
     * Whether the Launcher is currently underneath the lockscreen (it's at the top of the activity
     * task stack).
     * Whether the Launcher was underneath the lockscreen as of the last time we locked (it's at the
     * top of the activity task stack).
     */
    fun isLauncherUnderneath(): Boolean {
        return repository.launcherActivityClass.value?.let {
        return repository.isLauncherUnderneath.value
    }

    /**
     * Updates whether Launcher is the app underneath the lock screen as of this moment. Triggers a
     * binder call, so this is run in the background.
     */
    private fun updateIsLauncherUnderneath() {
        backgroundScope.launch {
            repository.setIsLauncherUnderneath(
                repository.launcherActivityClass.value?.let {
                    activityManager.runningTask?.topActivity?.className?.equals(it)
                } ?: false
            )
        }
            ?: false
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ val Kosmos.inWindowLauncherUnlockAnimationInteractor by
        InWindowLauncherUnlockAnimationInteractor(
            repository = inWindowLauncherUnlockAnimationRepository,
            scope = applicationCoroutineScope,
            backgroundScope = applicationCoroutineScope,
            transitionInteractor = keyguardTransitionInteractor,
            surfaceBehindRepository = Lazy { keyguardSurfaceBehindRepository },
            activityManager = activityManagerWrapper,