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

Commit 49cf41db authored by Evan Laird's avatar Evan Laird Committed by Android (Google) Code Review
Browse files

Merge changes Ia56f8ea6,I478d974f into main

* changes:
  [sb] fix lint errors in SystemStatusAnimationSchedulerImplTest
  [events] move SystemAnimationState to enum, expose flow
parents 7481be3c 90690102
Loading
Loading
Loading
Loading
+4 −30
Original line number Diff line number Diff line
@@ -16,17 +16,19 @@

package com.android.systemui.statusbar.events

import android.annotation.IntDef
import androidx.core.animation.Animator
import androidx.core.animation.AnimatorSet
import androidx.core.animation.PathInterpolator
import com.android.systemui.Dumpable
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.policy.CallbackController
import kotlinx.coroutines.flow.StateFlow

interface SystemStatusAnimationScheduler :
    CallbackController<SystemStatusAnimationCallback>, Dumpable {

    @SystemAnimationState fun getAnimationState(): Int
    /** StateFlow holding the current [SystemEventAnimationState] at any time. */
    val animationState: StateFlow<SystemEventAnimationState>

    fun onStatusEvent(event: StatusEvent)

@@ -63,34 +65,6 @@ interface SystemStatusAnimationCallback {
    }
}

/** Animation state IntDef */
@Retention(AnnotationRetention.SOURCE)
@IntDef(
    value =
        [
            IDLE,
            ANIMATION_QUEUED,
            ANIMATING_IN,
            RUNNING_CHIP_ANIM,
            ANIMATING_OUT,
            SHOWING_PERSISTENT_DOT,
        ]
)
annotation class SystemAnimationState

/** No animation is in progress */
@SystemAnimationState const val IDLE = 0
/** An animation is queued, and awaiting the debounce period */
const val ANIMATION_QUEUED = 1
/** System is animating out, and chip is animating in */
const val ANIMATING_IN = 2
/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */
const val RUNNING_CHIP_ANIM = 3
/** Chip is animating away and system is animating back */
const val ANIMATING_OUT = 4
/** Chip has animated away, and the persistent dot is showing */
const val SHOWING_PERSISTENT_DOT = 5

