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

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

Merge "[DesktopStatusBar] Make the shadeOverlays behave like desktop panels" into main

parents d0bc6945 e6849c14
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.DefaultEdgeDetector
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.desktop.domain.interactor.enableDesktopFeatureSet
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -430,4 +431,21 @@ class SceneContainerViewModelTest : SysuiTestCase() {
            assertThat(underTest.swipeSourceDetector)
                .isInstanceOf(SceneContainerSwipeDetector::class.java)
        }

    @Test
    fun onEmptySpaceMotionEvent_hidesDualShadeOverlays_onDesktopMode() =
        kosmos.runTest {
            // GIVEN a device in desktop mode with dual shade enabled and an overlay present
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            enableDualShade()
            enableDesktopFeatureSet()
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
            assertThat(currentOverlays).isNotEmpty()

            // WHEN a touch event occurs outside the shade window
            underTest.onEmptySpaceMotionEvent(MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0))

            // THEN the overlay is hidden
            assertThat(currentOverlays).isEmpty()
        }
}
+128 −47
Original line number Diff line number Diff line
@@ -16,65 +16,81 @@

package com.android.systemui.statusbar.phone

import android.content.res.Configuration
import android.content.testableContext
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.compose.animation.scene.OverlayKey
import com.android.internal.policy.SystemBarUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.data.repository.setSceneTransition
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.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeMode
import com.android.systemui.shade.notificationShadeWindowView
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.getValue
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.whenever

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeTouchableRegionManagerTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val sceneRepository = kosmos.sceneContainerRepository

    private val underTest by Lazy { kosmos.shadeTouchableRegionManager }
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val Kosmos.underTest by Kosmos.Fixture { kosmos.shadeTouchableRegionManager }

    @Before
    fun setUp() {
        kosmos.notificationShadeWindowView.apply {
            whenever(width).thenReturn(1000)
            whenever(height).thenReturn(1000)
        }
        kosmos.underTest.setup(kosmos.notificationShadeWindowView)
    }

    @Test
    @EnableSceneContainer
    fun entireScreenTouchable_sceneContainerEnabled_isRemoteUserInteractionOngoing() =
        testScope.runTest {
            sceneRepository.setTransitionState(
                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
            )
            runCurrent()
        kosmos.runTest {
            sceneContainerRepository.setTransitionState(flowOf(Idle(currentScene = Scenes.Gone)))
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()

            sceneRepository.isRemoteUserInputOngoing.value = true
            runCurrent()
            sceneContainerRepository.isRemoteUserInputOngoing.value = true
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()

            sceneRepository.isRemoteUserInputOngoing.value = false
            runCurrent()
            sceneContainerRepository.isRemoteUserInputOngoing.value = false
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
        }

    @Test
    @DisableSceneContainer
    fun entireScreenTouchable_sceneContainerDisabled_isRemoteUserInteractionOngoing() =
        testScope.runTest {
        kosmos.runTest {
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()

            sceneRepository.isRemoteUserInputOngoing.value = true
            runCurrent()
            sceneContainerRepository.isRemoteUserInputOngoing.value = true

            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
        }
@@ -82,36 +98,24 @@ class ShadeTouchableRegionManagerTest : SysuiTestCase() {
    @Test
    @EnableSceneContainer
    fun entireScreenTouchable_sceneContainerEnabled_isIdleOnGone() =
        testScope.runTest {
            sceneRepository.setTransitionState(
                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
            )
            runCurrent()
        kosmos.runTest {
            sceneContainerRepository.setTransitionState(flowOf(Idle(currentScene = Scenes.Gone)))
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()

            sceneRepository.setTransitionState(
                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade))
            )
            runCurrent()
            sceneContainerRepository.setTransitionState(flowOf(Idle(currentScene = Scenes.Shade)))
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()

            sceneRepository.setTransitionState(
                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
            )
            runCurrent()
            sceneContainerRepository.setTransitionState(flowOf(Idle(currentScene = Scenes.Gone)))
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
        }

    @Test
    @DisableSceneContainer
    fun entireScreenTouchable_sceneContainerDisabled_isIdleOnGone() =
        testScope.runTest {
        kosmos.runTest {
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()

            sceneRepository.setTransitionState(
                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade))
            )
            runCurrent()
            sceneContainerRepository.setTransitionState(flowOf(Idle(currentScene = Scenes.Shade)))

            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
        }
@@ -119,17 +123,94 @@ class ShadeTouchableRegionManagerTest : SysuiTestCase() {
    @Test
    @DisableSceneContainer
    fun entireScreenTouchable_communalVisible() =
        testScope.runTest {
        kosmos.runTest {
            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()

            kosmos.fakeCommunalSceneRepository.instantlyTransitionTo(CommunalScenes.Communal)
            runCurrent()

            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()

            kosmos.fakeCommunalSceneRepository.instantlyTransitionTo(CommunalScenes.Blank)
            runCurrent()

            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
        }

    @Test
    @EnableSceneContainer
    fun entireScreenTouchable_desktopMode() =
        kosmos.runTest {
            enableStatusBarForDesktop()
            openShadeOverlay(Overlays.QuickSettingsShade)

            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
        }

    @Test
    @EnableSceneContainer
    fun calculateTouchableRegionForDesktop_sceneGone_withShadeBounds() =
        kosmos.runTest {
            val bounds = Rect(0, 0, 100, 100)
            enableStatusBarForDesktop()
            lockDevice()
            unlockDevice()
            openShadeOverlay(Overlays.QuickSettingsShade)
            shadeInteractor.setShadeOverlayBounds(bounds)

            val rects = underTest.calculateTouchableRegionForDesktop()

            assertThat(rects).containsExactly(bounds)
        }

    @Test
    @EnableSceneContainer
    fun calculateTouchableRegionForDesktop_sceneVisible_withoutShadeBounds() =
        kosmos.runTest {
            enableStatusBarForDesktop()
            lockDevice()
            shadeInteractor.setShadeOverlayBounds(null)
            val statusBarHeight = SystemBarUtils.getStatusBarHeight(mContext)
            val expectedRect = Rect(0, statusBarHeight, 1000, 1000)

            val rects = underTest.calculateTouchableRegionForDesktop()

            assertThat(rects).containsExactly(expectedRect)
        }

    private fun Kosmos.openShadeOverlay(overlay: OverlayKey) {
        val shadeMode by collectLastValue(shadeMode)
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
        val initialScene = checkNotNull(currentScene)
        assertThat(shadeMode).isEqualTo(ShadeMode.Dual)

        sceneInteractor.showOverlay(overlay, "test")
        setSceneTransition(Idle(initialScene, checkNotNull(currentOverlays)))
        assertThat(currentScene).isEqualTo(initialScene)
        assertThat(currentOverlays).contains(overlay)
    }

    private fun Kosmos.closeShadeOverlay(overlay: OverlayKey) {
        sceneInteractor.hideOverlay(overlay, "test")
    }

    private fun Kosmos.enableStatusBarForDesktop() {
        enableDualShade()
        testableContext.orCreateTestableResources.addOverride(
            R.bool.config_enableDesktopFeatureSet,
            true,
        )
        configurationController.onConfigurationChanged(Configuration())
    }

    private fun Kosmos.unlockDevice() {
        fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
            SuccessFingerprintAuthenticationStatus(0, true)
        )
        sceneInteractor.changeScene(Scenes.Gone, "unlock")
        sceneContainerRepository.setTransitionState(flowOf(Idle(Scenes.Gone)))
    }

    private fun Kosmos.lockDevice() {
        sceneContainerRepository.instantlyTransitionTo(Scenes.Lockscreen)
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ constructor(
    configurationController: ConfigurationController,
) {

    // TODO(441100057): This StateFlow should support Connected Displays.
    /** Whether the desktop feature set is enabled. */
    val isDesktopFeatureSetEnabled: StateFlow<Boolean> =
        configurationController.onConfigChanged
+28 −2
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.desktop.domain.interactor.DesktopInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
@@ -40,7 +41,6 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.qs.panels.ui.viewmodel.AnimateQsTilesViewModel
import com.android.systemui.statusbar.notification.domain.interactor.NotificationContainerInteractor
import com.android.systemui.scene.domain.interactor.OnBootTransitionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.logger.SceneLogger
@@ -49,7 +49,9 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.core.StatusBarForDesktop
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationContainerInteractor
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
import dagger.assisted.Assisted
@@ -66,12 +68,13 @@ class SceneContainerViewModel
@AssistedInject
constructor(
    private val sceneInteractor: SceneInteractor,
    private val desktopInteractor: DesktopInteractor,
    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
    private val falsingInteractor: FalsingInteractor,
    private val powerInteractor: PowerInteractor,
    private val onBootTransitionInteractor: OnBootTransitionInteractor,
    private val shadeModeInteractor: ShadeModeInteractor,
    private val notificationContainerInteractor: NotificationContainerInteractor,
    shadeModeInteractor: ShadeModeInteractor,
    private val remoteInputInteractor: RemoteInputInteractor,
    private val logger: SceneLogger,
    hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
@@ -124,6 +127,16 @@ constructor(
            initialValue = 1f,
        )

    private val isDesktopStatusBarEnabled by
        hydrator.hydratedStateOf(
            traceName = "isDesktopStatusBarEnabled",
            source =
                desktopInteractor.isDesktopFeatureSetEnabled.map { enabled ->
                    enabled && StatusBarForDesktop.isEnabled
                },
            initialValue = false,
        )

    override suspend fun onActivated(): Nothing {
        try {
            // Sends a MotionEventHandler to the owner of the view-model so they can report
@@ -193,6 +206,19 @@ constructor(
     * Call this after the [MotionEvent] has finished propagating through the UI hierarchy.
     */
    fun onEmptySpaceMotionEvent(event: MotionEvent) {
        // Hide dual shade overlays when there is a touch outside the shade window.
        // This is only applicable when the desktop status bar is enabled.
        if (
            shadeModeInteractor.isDualShade &&
                isDesktopStatusBarEnabled &&
                event.action == MotionEvent.ACTION_OUTSIDE &&
                sceneInteractor.currentOverlays.value.isNotEmpty()
        ) {
            sceneInteractor.currentOverlays.value.forEach {
                sceneInteractor.hideOverlay(it, "Empty space touch")
            }
        }

        // check if the touch is outside the window and if remote input is active.
        // If true, close any active remote inputs.
        if (
+172 −28

File changed.

Preview size limit exceeded, changes collapsed.

Loading