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

Commit e2c79f5c authored by Lucas Silva's avatar Lucas Silva
Browse files

Add new hub background behind a setting

Add two new alternatives for the background of the hub: a static linear
gradient or an animated gradient. The type of background is determined
by a setting, so it can be easily switched via adb without needing to
restart the device.

Command to change between settings:
adb shell settings put secure glanceable_hub_background {0|1|2}

Test: atest CommunalSettingsRepositoryTest
Flag: com.android.systemui.communal_hub
Bug: 341366334
Change-Id: I6b28b4987e45c131a6b1c5fcd32e20228e7d2ce7
parent e89f7097
Loading
Loading
Loading
Loading
+97 −6
Original line number Original line Diff line number Diff line
package com.android.systemui.communal.ui.compose
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.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.CommunalSwipeDetector
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.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.Flags
import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubFullscreenSwipe
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.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -102,6 +119,10 @@ fun CommunalContainer(
    val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
    val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
    val showGestureIndicator by
    val showGestureIndicator by
        viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
        viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
    val backgroundType by
        viewModel.communalBackground.collectAsStateWithLifecycle(
            initialValue = CommunalBackgroundType.DEFAULT
        )
    val state: MutableSceneTransitionLayoutState = remember {
    val state: MutableSceneTransitionLayoutState = remember {
        MutableSceneTransitionLayoutState(
        MutableSceneTransitionLayoutState(
            initialScene = currentSceneKey,
            initialScene = currentSceneKey,
@@ -174,7 +195,7 @@ fun CommunalContainer(
            userActions =
            userActions =
                mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank)
                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. */
/** Scene containing the glanceable hub UI. */
@Composable
@Composable
private fun SceneScope.CommunalScene(
private fun SceneScope.CommunalScene(
    backgroundType: CommunalBackgroundType,
    colors: CommunalColors,
    colors: CommunalColors,
    content: CommunalContent,
    content: CommunalContent,
    modifier: Modifier = Modifier,
    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()
    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(
    Box(
        modifier =
        Modifier.matchParentSize()
            Modifier.element(Communal.Elements.Scrim)
            .background(
                .fillMaxSize()
                Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)),
                .background(Color(backgroundColor.toArgb())),
            )
            )
    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 Original line 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.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.DisabledReason
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.coroutines.collectLastValue
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
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.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Before
import org.junit.Test
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) {
    private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
        whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
        whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
            .thenReturn(disabledFlags)
            .thenReturn(disabledFlags)
+2 −0
Original line number Original line 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.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
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.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -146,6 +147,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                kosmos.keyguardInteractor,
                kosmos.keyguardInteractor,
                kosmos.communalSceneInteractor,
                kosmos.communalSceneInteractor,
                kosmos.communalInteractor,
                kosmos.communalInteractor,
                kosmos.communalSettingsInteractor,
                kosmos.communalTutorialInteractor,
                kosmos.communalTutorialInteractor,
                kosmos.shadeInteractor,
                kosmos.shadeInteractor,
                mediaHost,
                mediaHost,
+20 −0
Original line number Original line 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_FLAG
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
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.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.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.FeatureFlagsClassic
@@ -59,6 +60,9 @@ interface CommunalSettingsRepository {


    /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
    /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
    fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
    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
@SysUISingleton
@@ -126,6 +130,21 @@ constructor(
            .emitOnStart()
            .emitOnStart()
            .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
            .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> =
    private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
        secureSettings
        secureSettings
            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
@@ -141,6 +160,7 @@ constructor(


    companion object {
    companion object {
        const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
        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
        private const val ENABLED_SETTING_DEFAULT = 1
    }
    }
}
}
+10 −0
Original line number Original line 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.CommunalEnabledState
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
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.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.dagger.CommunalTableLog
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 com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.util.concurrent.Executor
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.awaitClose
@@ -38,6 +40,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.stateIn


@@ -47,6 +50,7 @@ class CommunalSettingsInteractor
@Inject
@Inject
constructor(
constructor(
    @Background private val bgScope: CoroutineScope,
    @Background private val bgScope: CoroutineScope,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Background private val bgExecutor: Executor,
    @Background private val bgExecutor: Executor,
    private val repository: CommunalSettingsRepository,
    private val repository: CommunalSettingsRepository,
    userInteractor: SelectedUserInteractor,
    userInteractor: SelectedUserInteractor,
@@ -78,6 +82,12 @@ constructor(
                initialValue = CommunalWidgetCategories.defaultCategories
                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 {
    private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
        fun send(profiles: List<UserInfo>) {
        fun send(profiles: List<UserInfo>) {
            trySend(profiles.find { it.isManagedProfile })
            trySend(profiles.find { it.isManagedProfile })
Loading