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

Commit 8a919d98 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topics "cd_state_flexi", "wear_fix_for_shade_window_missing" into main

* changes:
  Fix per-display flags propagation in flexiglass
  Provide empty ShadeDisplaysInteractor impl for sysui variants without shade window
parents f0f805b7 9dbdcb91
Loading
Loading
Loading
Loading
+47 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -61,6 +62,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteract
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.haptics.vibratorHelper
@@ -88,6 +90,7 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.fakeSysUIStatePerDisplayRepository
import com.android.systemui.model.sysUiState
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.data.repository.powerRepository
@@ -103,6 +106,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -128,6 +132,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -162,7 +167,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
    private val sysUiState = kosmos.sysUiState
    private val falsingCollector = mock<FalsingCollector>().also { kosmos.falsingCollector = it }
    private val vibratorHelper = mock<VibratorHelper>().also { kosmos.vibratorHelper = it }
    private val fakeSceneDataSource = kosmos.fakeSceneDataSource
@@ -174,6 +178,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
    private val msdlPlayer = kosmos.fakeMSDLPlayer
    private val authInteractionProperties = AuthInteractionProperties()
    private val mockActivityTransitionAnimator = mock<ActivityTransitionAnimator>()
    private val sysuiStateRepository = kosmos.fakeSysUIStatePerDisplayRepository
    private val sysUiState = sysuiStateRepository[Display.DEFAULT_DISPLAY]!!
    private val secondaryDisplaySysUIState = sysuiStateRepository[SECONDARY_DISPLAY]!!

    private lateinit var underTest: SceneContainerStartable

@@ -183,7 +190,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
        whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
            .thenReturn(true)
        kosmos.activityTransitionAnimator = mockActivityTransitionAnimator

        runBlocking { kosmos.displayRepository.addDisplay(Display.DEFAULT_DISPLAY) }
        underTest = kosmos.sceneContainerStartable
    }

@@ -1163,14 +1170,48 @@ class SceneContainerStartableTest : SysuiTestCase() {
                    sceneInteractor.changeScene(sceneKey, "reason")
                    runCurrent()
                    verify(sysUiState, times(index)).commitUpdate()
                    verify(secondaryDisplaySysUIState, never()).commitUpdate()

                    fakeSceneDataSource.unpause(expectedScene = sceneKey)
                    runCurrent()
                    verify(sysUiState, times(index)).commitUpdate()
                    verify(secondaryDisplaySysUIState, never()).commitUpdate()

                    transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
                    runCurrent()
                    verify(sysUiState, times(index + 1)).commitUpdate()
                    verify(secondaryDisplaySysUIState, never()).commitUpdate()
                }
        }

    @Test
    fun hydrateSystemUiState_onSecondaryDisplay() =
        testScope.runTest {
            val transitionStateFlow = prepareState()
            kosmos.displayRepository.addDisplay(SECONDARY_DISPLAY)
            kosmos.fakeShadeDisplaysRepository.setPendingDisplayId(SECONDARY_DISPLAY)
            underTest.start()
            runCurrent()
            clearInvocations(secondaryDisplaySysUIState)

            listOf(Scenes.Gone, Scenes.Lockscreen, Scenes.Gone, Scenes.Shade, Scenes.QuickSettings)
                .forEachIndexed { index, sceneKey ->
                    if (sceneKey == Scenes.Gone) {
                        updateFingerprintAuthStatus(isSuccess = true)
                        runCurrent()
                    }
                    fakeSceneDataSource.pause()
                    sceneInteractor.changeScene(sceneKey, "reason")
                    runCurrent()
                    verify(secondaryDisplaySysUIState, times(index)).commitUpdate()

                    fakeSceneDataSource.unpause(expectedScene = sceneKey)
                    runCurrent()
                    verify(secondaryDisplaySysUIState, times(index)).commitUpdate()

                    transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
                    runCurrent()
                    verify(secondaryDisplaySysUIState, times(index + 1)).commitUpdate()
                }
        }

@@ -3028,4 +3069,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
            )
        }
    }

    private companion object {
        const val SECONDARY_DISPLAY = Display.DEFAULT_DISPLAY + 1
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -42,6 +42,9 @@ import kotlinx.coroutines.flow.StateFlow
/**
 * A plugin for [SysUiState] that provides overrides for certain state flags that must be pulled
 * from the scene framework when that framework is enabled.
 *
 * Note that those flags only apply to the display id containing the shade window, as defined by
 * [com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor.displayId]
 */
interface SceneContainerPlugin {
    /**
+7 −0
Original line number Diff line number Diff line
@@ -94,4 +94,11 @@ class StateChange {
            )
        })"""
    }

    companion object {
        /** Creates a [StateChange] from a list of pairs. */
        fun from(iterable: Iterable<Pair<Long, Boolean>>): StateChange {
            return StateChange().apply { iterable.forEach { (bit, state) -> setFlag(bit, state) } }
        }
    }
}
+30 −15
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.scene.domain.startable

