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

Commit c1406cd8 authored by András Kurucz's avatar András Kurucz
Browse files

[Dual Shade] Clip Notifications by the QS panel

In Dual Shade there are some scenarios when the QS panel covers
Notifications like:
 - opening the QS panel over the LockScreen
 - opening the QS panel over an open Notificatins panel

While the NotificationsShadeOverlay or non-notification content on the
Lockscreen is covered by the QuickSettingsShadeOverlay, the NSSL still
draws content above the QS. To solve this, clip the NSSL's content when
it is covered by the QS panel.

Bug: 387961133
Test: expand QS over LS -> the QS panel covers Notifications
Flag: com.android.systemui.dual_shade
Change-Id: If931707475730e092fcbd588666906bf1ebc1297
parent 750aed45
Loading
Loading
Loading
Loading
+31 −2
Original line number Diff line number Diff line
@@ -31,10 +31,14 @@ import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
@@ -59,6 +63,8 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
@@ -96,13 +102,37 @@ constructor(
    override fun ContentScope.Content(modifier: Modifier) {
        val viewModel =
            rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
        val panelCornerRadius =
            with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }

        // set the bounds to null when the QuickSettings overlay disappears
        DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }

        OverlayShade(
            panelAlignment = Alignment.TopEnd,
            modifier = modifier,
            onScrimClicked = viewModel::onScrimClicked,
        ) {
            Column {
            Column(
                modifier =
                    Modifier.onPlaced { coordinates ->
                        val boundsInWindow = coordinates.boundsInWindow()
                        val shadeScrimBounds =
                            ShadeScrimBounds(
                                left = boundsInWindow.left,
                                top = boundsInWindow.top,
                                right = boundsInWindow.right,
                                bottom = boundsInWindow.bottom,
                            )
                        val shape =
                            ShadeScrimShape(
                                bounds = shadeScrimBounds,
                                topRadius = 0,
                                bottomRadius = panelCornerRadius,
                            )
                        viewModel.onPanelShapeChanged(shape)
                    }
            ) {
                if (viewModel.showHeader) {
                    CollapsedShadeHeader(
                        viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -112,7 +142,6 @@ constructor(
                        statusBarIconController = statusBarIconController,
                    )
                }

                ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
            }

+20 −0
Original line number Diff line number Diff line
@@ -39,6 +39,9 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -143,6 +146,23 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
            assertThat(underTest.showHeader).isFalse()
        }

    @Test
    fun onPanelShapeChanged() =
        testScope.runTest {
            val actual by
                collectLastValue(kosmos.notificationStackAppearanceInteractor.qsPanelShape)
            val expected =
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
                    topRadius = 0,
                    bottomRadius = 100,
                )

            underTest.onPanelShapeChanged(expected)

            assertThat(expected).isEqualTo(actual)
        }

    private fun TestScope.lockDevice() {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        kosmos.powerInteractor.setAsleepForTest()
+4 −3
Original line number Diff line number Diff line
@@ -74,7 +74,8 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
        testScope.runTest {
            val radius = MutableStateFlow(32)
            val leftOffset = MutableStateFlow(0)
            val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset))
            val shape by
                collectLastValue(scrollViewModel.notificationScrimShape(radius, leftOffset))

            // When: receive scrim bounds
            placeholderViewModel.onScrimBoundsChanged(
@@ -87,7 +88,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
                        bounds =
                            ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
                        topRadius = 32,
                        bottomRadius = 0
                        bottomRadius = 0,
                    )
                )

@@ -104,7 +105,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
                        bounds =
                            ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
                        topRadius = 24,
                        bottomRadius = 0
                        bottomRadius = 0,
                    )
                )

+36 −20
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -40,27 +41,38 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
    private val underTest = kosmos.notificationStackAppearanceInteractor

    @Test
    fun stackBounds() =
    fun stackNotificationScrimBounds() =
        testScope.runTest {
            val stackBounds by collectLastValue(underTest.shadeScrimBounds)
            val stackBounds by collectLastValue(underTest.notificationShadeScrimBounds)

            val bounds1 =
                ShadeScrimBounds(
                    top = 100f,
                    bottom = 200f,
                )
            underTest.setShadeScrimBounds(bounds1)
            val bounds1 = ShadeScrimBounds(top = 100f, bottom = 200f)
            underTest.setNotificationShadeScrimBounds(bounds1)
            assertThat(stackBounds).isEqualTo(bounds1)

            val bounds2 =
                ShadeScrimBounds(
                    top = 200f,
                    bottom = 300f,
                )
            underTest.setShadeScrimBounds(bounds2)
            val bounds2 = ShadeScrimBounds(top = 200f, bottom = 300f)
            underTest.setNotificationShadeScrimBounds(bounds2)
            assertThat(stackBounds).isEqualTo(bounds2)
        }

    @Test
    fun setQsPanelShape() =
        testScope.runTest {
            val actual by collectLastValue(underTest.qsPanelShape)

            val expected1 =
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(top = 0f, bottom = 100f),
                    topRadius = 0,
                    bottomRadius = 10,
                )
            underTest.setQsPanelShape(expected1)
            assertThat(actual).isEqualTo(expected1)

            val expected2 = expected1.copy(topRadius = 10)
            underTest.setQsPanelShape(expected2)
            assertThat(expected2).isEqualTo(actual)
        }

    @Test
    fun stackRounding() =
        testScope.runTest {
@@ -76,13 +88,17 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
        }

    @Test(expected = IllegalStateException::class)
    fun setStackBounds_withImproperBounds_throwsException() =
    fun stackNotificationScrimBounds_withImproperBounds_throwsException() =
        testScope.runTest {
            underTest.setShadeScrimBounds(
                ShadeScrimBounds(
                    top = 100f,
                    bottom = 99f,
                )
            underTest.setNotificationShadeScrimBounds(ShadeScrimBounds(top = 100f, bottom = 99f))
        }

    @Test(expected = IllegalStateException::class)
    fun setQsPanelShape_withImproperBounds_throwsException() =
        testScope.runTest {
            val invalidBounds = ShadeScrimBounds(top = 0f, bottom = -10f)
            underTest.setQsPanelShape(
                ShadeScrimShape(bounds = invalidBounds, topRadius = 10, bottomRadius = 10)
            )
        }

+4 −2
Original line number Diff line number Diff line
@@ -38,12 +38,14 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
    private val underTest by lazy { kosmos.notificationsPlaceholderViewModel }

    @Test
    fun onBoundsChanged() =
    fun onScrimBoundsChanged() =
        kosmos.testScope.runTest {
            val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f)
            underTest.onScrimBoundsChanged(bounds)
            val stackBounds by
                collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
                collectLastValue(
                    kosmos.notificationStackAppearanceInteractor.notificationShadeScrimBounds
                )
            assertThat(stackBounds).isEqualTo(bounds)
        }
}
Loading