/** Commonly-needed interpolators can go here */
@JvmField val STATUS_BAR_X_MOVE_OUT = PathInterpolator(0.33f, 0f, 0f, 1f)
@JvmField val STATUS_BAR_X_MOVE_IN = PathInterpolator(0f, 0f, 0f, 1f)
+30 −25
Original line number Diff line number Diff line
@@ -23,6 +23,12 @@ import androidx.core.animation.AnimatorListenerAdapter
import androidx.core.animation.AnimatorSet
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimationQueued
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.RunningChipAnim
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.ShowingPersistentDot
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.util.Assert
import com.android.systemui.util.time.SystemClock
@@ -33,6 +39,7 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.first
@@ -85,8 +92,8 @@ constructor(
     */
    private var currentlyDisplayedEvent: StatusEvent? = null

    /** StateFlow holding the current [SystemAnimationState] at any time. */
    private var animationState = MutableStateFlow(IDLE)
    private val _animationState = MutableStateFlow(Idle)
    override val animationState = _animationState.asStateFlow()

    /** True if the persistent privacy dot should be active */
    var hasPersistentDot = false
@@ -109,24 +116,22 @@ constructor(
            // Wait for animationState to become ANIMATION_QUEUED and scheduledEvent to be non null.
            // Once this combination is stable for at least DEBOUNCE_DELAY, then start a chip enter
            // animation
            animationState
            _animationState
                .combine(scheduledEvent) { animationState, scheduledEvent ->
                    Pair(animationState, scheduledEvent)
                }
                .debounce(DEBOUNCE_DELAY)
                .collect { (animationState, event) ->
                    if (animationState == ANIMATION_QUEUED && event != null) {
                    if (animationState == AnimationQueued && event != null) {
                        startAnimationLifecycle(event)
                        scheduledEvent.value = null
                    }
                }
        }

        coroutineScope.launch { animationState.collect { logger?.logAnimationStateUpdate(it) } }
        coroutineScope.launch { _animationState.collect { logger?.logAnimationStateUpdate(it) } }
    }

    @SystemAnimationState override fun getAnimationState(): Int = animationState.value

    override fun onStatusEvent(event: StatusEvent) {
        Assert.isMainThread()

@@ -146,11 +151,11 @@ constructor(
            logger?.logScheduleEvent(event)
            scheduleEvent(event)
        } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) {
            logger?.logUpdateEvent(event, animationState.value)
            logger?.logUpdateEvent(event, _animationState.value)
            currentlyDisplayedEvent?.updateFromEvent(event)
            if (event.forceVisible) hasPersistentDot = true
        } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
            logger?.logUpdateEvent(event, animationState.value)
            logger?.logUpdateEvent(event, _animationState.value)
            scheduledEvent.value?.updateFromEvent(event)
        } else {
            logger?.logIgnoreEvent(event)
@@ -170,15 +175,15 @@ constructor(
        // the disappear animation will not animate into a dot but remove the chip entirely
        hasPersistentDot = false

        if (animationState.value == SHOWING_PERSISTENT_DOT) {
        if (_animationState.value == ShowingPersistentDot) {
            // if we are currently showing a persistent dot, hide it and update the animationState
            notifyHidePersistentDot()
            if (scheduledEvent.value != null) {
                animationState.value = ANIMATION_QUEUED
                _animationState.value = AnimationQueued
            } else {
                animationState.value = IDLE
                _animationState.value = Idle
            }
        } else if (animationState.value == ANIMATING_OUT) {
        } else if (_animationState.value == AnimatingOut) {
            // if we are currently animating out, hide the dot. The animationState will be updated
            // once the animation has ended in the onAnimationEnd callback
            notifyHidePersistentDot()
@@ -206,9 +211,9 @@ constructor(
            cancelCurrentlyDisplayedEvent()
            return
        }
        if (animationState.value == IDLE) {
        if (_animationState.value == Idle) {
            // If we are in IDLE state, set it to ANIMATION_QUEUED now
            animationState.value = ANIMATION_QUEUED
            _animationState.value = AnimationQueued
        }
    }

@@ -223,7 +228,7 @@ constructor(
                withTimeout(APPEAR_ANIMATION_DURATION) {
                    // wait for animationState to become RUNNING_CHIP_ANIM, then cancel the running
                    // animation job and run the disappear animation immediately
                    animationState.first { it == RUNNING_CHIP_ANIM }
                    _animationState.first { it == RunningChipAnim }
                    currentlyRunningAnimationJob?.cancel()
                    runChipDisappearAnimation()
                }
@@ -241,7 +246,7 @@ constructor(

        if (!event.showAnimation && event.forceVisible) {
            // If animations are turned off, we'll transition directly to the dot
            animationState.value = SHOWING_PERSISTENT_DOT
            _animationState.value = ShowingPersistentDot
            notifyTransitionToPersistentDot(event)
            return
        }
@@ -277,7 +282,7 @@ constructor(
        if (hasPersistentDot) {
            statusBarWindowControllerStore.defaultDisplay.setForceStatusBarVisible(true)
        }
        animationState.value = ANIMATING_IN
        _animationState.value = AnimatingIn

        val animSet = collectStartAnimations()
        if (animSet.totalDuration > 500) {
@@ -289,7 +294,7 @@ constructor(
        animSet.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    animationState.value = RUNNING_CHIP_ANIM
                    _animationState.value = RunningChipAnim
                }
            }
        )
@@ -299,15 +304,15 @@ constructor(
    private fun runChipDisappearAnimation() {
        Assert.isMainThread()
        val animSet2 = collectFinishAnimations()
        animationState.value = ANIMATING_OUT
        _animationState.value = AnimatingOut
        animSet2.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    animationState.value =
                    _animationState.value =
                        when {
                            hasPersistentDot -> SHOWING_PERSISTENT_DOT
                            scheduledEvent.value != null -> ANIMATION_QUEUED
                            else -> IDLE
                            hasPersistentDot -> ShowingPersistentDot
                            scheduledEvent.value != null -> AnimationQueued
                            else -> Idle
                        }
                    statusBarWindowControllerStore.defaultDisplay.setForceStatusBarVisible(false)
                }
@@ -401,7 +406,7 @@ constructor(
        pw.println("Scheduled event: ${scheduledEvent.value}")
        pw.println("Currently displayed event: $currentlyDisplayedEvent")
        pw.println("Has persistent privacy dot: $hasPersistentDot")
        pw.println("Animation state: ${animationState.value}")
        pw.println("Animation state: ${_animationState.value}")
        pw.println("Listeners:")
        if (listeners.isEmpty()) {
            pw.println("(none)")
+11 −24
Original line number Diff line number Diff line
@@ -3,15 +3,14 @@ package com.android.systemui.statusbar.events
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import javax.inject.Inject

/** Logs for the SystemStatusAnimationScheduler. */
@SysUISingleton
class SystemStatusAnimationSchedulerLogger
@Inject
constructor(
    @SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer,
) {
constructor(@SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer) {

    fun logScheduleEvent(event: StatusEvent) {
        logBuffer.log(
@@ -23,11 +22,11 @@ constructor(
                bool1 = event.forceVisible
                bool2 = event.showAnimation
            },
            { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
            { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" },
        )
    }

    fun logUpdateEvent(event: StatusEvent, @SystemAnimationState animationState: Int) {
    fun logUpdateEvent(event: StatusEvent, animationState: SystemEventAnimationState) {
        logBuffer.log(
            TAG,
            LogLevel.DEBUG,
@@ -36,12 +35,12 @@ constructor(
                int1 = event.priority
                bool1 = event.forceVisible
                bool2 = event.showAnimation
                int2 = animationState
                str2 = animationState.name
            },
            {
                "Updating current event from: $str1(forceVisible=$bool1, priority=$int1, " +
                    "showAnimation=$bool2), animationState=${animationState.name()}"
            }
                    "showAnimation=$bool2), animationState=$str2"
            },
        )
    }

@@ -55,7 +54,7 @@ constructor(
                bool1 = event.forceVisible
                bool2 = event.showAnimation
            },
            { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
            { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" },
        )
    }

@@ -67,25 +66,13 @@ constructor(
        logBuffer.log(TAG, LogLevel.DEBUG, "Transition to persistent dot callback invoked")
    }

    fun logAnimationStateUpdate(@SystemAnimationState animationState: Int) {
    fun logAnimationStateUpdate(animationState: SystemEventAnimationState) {
        logBuffer.log(
            TAG,
            LogLevel.DEBUG,
            { int1 = animationState },
            { "AnimationState update: ${int1.name()}" }
            { str1 = animationState.name },
            { "AnimationState update: $str1" },
        )
        animationState.name()
    }

    private fun @receiver:SystemAnimationState Int.name() =
        when (this) {
            IDLE -> "IDLE"
            ANIMATION_QUEUED -> "ANIMATION_QUEUED"
            ANIMATING_IN -> "ANIMATING_IN"
            RUNNING_CHIP_ANIM -> "RUNNING_CHIP_ANIM"
            ANIMATING_OUT -> "ANIMATING_OUT"
            SHOWING_PERSISTENT_DOT -> "SHOWING_PERSISTENT_DOT"
            else -> "UNKNOWN_ANIMATION_STATE"
    }
}

+35 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.statusbar.events.shared.model

/** Direct representation of the system event animation scheduler's current state */
enum class SystemEventAnimationState {
    /** No animation is in progress */
    Idle,
    /** An animation is queued, and awaiting the debounce period */
    AnimationQueued,
    /** System is animating out, and chip is animating in */
    AnimatingIn,
    /**
     * Chip has animated in and is awaiting exit animation, and optionally playing its own animation
     */
    RunningChipAnim,
    /** Chip is animating away and system is animating back */
    AnimatingOut,
    /** Chip has animated away, and the persistent dot is showing */
    ShowingPersistentDot,
}
+16 −17
Original line number Diff line number Diff line
@@ -65,7 +65,6 @@ import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.events.ANIMATING_OUT
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -176,7 +175,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
            BiometricStatusInteractorImpl(
                activityTaskManager,
                biometricStatusRepository,
                fingerprintRepository
                fingerprintRepository,
            )
        iconProvider = IconProvider(context)
        // Set up default logo icon
@@ -245,7 +244,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
    @Test
    fun testIgnoresAnimatedInWhenDialogAnimatingOut() {
        val container = initializeFingerprintContainer(addToView = false)
        container.mContainerState = ANIMATING_OUT
        container.mContainerState = 4 // STATE_ANIMATING_OUT
        container.addToView()
        waitForIdleSync()

@@ -278,7 +277,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
            .onDismissed(
                eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
                eq<ByteArray?>(null), /* credentialAttestation */
                eq(authContainer?.requestId ?: 0L)
                eq(authContainer?.requestId ?: 0L),
            )
        assertThat(container.parent).isNull()
    }
@@ -292,13 +291,13 @@ open class AuthContainerViewTest : SysuiTestCase() {
        verify(callback)
            .onSystemEvent(
                eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL),
                eq(authContainer?.requestId ?: 0L)
                eq(authContainer?.requestId ?: 0L),
            )
        verify(callback)
            .onDismissed(
                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
                eq<ByteArray?>(null), /* credentialAttestation */
                eq(authContainer?.requestId ?: 0L)
                eq(authContainer?.requestId ?: 0L),
            )
        assertThat(container.parent).isNull()
    }
@@ -313,7 +312,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
            .onDismissed(
                eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
                eq<ByteArray?>(null), /* credentialAttestation */
                eq(authContainer?.requestId ?: 0L)
                eq(authContainer?.requestId ?: 0L),
            )
        assertThat(container.parent).isNull()
    }
@@ -340,7 +339,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
            .onDismissed(
                eq(AuthDialogCallback.DISMISSED_ERROR),
                eq<ByteArray?>(null), /* credentialAttestation */
                eq(authContainer?.requestId ?: 0L)
                eq(authContainer?.requestId ?: 0L),
            )
        assertThat(authContainer!!.parent).isNull()
    }
@@ -454,7 +453,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
        val container =
            initializeFingerprintContainer(
                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
                verticalListContentView = PromptVerticalListContentView.Builder().build()
                verticalListContentView = PromptVerticalListContentView.Builder().build(),
            )
        // Two-step credential view should show -
        // 1. biometric prompt without sensor 2. credential view ui
@@ -479,7 +478,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
        val container =
            initializeFingerprintContainer(
                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
                contentViewWithMoreOptionsButton = contentView
                contentViewWithMoreOptionsButton = contentView,
            )
        waitForIdleSync()

@@ -565,7 +564,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
    }

    private fun initializeCredentialPasswordContainer(
        addToView: Boolean = true,
        addToView: Boolean = true
    ): TestAuthContainerView {
        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20)))
@@ -597,25 +596,25 @@ open class AuthContainerViewTest : SysuiTestCase() {
                fingerprintProps = fingerprintSensorPropertiesInternal(),
                verticalListContentView = verticalListContentView,
            ),
            addToView
            addToView,
        )

    private fun initializeCoexContainer(
        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
        addToView: Boolean = true
        addToView: Boolean = true,
    ) =
        initializeContainer(
            TestAuthContainerView(
                authenticators = authenticators,
                fingerprintProps = fingerprintSensorPropertiesInternal(),
                faceProps = faceSensorPropertiesInternal()
                faceProps = faceSensorPropertiesInternal(),
            ),
            addToView
            addToView,
        )

    private fun initializeContainer(
        view: TestAuthContainerView,
        addToView: Boolean
        addToView: Boolean,
    ): TestAuthContainerView {
        authContainer = view

@@ -668,7 +667,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
                biometricStatusInteractor,
                udfpsUtils,
                iconProvider,
                activityTaskManager
                activityTaskManager,
            ),
            { credentialViewModel },
            fakeExecutor,
Loading