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

Commit 18d04dac authored by Lucas Silva's avatar Lucas Silva Committed by Android (Google) Code Review
Browse files

Merge "Add new hub background behind a setting" into main

parents 9d61d737 e2c79f5c
Loading
Loading
Loading
Loading
+97 −6
Original line number Diff line number Diff line
package com.android.systemui.communal.ui.compose

import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -13,12 +20,20 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.CommunalSwipeDetector
@@ -36,8 +51,10 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubFullscreenSwipe
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -102,6 +119,10 @@ fun CommunalContainer(
    val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
    val showGestureIndicator by
        viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
    val backgroundType by
        viewModel.communalBackground.collectAsStateWithLifecycle(
            initialValue = CommunalBackgroundType.DEFAULT
        )
    val state: MutableSceneTransitionLayoutState = remember {
        MutableSceneTransitionLayoutState(
            initialScene = currentSceneKey,
@@ -174,7 +195,7 @@ fun CommunalContainer(
            userActions =
                mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank)
        ) {
            CommunalScene(colors, content)
            CommunalScene(backgroundType, colors, content)
        }
    }

@@ -186,17 +207,87 @@ fun CommunalContainer(
/** Scene containing the glanceable hub UI. */
@Composable
private fun SceneScope.CommunalScene(
    backgroundType: CommunalBackgroundType,
    colors: CommunalColors,
    content: CommunalContent,
    modifier: Modifier = Modifier,
) {
    Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) {
        when (backgroundType) {
            CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors)
            CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient()
            CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient()
        }
    }
    with(content) { Content(modifier = modifier) }
}

/** Default background of the hub, a single color */
@Composable
private fun BoxScope.DefaultBackground(
    colors: CommunalColors,
) {
    val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
    Box(
        modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())),
    )
}

/** Experimental hub background, static linear gradient */
@Composable
private fun BoxScope.StaticLinearGradient() {
    val colors = LocalAndroidColorScheme.current
    Box(
        modifier =
            Modifier.element(Communal.Elements.Scrim)
                .fillMaxSize()
                .background(Color(backgroundColor.toArgb())),
        Modifier.matchParentSize()
            .background(
                Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)),
            )
    with(content) { Content(modifier = modifier) }
    )
    BackgroundTopScrim()
}

/** Experimental hub background, animated linear gradient */
@Composable
private fun BoxScope.AnimatedLinearGradient() {
    val colors = LocalAndroidColorScheme.current
    Box(
        Modifier.matchParentSize()
            .animatedGradientBackground(colors = listOf(colors.primary, colors.primaryContainer))
    )
    BackgroundTopScrim()
}

/** Scrim placed on top of the background in order to dim/bright colors */
@Composable
private fun BoxScope.BackgroundTopScrim() {
    val darkTheme = isSystemInDarkTheme()
    val scrimOnTopColor = if (darkTheme) Color.Black else Color.White
    Box(Modifier.matchParentSize().alpha(0.34f).background(scrimOnTopColor))
}

/** Modifier which sets the background of a composable to an animated gradient */
@Composable
private fun Modifier.animatedGradientBackground(colors: List<Color>): Modifier = composed {
    var size by remember { mutableStateOf(IntSize.Zero) }
    val transition = rememberInfiniteTransition(label = "scrim background")
    val startOffsetX by
        transition.animateFloat(
            initialValue = -size.width.toFloat(),
            targetValue = size.width.toFloat(),
            animationSpec =
                infiniteRepeatable(
                    animation = tween(durationMillis = 5_000, easing = LinearEasing),
                    repeatMode = RepeatMode.Reverse,
                ),
            label = "scrim start offset"
        )
    background(
            brush =
                Brush.linearGradient(
                    colors = colors,
                    start = Offset(startOffsetX, 0f),
                    end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()),
                )
        )
        .onGloballyPositioned { size = it.size }
}
+29 −0
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.DisabledReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -43,6 +45,7 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -216,6 +219,32 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
                )
        }

    @Test
    fun backgroundType_defaultValue() =
        testScope.runTest {
            val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
            assertThat(backgroundType).isEqualTo(CommunalBackgroundType.DEFAULT)
        }

    @Test
    fun backgroundType_verifyAllValues() =
        testScope.runTest {
            val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
            for (type in CommunalBackgroundType.entries) {
                kosmos.fakeSettings.putIntForUser(
                    GLANCEABLE_HUB_BACKGROUND_SETTING,
                    type.value,
                    PRIMARY_USER.id
                )
                assertWithMessage(
                        "Expected $type when $GLANCEABLE_HUB_BACKGROUND_SETTING is set to" +
                            " ${type.value} but was $backgroundType"
                    )
                    .that(backgroundType)
                    .isEqualTo(type)
            }
        }

    private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
        whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
            .thenReturn(disabledFlags)
+2 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -152,6 +153,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                kosmos.keyguardInteractor,
                kosmos.communalSceneInteractor,
                kosmos.communalInteractor,
                kosmos.communalSettingsInteractor,
                kosmos.communalTutorialInteractor,
                kosmos.shadeInteractor,
                mediaHost,
+20 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_D
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
@@ -59,6 +60,9 @@ interface CommunalSettingsRepository {

    /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
    fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>

    /** The type of background to use for the hub. Used to experiment with different backgrounds. */
    fun getBackground(user: UserInfo): Flow<CommunalBackgroundType>
}

@SysUISingleton
@@ -126,6 +130,21 @@ constructor(
            .emitOnStart()
            .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }

    override fun getBackground(user: UserInfo): Flow<CommunalBackgroundType> =
        secureSettings
            .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_BACKGROUND_SETTING))
            .emitOnStart()
            .map {
                val intType =
                    secureSettings.getIntForUser(
                        GLANCEABLE_HUB_BACKGROUND_SETTING,
                        CommunalBackgroundType.DEFAULT.value,
                        user.id
                    )
                CommunalBackgroundType.entries.find { type -> type.value == intType }
                    ?: CommunalBackgroundType.DEFAULT
            }

    private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
        secureSettings
            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
@@ -141,6 +160,7 @@ constructor(

    companion object {
        const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
        const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
        private const val ENABLED_SETTING_DEFAULT = 1
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.communal.data.model.CommunalEnabledState
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.dagger.CommunalTableLog
@@ -30,6 +31,7 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -38,6 +40,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@@ -47,6 +50,7 @@ class CommunalSettingsInteractor
@Inject
constructor(
    @Background private val bgScope: CoroutineScope,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Background private val bgExecutor: Executor,
    private val repository: CommunalSettingsRepository,
    userInteractor: SelectedUserInteractor,
@@ -78,6 +82,12 @@ constructor(
                initialValue = CommunalWidgetCategories.defaultCategories
            )

    /** The type of background to use for the hub. Used to experiment with different backgrounds */
    val communalBackground: Flow<CommunalBackgroundType> =
        userInteractor.selectedUserInfo
            .flatMapLatest { user -> repository.getBackground(user) }
            .flowOn(bgDispatcher)

    private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
        fun send(profiles: List<UserInfo>) {
            trySend(profiles.find { it.isManagedProfile })
Loading