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

Commit 226b3284 authored by Evan Laird's avatar Evan Laird
Browse files

[sb] Move status bar animation to binder

Since the visibility of the status bar is controlled by the view binder
now, we have to move the animation logic to the binder as well.

The old system asked views to provide an AnimatorSet that would get
`playTogether()`'d with the chip animator to ensure that all animations
had the same keyframes. This change removes that guarantee, and instead
exposes explicit states from the animation repository and expects that
views can coordinate on their own terms.

For now, this means that we have the possibility that animations could
be out of sync by a frame or so. In practice, that's not an issue. And
the further things move into Compose, the more we can get that kind of
state synchronization back, since the whole view tree can be modeled on
the state of the system event animation repository.

Test: SystemStatusAnimationSchedulerImplTest
Test: atest SystemUITests
Test: manually triggering battery / camera events and checking the
animation
Bug: 364360986
Flag: com.android.systemui.status_bar_root_modernization

Change-Id: I576aa9f3e2b2a45e73f6899aa9b5a6991a322d35
parent 6900d991
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ class FakeHomeStatusBarViewBinder : HomeStatusBarViewBinder {
    override fun bind(
        view: View,
        viewModel: HomeStatusBarViewModel,
        systemEventChipAnimateIn: ((View) -> Unit)?,
        systemEventChipAnimateOut: ((View) -> Unit)?,
        listener: StatusBarVisibilityChangeListener,
    ) {
        this.listener = listener
+8 −4
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.view.View
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,11 +54,14 @@ class FakeHomeStatusBarViewModel : HomeStatusBarViewModel {
            )
        )

    override val isSystemInfoVisible =
    override val systemInfoCombinedVis =
        MutableStateFlow(
            HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
                HomeStatusBarViewModel.VisibilityModel(
                    visibility = View.GONE,
                    shouldAnimateChange = false,
                ),
                Idle,
            )
        )

+61 −30
Original line number Diff line number Diff line
@@ -60,11 +60,16 @@ import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositor
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository
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.Idle
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
@@ -90,6 +95,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
    private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
    private val systemStatusEventAnimationRepository = kosmos.systemStatusEventAnimationRepository

    private lateinit var underTest: HomeStatusBarViewModel

@@ -546,25 +552,50 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
    @Test
    fun isSystemInfoVisible_allowedByDisableFlags_visible() =
        testScope.runTest {
            val latest by collectLastValue(underTest.isSystemInfoVisible)
            val latest by collectLastValue(underTest.systemInfoCombinedVis)
            transitionKeyguardToGone()

            disableFlagsRepository.disableFlags.value =
                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)

            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
        }

    @Test
    fun isSystemInfoVisible_notAllowedByDisableFlags_gone() =
        testScope.runTest {
            val latest by collectLastValue(underTest.isSystemInfoVisible)
            val latest by collectLastValue(underTest.systemInfoCombinedVis)
            transitionKeyguardToGone()

            disableFlagsRepository.disableFlags.value =
                DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE)

            assertThat(latest!!.visibility).isEqualTo(View.GONE)
            assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
    fun systemInfoCombineVis_animationsPassThrough() =
        testScope.runTest {
            val latest by collectLastValue(underTest.systemInfoCombinedVis)
            transitionKeyguardToGone()

            assertThat(latest!!.baseVisibility)
                .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
            assertThat(latest!!.animationState).isEqualTo(Idle)

            // WHEN the animation state changes, but the visibility state doesn't change
            systemStatusEventAnimationRepository.animationState.value = AnimatingIn

            // THEN the visibility is the same
            assertThat(latest!!.baseVisibility)
                .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
            // THEN the animation state updates
            assertThat(latest!!.animationState).isEqualTo(AnimatingIn)

            systemStatusEventAnimationRepository.animationState.value = AnimatingOut
            assertThat(latest!!.baseVisibility)
                .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false))
            assertThat(latest!!.animationState).isEqualTo(AnimatingOut)
        }

    @Test
@@ -573,7 +604,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            keyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
@@ -583,7 +614,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -592,13 +623,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -607,7 +638,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            keyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
@@ -617,7 +648,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -626,13 +657,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -641,7 +672,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            keyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
@@ -651,7 +682,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {

            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
        }

    @Test
@@ -660,14 +691,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)

            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
        }

    @Test
@@ -676,13 +707,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            transitionKeyguardToGone()

            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
        }

    @Test
@@ -691,14 +722,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
            transitionKeyguardToGone()

            kosmos.shadeTestUtil.setShadeExpansion(0f)

            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
        }

    @Test
@@ -707,13 +738,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)

            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
        }

    @Test
@@ -722,14 +753,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
            transitionKeyguardToGone()

            kosmos.shadeTestUtil.setShadeExpansion(1f)

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -738,14 +769,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
            transitionKeyguardToGone()

            kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -754,7 +785,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            // Secure camera is an occluding activity
            keyguardTransitionRepository.sendTransitionSteps(
@@ -766,7 +797,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    @Test
@@ -775,7 +806,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        testScope.runTest {
            val clockVisible by collectLastValue(underTest.isClockVisible)
            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
            val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)

            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
            // Secure camera is an occluding activity
@@ -784,7 +815,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {

            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
            assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
        }

    private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
+4 −1
Original line number Diff line number Diff line
@@ -27,7 +27,10 @@ import kotlinx.coroutines.flow.StateFlow
interface SystemStatusAnimationScheduler :
    CallbackController<SystemStatusAnimationCallback>, Dumpable {

    /** StateFlow holding the current [SystemEventAnimationState] at any time. */
    /**
     * The current state of the animation. This can be used from compose functions to coordinate
     * their animations with the chip
     */
    val animationState: StateFlow<SystemEventAnimationState>

    fun onStatusEvent(event: StatusEvent)
+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.data.repository

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow

/** Repository to expose the [SystemStatusAnimationScheduler] state via flows */
interface SystemStatusEventAnimationRepository {
    val animationState: StateFlow<SystemEventAnimationState>
}

@SysUISingleton
class SystemStatusEventAnimationRepositoryImpl
@Inject
constructor(scheduler: SystemStatusAnimationScheduler) : SystemStatusEventAnimationRepository {
    override val animationState = scheduler.animationState
}
Loading