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

Commit 363bcf0d authored by Will Leshner's avatar Will Leshner
Browse files

Several fixes to GH bottom area on mobile.

1. Always show the "go to dream" button if charging.
2. Position the "go to dream" button in the bottom right corner.
3. Tapping the "go to dream" button when dreams are disabled
   goes to settings.
4. Lock icon is always at the bottom of the screen, even on
   devices with UDFPS.

Bug: 382693988
Bug: 382739998
Test: atest CommunalSettingsRepositoryImplTest
Test: atest CommunalToDreamButtonViewModelTest
Flag: com.android.systemui.glanceable_hub_v2
Change-Id: Id7413b41c4d4d52780431fde2b1878fe8c1fd02b
parent 95bc2bfb
Loading
Loading
Loading
Loading
+68 −13
Original line number Diff line number Diff line
@@ -19,17 +19,20 @@ package com.android.systemui.communal.ui.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
@@ -39,8 +42,11 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
import kotlin.math.min
import kotlin.math.roundToInt

/** Renders the content of the glanceable hub. */
class CommunalContent
@@ -48,6 +54,7 @@ class CommunalContent
constructor(
    private val viewModel: CommunalViewModel,
    private val interactionHandler: SmartspaceInteractionHandler,
    private val communalSettingsInteractor: CommunalSettingsInteractor,
    private val dialogFactory: SystemUIDialogFactory,
    private val lockSection: LockSection,
    private val bottomAreaSection: BottomAreaSection,
@@ -77,12 +84,21 @@ constructor(
                            sceneScope = this@Content,
                        )
                    }
                    if (communalSettingsInteractor.isV2FlagEnabled()) {
                        Icon(
                            painter = painterResource(id = R.drawable.ic_lock),
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.onPrimaryContainer,
                            modifier = Modifier.element(Communal.Elements.LockIcon),
                        )
                    } else {
                        with(lockSection) {
                            LockIcon(
                                overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
                                modifier = Modifier.element(Communal.Elements.LockIcon),
                            )
                        }
                    }
                    with(bottomAreaSection) {
                        IndicationArea(
                            Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
@@ -98,14 +114,42 @@ constructor(

                val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)

                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
                val lockIconPlaceable =
                    if (communalSettingsInteractor.isV2FlagEnabled()) {
                        val lockIconSizeInt = lockIconSize.roundToPx()
                        lockIconMeasurable.measure(
                            Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt)
                        )
                    } else {
                        lockIconMeasurable.measure(noMinConstraints)
                    }
                val lockIconBounds =
                    if (communalSettingsInteractor.isV2FlagEnabled()) {
                        val lockIconDistanceFromBottom =
                            min(
                                (constraints.maxHeight * lockIconPercentDistanceFromBottom)
                                    .roundToInt(),
                                lockIconMinDistanceFromBottom.roundToPx(),
                            )
                        val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2
                        val y =
                            constraints.maxHeight -
                                lockIconDistanceFromBottom -
                                lockIconPlaceable.height
                        IntRect(
                            left = x,
                            top = y,
                            right = x + lockIconPlaceable.width,
                            bottom = y + lockIconPlaceable.height,
                        )
                    } else {
                        IntRect(
                            left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
                            top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
                            right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
                            bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
                        )
                    }

                val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)

@@ -129,12 +173,17 @@ constructor(

                    val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height
                    bottomAreaPlaceable.place(x = 0, y = bottomAreaTop)

                    val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx()
                    screensaverButtonPlaceable?.place(
                        x =
                            constraints.maxWidth -
                                screensaverButtonSizeInt -
                                Dimensions.ItemSpacing.roundToPx(),
                        y = lockIconBounds.top,
                                screensaverButtonPaddingInt,
                        y =
                            constraints.maxHeight -
                                screensaverButtonSizeInt -
                                screensaverButtonPaddingInt,
                    )
                }
            }
