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

Commit 3871e2d3 authored by Andreas Miko's avatar Andreas Miko
Browse files

Fix new lightreveal transition

- Lightreveal will now play independently instead of being directly tied
to Transition steps. This is needed because the transition AOD => GONE
has actually two parts of the transition AOD => LOCKSCREEN then
LOCKSCREEN => GONE would otherwise cancel the animation when the second
part of the transition starts.
- LightReveal will now play backwards from it's current state when
unlocking/locking quickly one after another (instead cancel/restart)
- LightReveal will now play correctly (from tap) when AOD has not yet
entered low power mode
- Power off will now animate correctly even if the device was unlocked
with tap

Test: added unit tests
Bug: b/292087855

Change-Id: Ie25efca4a829dede2da80a518e425ec9f314d6d9
parent 8d9f729a
Loading
Loading
Loading
Loading
+33 −6
Original line number Original line Diff line number Diff line
@@ -20,10 +20,13 @@ package com.android.systemui.keyguard.data.repository


import android.content.Context
import android.content.Context
import android.graphics.Point
import android.graphics.Point
import androidx.core.animation.Animator
import androidx.core.animation.ValueAnimator
import com.android.systemui.R
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealEffect
@@ -31,9 +34,12 @@ import com.android.systemui.statusbar.PowerButtonReveal
import javax.inject.Inject
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
@@ -52,6 +58,10 @@ interface LightRevealScrimRepository {
     * at the current screen position of the appropriate sensor.
     * at the current screen position of the appropriate sensor.
     */
     */
    val revealEffect: Flow<LightRevealEffect>
    val revealEffect: Flow<LightRevealEffect>

    val revealAmount: Flow<Float>

    fun startRevealAmountAnimator(reveal: Boolean)
}
}


