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

Commit 4ddd8c1d authored by Shawn Lee's avatar Shawn Lee Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Implement QS bottom swipe up to Gone" into main

parents e2aaa666 70d9da86
Loading
Loading
Loading
Loading
+61 −3
Original line number Original line Diff line number Diff line
@@ -19,13 +19,20 @@ package com.android.systemui.qs.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Swipe
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.UserActionResult
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -41,6 +48,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
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
@@ -66,12 +74,15 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {


    private lateinit var underTest: QuickSettingsSceneViewModel
    private lateinit var underTest: QuickSettingsSceneViewModel


    @OptIn(ExperimentalCoroutinesApi::class)
    @Before
    @Before
    fun setUp() {
    fun setUp() {
        kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
        kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)


        underTest =
        underTest =
            QuickSettingsSceneViewModel(
            QuickSettingsSceneViewModel(
                applicationScope = testScope.backgroundScope,
                deviceEntryInteractor = kosmos.deviceEntryInteractor,
                brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
                brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
                shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
                shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
                qsSceneAdapter = qsFlexiglassAdapter,
                qsSceneAdapter = qsFlexiglassAdapter,
@@ -83,17 +94,27 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun destinations_whenNotCustomizing() =
    fun destinations_whenNotCustomizing_unlocked() =
        testScope.runTest {
        testScope.runTest {
            overrideResource(R.bool.config_use_split_notification_shade, false)
            overrideResource(R.bool.config_use_split_notification_shade, false)
            val destinations by collectLastValue(underTest.destinationScenes)
            val destinations by collectLastValue(underTest.destinationScenes)
            qsFlexiglassAdapter.setCustomizing(false)
            qsFlexiglassAdapter.setCustomizing(false)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pin
            )
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )


            assertThat(destinations)
            assertThat(destinations)
                .isEqualTo(
                .isEqualTo(
                    mapOf(
                    mapOf(
                        Back to UserActionResult(Scenes.Shade),
                        Back to UserActionResult(Scenes.Shade),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                        Swipe(
                            fromSource = Edge.Bottom,
                            direction = SwipeDirection.Up,
                        ) to UserActionResult(Scenes.Gone)
                    )
                    )
                )
                )
        }
        }
@@ -111,12 +132,39 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
            assertThat(previousScene).isEqualTo(Scenes.Lockscreen)
            assertThat(previousScene).isEqualTo(Scenes.Lockscreen)

            assertThat(destinations)
            assertThat(destinations)
                .isEqualTo(
                .isEqualTo(
                    mapOf(
                    mapOf(
                        Back to UserActionResult(Scenes.Lockscreen),
                        Back to UserActionResult(Scenes.Lockscreen),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen),
                        Swipe(
                            fromSource = Edge.Bottom,
                            direction = SwipeDirection.Up,
                        ) to UserActionResult(Scenes.Lockscreen)
                    )
                )
        }

    @Test
    fun destinations_whenNotCustomizing_authMethodSwipe_lockscreenNotDismissed() =
        testScope.runTest {
            overrideResource(R.bool.config_use_split_notification_shade, false)
            val destinations by collectLastValue(underTest.destinationScenes)
            qsFlexiglassAdapter.setCustomizing(false)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.None
            )

            assertThat(destinations)
                .isEqualTo(
                    mapOf(
                        Back to UserActionResult(Scenes.Shade),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                        Swipe(
                            fromSource = Edge.Bottom,
                            direction = SwipeDirection.Up,
                        ) to UserActionResult(Scenes.Lockscreen)
                    )
                    )
                )
                )
        }
        }
@@ -132,17 +180,27 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
        }
        }


    @Test
    @Test
    fun destinations_whenNotCustomizing_inSplitShade() =
    fun destinations_whenNotCustomizing_inSplitShade_unlocked() =
        testScope.runTest {
        testScope.runTest {
            overrideResource(R.bool.config_use_split_notification_shade, true)
            overrideResource(R.bool.config_use_split_notification_shade, true)
            val destinations by collectLastValue(underTest.destinationScenes)
            val destinations by collectLastValue(underTest.destinationScenes)
            qsFlexiglassAdapter.setCustomizing(false)
            qsFlexiglassAdapter.setCustomizing(false)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pin
            )
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )


            assertThat(destinations)
            assertThat(destinations)
                .isEqualTo(
                .isEqualTo(
                    mapOf(
                    mapOf(
                        Back to UserActionResult(Scenes.Shade),
                        Back to UserActionResult(Scenes.Shade),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
                        Swipe(
                            fromSource = Edge.Bottom,
                            direction = SwipeDirection.Up,
                        ) to UserActionResult(Scenes.Gone),
                    )
                    )
                )
                )
        }
        }