@@ -142,6 +191,12 @@ constructor(
    }

    companion object {
        val screensaverButtonSize: Dp = 64.dp
        private val screensaverButtonSize: Dp = 64.dp
        private val screensaverButtonPadding: Dp = 24.dp
        // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
        // position are sorted.
        private val lockIconSize: Dp = 54.dp
        private val lockIconPercentDistanceFromBottom = 0.1f
        private val lockIconMinDistanceFromBottom = 70.dp
    }
}
+28 −0
Original line number Diff line number Diff line
@@ -295,6 +295,34 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
            }
        }

    @Test
    fun screensaverDisabledByUser() =
        testScope.runTest {
            val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))

            kosmos.fakeSettings.putIntForUser(
                Settings.Secure.SCREENSAVER_ENABLED,
                0,
                PRIMARY_USER.id,
            )

            assertThat(enabledState).isFalse()
        }

    @Test
    fun screensaverEnabledByUser() =
        testScope.runTest {
            val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))

            kosmos.fakeSettings.putIntForUser(
                Settings.Secure.SCREENSAVER_ENABLED,
                1,
                PRIMARY_USER.id,
            )

            assertThat(enabledState).isTrue()
        }

    private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
        whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
            .thenReturn(disabledFlags)
+30 −12
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel

import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -29,12 +30,16 @@ import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
@@ -56,10 +61,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
    }

    @Test
    fun shouldShowDreamButtonOnHub_trueWhenCanDream() =
    fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() =
        with(kosmos) {
            runTest {
                whenever(dreamManager.canStartDreaming(any())).thenReturn(true)
                whenever(batteryController.isPluggedIn()).thenReturn(true)

                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
@@ -68,11 +72,10 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
        }

    @Test
    fun shouldShowDreamButtonOnHub_falseWhenCannotDream() =
    fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
        with(kosmos) {
            runTest {
                whenever(dreamManager.canStartDreaming(any())).thenReturn(false)
                whenever(batteryController.isPluggedIn()).thenReturn(true)
                whenever(batteryController.isPluggedIn()).thenReturn(false)

                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
                assertThat(shouldShowButton).isFalse()
@@ -80,25 +83,40 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
        }

    @Test
    fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
    fun onShowDreamButtonTap_dreamsEnabled_startsDream() =
        with(kosmos) {
            runTest {
                whenever(dreamManager.canStartDreaming(any())).thenReturn(true)
                whenever(batteryController.isPluggedIn()).thenReturn(false)
                val currentUser = fakeUserRepository.asMainUser()
                kosmos.fakeSettings.putIntForUser(
                    Settings.Secure.SCREENSAVER_ENABLED,
                    1,
                    currentUser.id,
                )
                runCurrent()

                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
                assertThat(shouldShowButton).isFalse()
                underTest.onShowDreamButtonTap()
                runCurrent()

                verify(dreamManager).startDream()
            }
        }

    @Test
    fun onShowDreamButtonTap_startsDream() =
    fun onShowDreamButtonTap_dreamsDisabled_startsActivity() =
        with(kosmos) {
            runTest {
                val currentUser = fakeUserRepository.asMainUser()
                kosmos.fakeSettings.putIntForUser(
                    Settings.Secure.SCREENSAVER_ENABLED,
                    0,
                    currentUser.id,
                )
                runCurrent()

                underTest.onShowDreamButtonTap()
                runCurrent()

                verify(dreamManager).startDream()
                verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt())
            }
        }
}
+17 −0
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ interface CommunalSettingsRepository {
    /** A [CommunalEnabledState] for the specified user. */
    fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>

    fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>

    /**
     * Returns true if any glanceable hub functionality should be enabled via configs and flags.
     *
@@ -138,6 +140,20 @@ constructor(
            .flowOn(bgDispatcher)
    }

    override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> =
        secureSettings
            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED))
            // Force an update
            .onStart { emit(Unit) }
            .map {
                secureSettings.getIntForUser(
                    Settings.Secure.SCREENSAVER_ENABLED,
                    SCREENSAVER_ENABLED_SETTING_DEFAULT,
                    user.id,
                ) == 1
            }
            .flowOn(bgDispatcher)

    override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
        broadcastDispatcher
            .broadcastFlow(
@@ -182,6 +198,7 @@ constructor(
    companion object {
        const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
        private const val ENABLED_SETTING_DEFAULT = 1
        private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0
    }
}

+6 −0
Original line number Diff line number Diff line
@@ -69,6 +69,12 @@ constructor(
            // Start this eagerly since the value is accessed synchronously in many places.
            .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)

    /** Whether or not screensaver (dreams) is enabled for the currently selected user. */
    val isScreensaverEnabled: Flow<Boolean> =
        userInteractor.selectedUserInfo.flatMapLatest { user ->
            repository.getScreensaverEnabledState(user)
        }

    /**
     * Returns true if any glanceable hub functionality should be enabled via configs and flags.
     *
Loading