import android.app.StatusBarManager
import android.view.Display
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
@@ -33,6 +34,7 @@ import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
@@ -48,8 +50,8 @@ import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVi
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.model.SceneContainerPlugin
import com.android.systemui.model.SceneContainerPluginImpl
import com.android.systemui.model.StateChange
import com.android.systemui.model.SysUiState
import com.android.systemui.model.updateFlags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -67,8 +69,10 @@ import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.VibratorHelper
@@ -122,7 +126,6 @@ constructor(
    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
    private val bouncerInteractor: BouncerInteractor,
    private val keyguardInteractor: KeyguardInteractor,
    private val sysUiState: SysUiState,
    private val sceneLogger: SceneLogger,
    @FalsingCollectorActual private val falsingCollector: FalsingCollector,
    private val falsingManager: FalsingManager,
@@ -150,12 +153,21 @@ constructor(
    private val shadeModeInteractor: ShadeModeInteractor,
    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
    private val trustInteractor: TrustInteractor,
    private val sysuiStateInteractor: SysUIStateDisplaysInteractor,
    private val shadeDisplaysInteractor: Lazy<ShadeDisplaysInteractor>,
) : CoreStartable {
    private val centralSurfaces: CentralSurfaces?
        get() = centralSurfacesOptLazy.get().getOrNull()

    private val authInteractionProperties = AuthInteractionProperties()

    private val shadePendingDisplayId: Flow<Int> =
        if (ShadeWindowGoesAround.isEnabled) {
            shadeDisplaysInteractor.get().pendingDisplayId
        } else {
            flowOf(Display.DEFAULT_DISPLAY)
        }

    override fun start() {
        if (SceneContainerFlag.isEnabled) {
            sceneLogger.logFrameworkEnabled(isEnabled = true)
@@ -759,24 +771,27 @@ constructor(
                        .distinctUntilChanged(),
                    sceneInteractor.isVisible,
                    occlusionInteractor.invisibleDueToOcclusion,
                ) { idleState, isVisible, invisibleDueToOcclusion ->
                    shadePendingDisplayId,
                ) { idleState, isVisible, invisibleDueToOcclusion, displayId ->
                    displayId to
                        SceneContainerPlugin.SceneContainerPluginState(
                            scene = idleState.currentScene,
                            overlays = idleState.currentOverlays,
                        isVisible = isVisible,
                            invisibleDueToOcclusion = invisibleDueToOcclusion,
                            isVisible = isVisible,
                        )
                }
                .map { sceneContainerPluginState ->
                .map { (displayId, sceneContainerPluginState) ->
                    displayId to
                        SceneContainerPluginImpl.EvaluatorByFlag.map { (flag, evaluator) ->
                            flag to evaluator(sceneContainerPluginState)
                        }
                        .toMap()
                }
                .distinctUntilChanged()
                .collect { flags ->
                    sysUiState.updateFlags(
                        *(flags.entries.map { (key, value) -> key to value }).toTypedArray()
                .collect { (displayId: Int, flagMap) ->
                    sysuiStateInteractor.setFlagsExclusivelyToDisplay(
                        targetDisplayId = displayId,
                        stateChanges = StateChange.from(flagMap),
                    )
                }
        }
+49 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.shade
import android.content.Context
import android.content.res.Resources
import android.os.Bundle
import android.view.Display
import android.view.LayoutInflater
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
@@ -49,6 +50,7 @@ import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeDisplaysDialogInteractor
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractorImpl
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHider
import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHiderImpl
@@ -65,6 +67,8 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Provider
import javax.inject.Qualifier
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/**
 * Module responsible for managing display-specific components and resources for the notification
@@ -324,13 +328,25 @@ object ShadeDisplayAwareModule {
    }
}

/** Module that should be included only if the shade window [WindowRootView] is available. */
/**
 * Module that should be included only if the shade window [WindowRootView] is available.
 *
 * This includes SystemUIGoogle variant.
 */
@Module
object ShadeDisplayAwareWithShadeWindowModule {

    @Provides
    @SysUISingleton
    fun bindShadeDisplaysInteractor(impl: ShadeDisplaysInteractorImpl): ShadeDisplaysInteractor =
        impl

    @Provides
    @IntoMap
    @ClassKey(ShadeDisplaysInteractor::class)
    fun provideShadeDisplaysInteractor(impl: Provider<ShadeDisplaysInteractor>): CoreStartable {
    @ClassKey(ShadeDisplaysInteractorImpl::class)
    fun provideShadeDisplaysInteractorCoreStartable(
        impl: Provider<ShadeDisplaysInteractorImpl>
    ): CoreStartable {
        return if (ShadeWindowGoesAround.isEnabled) {
            impl.get()
        } else {
@@ -350,6 +366,36 @@ object ShadeDisplayAwareWithShadeWindowModule {
        impl
}

/**
 * Dagger module to be included in Android variants where the `WindowRootView` (responsible for the
 * movable shade window) is NOT present, such as Wear OS or Android TV.
 *
 * Since `SystemUIModule` is common to all variants, some of its bound classes may have dependencies
 * expecting the shade window. This module ensures these dependencies are satisfied with no-op
 * implementations when the shade window is unavailable.
 *
 * Ideally, classes having WindowRootView dependencies shouldn't be instantiated at all in variants
 * that don't provide it, but sometimes this is not possible or too complicated.
 *
 * Making a concrete example might help understanding this: the Wear of sysui seems to be including
 * [SceneContainerFrameworkModule] that has some classes depending to the shade window and its
 * position. While it's unclear why Wear needs to depend on the Scene container, providing here a
 * no-op [ShadeDisplaysInteractor] will guarantee no classes depending on the WindowRootView are
 * created (as the window root view is not available).
 */
@Module
object ShadeDisplayAwareWindowWithoutShadeModule {

    @Provides
    @SysUISingleton
    fun bindShadeDisplaysInteractor(): ShadeDisplaysInteractor =
        object : ShadeDisplaysInteractor {
            override val displayId: StateFlow<Int> = MutableStateFlow(Display.DEFAULT_DISPLAY)
            override val pendingDisplayId: StateFlow<Int> =
                MutableStateFlow(Display.DEFAULT_DISPLAY)
        }
}

/**
 * Annotates the boolean value that defines whether the shade window should go back to the default
 * display when the keyguard is visible.
Loading