+68 −14
Original line number Original line Diff line number Diff line
@@ -18,10 +18,15 @@ package com.android.systemui.qs.ui.viewmodel


import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
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.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -32,35 +37,84 @@ import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn


/** Models UI state and handles user input for the quick settings scene. */
/** Models UI state and handles user input for the quick settings scene. */
@SysUISingleton
@SysUISingleton
class QuickSettingsSceneViewModel
class QuickSettingsSceneViewModel
@Inject
@Inject
constructor(
constructor(
    @Application private val applicationScope: CoroutineScope,
    deviceEntryInteractor: DeviceEntryInteractor,
    val brightnessMirrorViewModel: BrightnessMirrorViewModel,
    val brightnessMirrorViewModel: BrightnessMirrorViewModel,
    val shadeHeaderViewModel: ShadeHeaderViewModel,
    val shadeHeaderViewModel: ShadeHeaderViewModel,
    val qsSceneAdapter: QSSceneAdapter,
    val qsSceneAdapter: QSSceneAdapter,
    val notifications: NotificationsPlaceholderViewModel,
    val notifications: NotificationsPlaceholderViewModel,
    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
    private val footerActionsController: FooterActionsController,
    private val footerActionsController: FooterActionsController,
    private val sceneInteractor: SceneInteractor,
    sceneInteractor: SceneInteractor,
) {
) {
    val destinationScenes =
    @OptIn(ExperimentalCoroutinesApi::class)
        qsSceneAdapter.isCustomizing.flatMapLatest { customizing ->
    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
            if (customizing) {
        combine(
                flowOf(emptyMap())
                deviceEntryInteractor.isUnlocked,
                deviceEntryInteractor.canSwipeToEnter,
                qsSceneAdapter.isCustomizing,
                sceneInteractor.previousScene(ignored = Scenes.QuickSettings),
            ) { isUnlocked, canSwipeToDismiss, isCustomizing, previousScene ->
                destinationScenes(
                    isUnlocked,
                    canSwipeToDismiss,
                    isCustomizing,
                    previousScene,
                )
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue =
                    destinationScenes(
                        isUnlocked = deviceEntryInteractor.isUnlocked.value,
                        canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
                        isCustomizing = qsSceneAdapter.isCustomizing.value,
                        previousScene = sceneInteractor
                                .previousScene(ignored = Scenes.QuickSettings).value,
                    ),
            )

    private fun destinationScenes(
        isUnlocked: Boolean,
        canSwipeToDismiss: Boolean?,
        isCustomizing: Boolean,
        previousScene: SceneKey?
    ): Map<UserAction, UserActionResult> {
        val upBottomEdge =
            when {
                canSwipeToDismiss == true -> Scenes.Lockscreen
                isUnlocked -> Scenes.Gone
                else -> Scenes.Lockscreen
            }

        return buildMap {
            if (isCustomizing) {
                // TODO(b/332749288) Empty map so there are no back handlers and back can close
                // customizer

                // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
                // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
                // while customizing
                // while customizing
            } else {
            } else {
                sceneInteractor.previousScene(ignored = Scenes.QuickSettings).map { previousScene ->
                this[Back] = UserActionResult(previousScene ?: Scenes.Shade)
                    mapOf(
                this[Swipe(SwipeDirection.Up)] = UserActionResult(previousScene ?: Scenes.Shade)
                        Back to UserActionResult(previousScene ?: Scenes.Shade),
                this[
                        Swipe(SwipeDirection.Up) to UserActionResult(previousScene ?: Scenes.Shade),
                    Swipe(
                    )
                        fromSource = Edge.Bottom,
                        direction = SwipeDirection.Up,
                    )] = UserActionResult(upBottomEdge)
            }
            }
        }
        }
    }
    }