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

Commit 6d8b1bb7 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge changes I5d18421d,I41bf6731 into main

* changes:
  Track StatusBarState
  Add NotificationScrimClip  to QSFragmentCompose
parents f45de596 6758405d
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.fgsManagerController
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
@@ -140,6 +142,42 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() {
            }
        }

    @Test
    fun statusBarState_followsController() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val statusBarState by collectLastValue(underTest.statusBarState)
                runCurrent()

                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)

                sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)

                sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
            }
        }

    @Test
    fun statusBarState_changesEarlyIfUpcomingStateIsKeyguard() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val statusBarState by collectLastValue(underTest.statusBarState)

                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
                sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE_LOCKED)
                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)

                sysuiStatusBarStateController.setUpcomingState(StatusBarState.KEYGUARD)
                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)

                sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE)
                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
            }
        }

    private inline fun TestScope.testWithinLifecycle(
        crossinline block: suspend TestScope.() -> TestResult
    ): TestResult {
+37 −2
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
@@ -50,6 +52,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,6 +62,7 @@ import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -100,6 +104,17 @@ constructor(
    private val qqsPositionOnRoot = Rect()
    private val composeViewPositionOnScreen = Rect()

    // Inside object for namespacing
    private val notificationScrimClippingParams =
        object {
            var isEnabled by mutableStateOf(false)
            var leftInset by mutableStateOf(0)
            var rightInset by mutableStateOf(0)
            var top by mutableStateOf(0)
            var bottom by mutableStateOf(0)
            var radius by mutableStateOf(0)
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

@@ -126,7 +141,18 @@ constructor(

                    AnimatedVisibility(
                        visible = visible,
                        modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
                        modifier =
                            Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
                                notificationScrimClippingParams.isEnabled
                            ) {
                                Modifier.notificationScrimClip(
                                    notificationScrimClippingParams.leftInset,
                                    notificationScrimClippingParams.top,
                                    notificationScrimClippingParams.rightInset,
                                    notificationScrimClippingParams.bottom,
                                    notificationScrimClippingParams.radius,
                                )
                            }
                    ) {
                        AnimatedContent(targetState = qsState) {
                            when (it) {
@@ -280,7 +306,16 @@ constructor(
        cornerRadius: Int,
        visible: Boolean,
        fullWidth: Boolean
    ) {}
    ) {
        notificationScrimClippingParams.isEnabled = visible
        notificationScrimClippingParams.top = top
        notificationScrimClippingParams.bottom = bottom
        // Full width means that QS will show in the entire width allocated to it (for example
        // phone) vs. showing in a narrower column (for example, tablet portrait).
        notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset
        notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset
        notificationScrimClippingParams.radius = cornerRadius
    }

    override fun isFullyCollapsed(): Boolean {
        return viewModel.qsExpansionValue <= 0f
+117 −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.qs.composefragment.ui

import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo

/**
 * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
 * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
 * from the QS container.
 */
fun Modifier.notificationScrimClip(
    leftInset: Int,
    top: Int,
    rightInset: Int,
    bottom: Int,
    radius: Int
): Modifier {
    return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius)
}

private class NotificationScrimClipNode(
    var leftInset: Float,
    var top: Float,
    var rightInset: Float,
    var bottom: Float,
    var radius: Float,
) : DrawModifierNode, Modifier.Node() {
    private val path = Path()

    var invalidated = true

    override fun ContentDrawScope.draw() {
        if (invalidated) {
            path.rewind()
            path
                .asAndroidPath()
                .addRoundRect(
                    -leftInset,
                    top,
                    size.width + rightInset,
                    bottom,
                    radius,
                    radius,
                    android.graphics.Path.Direction.CW
                )
            invalidated = false
        }
        clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
    }
}

private data class NotificationScrimClipElement(
    val leftInset: Int,
    val top: Int,
    val rightInset: Int,
    val bottom: Int,
    val radius: Int,
) : ModifierNodeElement<NotificationScrimClipNode>() {
    override fun create(): NotificationScrimClipNode {
        return NotificationScrimClipNode(
            leftInset.toFloat(),
            top.toFloat(),
            rightInset.toFloat(),
            bottom.toFloat(),
            radius.toFloat(),
        )
    }

    override fun update(node: NotificationScrimClipNode) {
        val changed =
            node.leftInset != leftInset.toFloat() ||
                node.top != top.toFloat() ||
                node.rightInset != rightInset.toFloat() ||
                node.bottom != bottom.toFloat() ||
                node.radius != radius.toFloat()
        if (changed) {
            node.leftInset = leftInset.toFloat()
            node.top = top.toFloat()
            node.rightInset = rightInset.toFloat()
            node.bottom = bottom.toFloat()
            node.radius = radius.toFloat()
            node.invalidated = true
        }
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "notificationScrimClip"
        properties["leftInset"] = leftInset
        properties["top"] = top
        properties["rightInset"] = rightInset
        properties["bottom"] = bottom
        properties["radius"] = radius
    }
}
+33 −1
Original line number Diff line number Diff line
@@ -19,21 +19,26 @@ package com.android.systemui.qs.composefragment.viewmodel
import android.content.res.Resources
import android.graphics.Rect
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -140,7 +145,34 @@ constructor(

    private val _keyguardAndExpanded = MutableStateFlow(false)

    private val _statusBarState = MutableStateFlow(-1)
    /**
     * Tracks the current [StatusBarState]. It will switch early if the upcoming state is
     * [StatusBarState.KEYGUARD]
     */
    @get:VisibleForTesting
    val statusBarState =
        conflatedCallbackFlow {
                val callback =
                    object : StatusBarStateController.StateListener {
                        override fun onStateChanged(newState: Int) {
                            trySend(newState)
                        }

                        override fun onUpcomingStateChanged(upcomingState: Int) {
                            if (upcomingState == StatusBarState.KEYGUARD) {
                                trySend(upcomingState)
                            }
                        }
                    }
                sysuiStatusBarStateController.addCallback(callback)

                awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
            }
            .stateIn(
                lifecycleScope,
                SharingStarted.WhileSubscribed(),
                sysuiStatusBarStateController.state,
            )

    private val _viewHeight = MutableStateFlow(0)