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

Commit 5f3bb108 authored by Matt Pietal's avatar Matt Pietal Committed by Android (Google) Code Review
Browse files

Merge changes I72c43c93,I254d2130 into tm-qpr-dev

* changes:
  Transitions - Add DOZING -> GONE
  Transitions - Refactor view model
parents 030b7809 85366153
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@ constructor(

    override fun start() {
        listenForDozingToLockscreen()
        listenForDozingToGone()
    }

    private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@ constructor(
        }
    }

    private fun listenForDozingToGone() {
        scope.launch {
            keyguardInteractor.biometricUnlockState
                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                .collect { (biometricUnlockState, lastStartedTransition) ->
                    if (
                        lastStartedTransition.to == KeyguardState.DOZING &&
                            isWakeAndUnlock(biometricUnlockState)
                    ) {
                        keyguardTransitionRepository.startTransition(
                            TransitionInfo(
                                name,
                                KeyguardState.DOZING,
                                KeyguardState.GONE,
                                getAnimator(),
                            )
                        )
                    }
                }
        }
    }

    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
        return ValueAnimator().apply {
            setInterpolator(Interpolators.LINEAR)
+0 −38
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
@@ -31,9 +30,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
import kotlin.time.Duration
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@ constructor(
    /* The last completed [KeyguardState] transition */
    val finishedKeyguardState: Flow<KeyguardState> =
        finishedKeyguardTransitionStep.map { step -> step.to }

    /**
     * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
     * range of [0, 1]. View animations should begin and end within a subset of this range. This
     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
     */
    fun transitionStepAnimation(
        flow: Flow<TransitionStep>,
        params: AnimationParams,
        totalDuration: Duration,
    ): Flow<Float> {
        val start = (params.startTime / totalDuration).toFloat()
        val chunks = (totalDuration / params.duration).toFloat()
        var isRunning = false
        return flow
            .map { step ->
                val value = (step.value - start) * chunks
                if (step.transitionState == STARTED) {
                    // When starting, make sure to always emit. If a transition is started from the
                    // middle, it is possible this animation is being skipped but we need to inform
                    // the ViewModels of the last update
                    isRunning = true
                    max(0f, min(1f, value))
                } else if (isRunning && value >= 1f) {
                    // Always send a final value of 1. Because of rounding, [value] may never be
                    // exactly 1.
                    isRunning = false
                    1f
                } else {
                    value
                }
            }
            .filter { value -> value >= 0f && value <= 1f }
    }
}
+0 −25
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */
package com.android.systemui.keyguard.shared.model

import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

/** Animation parameters */
data class AnimationParams(
    val startTime: Duration = 0.milliseconds,
    val duration: Duration,
)
+106 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */
package com.android.systemui.keyguard.ui

import android.view.animation.Interpolator
import com.android.systemui.animation.Interpolators.LINEAR
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import kotlin.math.max
import kotlin.math.min
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map

/**
 * For the given transition params, construct a flow using [createFlow] for the specified portion of
 * the overall transition.
 */
class KeyguardTransitionAnimationFlow(
    private val transitionDuration: Duration,
    private val transitionFlow: Flow<TransitionStep>,
) {
    /**
     * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in
     * the range of [0, 1]. View animations should begin and end within a subset of this range. This
     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
     */
    fun createFlow(
        duration: Duration,
        onStep: (Float) -> Float,
        startTime: Duration = 0.milliseconds,
        onCancel: (() -> Float)? = null,
        onFinish: (() -> Float)? = null,
        interpolator: Interpolator = LINEAR,
    ): Flow<Float> {
        if (!duration.isPositive()) {
            throw IllegalArgumentException("duration must be a positive number: $duration")
        }
        if ((startTime + duration).compareTo(transitionDuration) > 0) {
            throw IllegalArgumentException(
                "startTime($startTime) + duration($duration) must be" +
                    " <= transitionDuration($transitionDuration)"
            )
        }

        val start = (startTime / transitionDuration).toFloat()
        val chunks = (transitionDuration / duration).toFloat()
        var isComplete = true

        fun stepToValue(step: TransitionStep): Float? {
            val value = (step.value - start) * chunks
            return when (step.transitionState) {
                // When starting, make sure to always emit. If a transition is started from the
                // middle, it is possible this animation is being skipped but we need to inform
                // the ViewModels of the last update
                STARTED -> {
                    isComplete = false
                    max(0f, min(1f, value))
                }
                // Always send a final value of 1. Because of rounding, [value] may never be
                // exactly 1.
                RUNNING ->
                    if (isComplete) {
                        null
                    } else if (value >= 1f) {
                        isComplete = true
                        1f
                    } else if (value >= 0f) {
                        value
                    } else {
                        null
                    }
                else -> null
            }?.let { onStep(interpolator.getInterpolation(it)) }
        }

        return transitionFlow
            .map { step ->
                when (step.transitionState) {
                    STARTED -> stepToValue(step)
                    RUNNING -> stepToValue(step)
                    CANCELED -> onCancel?.invoke()
                    FINISHED -> onFinish?.invoke()
                }
            }
            .filterNotNull()
    }
}
+28 −32
Original line number Diff line number Diff line
@@ -21,15 +21,10 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge

/**
 * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@ class DreamingToLockscreenTransitionViewModel
constructor(
    private val interactor: KeyguardTransitionInteractor,
) {
    private val transitionAnimation =
        KeyguardTransitionAnimationFlow(
            transitionDuration = TO_LOCKSCREEN_DURATION,
            transitionFlow = interactor.dreamingToLockscreenTransition,
        )

    /** Dream overlay y-translation on exit */
    fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
        return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
            EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
        }
        return transitionAnimation.createFlow(
            duration = 600.milliseconds,
            onStep = { it * translatePx },
            interpolator = EMPHASIZED_ACCELERATE,
        )
    }
    /** Dream overlay views alpha - fade out */
    val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
    val dreamOverlayAlpha: Flow<Float> =
        transitionAnimation.createFlow(
            duration = 250.milliseconds,
            onStep = { 1f - it },
        )

    /** Lockscreen views y-translation */
    fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
        return merge(
            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
                -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
            },
            // On end, reset the translation to 0
            interactor.dreamingToLockscreenTransition
                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
                .map { 0f }
        return transitionAnimation.createFlow(
            duration = TO_LOCKSCREEN_DURATION,
            onStep = { value -> -translatePx + value * translatePx },
            // Reset on cancel or finish
            onFinish = { 0f },
            onCancel = { 0f },
            interpolator = EMPHASIZED_DECELERATE,
        )
    }

    /** Lockscreen views alpha */
    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)

    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
        return interactor.transitionStepAnimation(
            interactor.dreamingToLockscreenTransition,
            params,
            totalDuration = TO_LOCKSCREEN_DURATION
    val lockscreenAlpha: Flow<Float> =
        transitionAnimation.createFlow(
            startTime = 233.milliseconds,
            duration = 250.milliseconds,
            onStep = { it },
        )
    }

    companion object {
        /* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@ constructor(
        @JvmField
        val LOCKSCREEN_ANIMATION_DURATION_MS =
            (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds

        val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
        val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
        val LOCKSCREEN_ALPHA =
            AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
    }
}
Loading