@SysUISingleton
@SysUISingleton
@@ -108,14 +118,31 @@ constructor(


    /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
    /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
    private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
    private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
        keyguardRepository.wakefulness.flatMapLatest { wakefulnessModel ->
        keyguardRepository.wakefulness
            .filter { it.isStartingToWake() || it.isStartingToSleep() }
            .flatMapLatest { wakefulnessModel ->
                when {
                when {
                    wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
                    wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
                wakefulnessModel.isAwakeFromTap() -> tapRevealEffect
                    wakefulnessModel.isWakingFrom(TAP) -> tapRevealEffect
                    else -> flowOf(LiftReveal)
                    else -> flowOf(LiftReveal)
                }
                }
            }
            }


    private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 }

    override val revealAmount: Flow<Float> = callbackFlow {
        val updateListener =
            Animator.AnimatorUpdateListener {
                trySend((it as ValueAnimator).animatedValue as Float)
            }
        revealAmountAnimator.addUpdateListener(updateListener)
        awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
    }

    override fun startRevealAmountAnimator(reveal: Boolean) {
        if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
    }

    override val revealEffect =
    override val revealEffect =
        combine(
        combine(
                keyguardRepository.biometricUnlockState,
                keyguardRepository.biometricUnlockState,
+24 −18
Original line number Original line Diff line number Diff line
@@ -17,28 +17,44 @@
package com.android.systemui.keyguard.domain.interactor
package com.android.systemui.keyguard.domain.interactor


import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.map


@ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@SysUISingleton
@SysUISingleton
class LightRevealScrimInteractor
class LightRevealScrimInteractor
@Inject
@Inject
constructor(
constructor(
    transitionRepository: KeyguardTransitionRepository,
    private val transitionInteractor: KeyguardTransitionInteractor,
    transitionInteractor: KeyguardTransitionInteractor,
    private val lightRevealScrimRepository: LightRevealScrimRepository,
    lightRevealScrimRepository: LightRevealScrimRepository,
    @Application private val scope: CoroutineScope,
) {
) {


    init {
        listenForStartedKeyguardTransitionStep()
    }

    private fun listenForStartedKeyguardTransitionStep() {
        scope.launch {
            transitionInteractor.startedKeyguardTransitionStep.collect {
                if (willTransitionChangeEndState(it)) {
                    lightRevealScrimRepository.startRevealAmountAnimator(
                        willBeRevealedInState(it.to)
                    )
                }
            }
        }
    }

    /**
    /**
     * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
     * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
     * and use that for the starting transition.
     * and use that for the starting transition.
@@ -54,17 +70,7 @@ constructor(
            lightRevealScrimRepository.revealEffect
            lightRevealScrimRepository.revealEffect
        )
        )


    /**
    val revealAmount = lightRevealScrimRepository.revealAmount
     * The reveal amount to use for the light reveal scrim, which is derived from the keyguard
     * transition steps.
     */
    val revealAmount: Flow<Float> =
        transitionRepository.transitions
            // Only listen to transitions that change the reveal amount.
            .filter { willTransitionAffectRevealAmount(it) }
            // Use the transition amount as the reveal amount, inverting it if we're transitioning
            // to a non-revealed (hidden) state.
            .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }


    companion object {
    companion object {


@@ -72,7 +78,7 @@ constructor(
         * Whether the transition requires a change in the reveal amount of the light reveal scrim.
         * Whether the transition requires a change in the reveal amount of the light reveal scrim.
         * If not, we don't care about the transition and don't need to listen to it.
         * If not, we don't care about the transition and don't need to listen to it.
         */
         */
        fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
        fun willTransitionChangeEndState(transition: TransitionStep): Boolean {
            return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
            return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
        }
        }


+20 −15
Original line number Original line Diff line number Diff line
@@ -16,6 +16,13 @@
package com.android.systemui.keyguard.shared.model
package com.android.systemui.keyguard.shared.model


import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.WakeSleepReason.GESTURE
import com.android.systemui.keyguard.shared.model.WakeSleepReason.POWER_BUTTON
import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
import com.android.systemui.keyguard.shared.model.WakefulnessState.ASLEEP
import com.android.systemui.keyguard.shared.model.WakefulnessState.AWAKE
import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_SLEEP
import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_WAKE


/** Model device wakefulness states. */
/** Model device wakefulness states. */
data class WakefulnessModel(
data class WakefulnessModel(
@@ -23,33 +30,31 @@ data class WakefulnessModel(
    val lastWakeReason: WakeSleepReason,
    val lastWakeReason: WakeSleepReason,
    val lastSleepReason: WakeSleepReason,
    val lastSleepReason: WakeSleepReason,
) {
) {
    fun isStartingToWake() = state == WakefulnessState.STARTING_TO_WAKE
    fun isStartingToWake() = state == STARTING_TO_WAKE


    fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
    fun isStartingToSleep() = state == STARTING_TO_SLEEP


    private fun isAsleep() = state == WakefulnessState.ASLEEP
    private fun isAsleep() = state == ASLEEP

    private fun isAwake() = state == AWAKE

    fun isStartingToWakeOrAwake() = isStartingToWake() || isAwake()


    fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
    fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()


    fun isDeviceInteractive() = !isAsleep()
    fun isDeviceInteractive() = !isAsleep()


    fun isStartingToWakeOrAwake() = isStartingToWake() || state == WakefulnessState.AWAKE
    fun isWakingFrom(wakeSleepReason: WakeSleepReason) =
        isStartingToWake() && lastWakeReason == wakeSleepReason


    fun isStartingToSleepFromPowerButton() =
    fun isStartingToSleepFrom(wakeSleepReason: WakeSleepReason) =
        isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
        isStartingToSleep() && lastSleepReason == wakeSleepReason

    fun isWakingFromPowerButton() =
        isStartingToWake() && lastWakeReason == WakeSleepReason.POWER_BUTTON


    fun isTransitioningFromPowerButton() =
    fun isTransitioningFromPowerButton() =
        isStartingToSleepFromPowerButton() || isWakingFromPowerButton()
        isStartingToSleepFrom(POWER_BUTTON) || isWakingFrom(POWER_BUTTON)

    fun isAwakeFromTap() =
        state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP


    fun isDeviceInteractiveFromTapOrGesture(): Boolean {
    fun isDeviceInteractiveFromTapOrGesture(): Boolean {
        return isDeviceInteractive() &&
        return isDeviceInteractive() && (lastWakeReason == TAP || lastWakeReason == GESTURE)
            (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
    }
    }


    companion object {
    companion object {
+2 −4
Original line number Original line Diff line number Diff line
@@ -20,20 +20,18 @@ import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map


/** View-model for the keyguard indication area view */
/** View-model for the keyguard indication area view */
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardIndicationAreaViewModel
class KeyguardIndicationAreaViewModel
@Inject
@Inject
constructor(
constructor(
    private val keyguardInteractor: KeyguardInteractor,
    private val keyguardInteractor: KeyguardInteractor,
    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
    bottomAreaInteractor: KeyguardBottomAreaInteractor,
    private val keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
    keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
    private val burnInHelperWrapper: BurnInHelperWrapper,
    private val burnInHelperWrapper: BurnInHelperWrapper,
) {
) {


+2 −0
Original line number Original line Diff line number Diff line
@@ -19,12 +19,14 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealEffect
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow


/**
/**
 * Models UI state for the light reveal scrim, which is used during screen on and off animations to
 * Models UI state for the light reveal scrim, which is used during screen on and off animations to
 * draw a gradient that reveals/hides the contents of the screen.
 * draw a gradient that reveals/hides the contents of the screen.
 */
 */
@OptIn(ExperimentalCoroutinesApi::class)
class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
    val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
    val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
    val revealAmount: Flow<Float> = interactor.revealAmount
    val revealAmount: Flow<Float> = interactor.revealAmount
Loading