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

Commit c79021d1 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[bc25] Create the `QuickSettingsShade` scene." into main

parents 9daec85a 6bcca2e1
Loading
Loading
Loading
Loading
+29 −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.scene

import com.android.systemui.qs.ui.composable.QuickSettingsShadeScene
import com.android.systemui.scene.shared.model.Scene
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet

@Module
interface QuickSettingsShadeSceneModule {

    @Binds @IntoSet fun quickSettingsShade(scene: QuickSettingsShadeScene): Scene
}
+71 −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.ui.composable

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow

@SysUISingleton
class QuickSettingsShadeScene
@Inject
constructor(
    viewModel: QuickSettingsShadeSceneViewModel,
    private val overlayShadeViewModel: OverlayShadeViewModel,
) : ComposableScene {

    override val key = Scenes.QuickSettingsShade

    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
        viewModel.destinationScenes

    @Composable
    override fun SceneScope.Content(
        modifier: Modifier,
    ) {
        OverlayShade(
            viewModel = overlayShadeViewModel,
            modifier = modifier,
            horizontalArrangement = Arrangement.End,
        ) {
            Text(
                text = "Quick settings grid",
                modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding)
            )
        }
    }
}

object QuickSettingsShade {
    object Dimensions {
        val Padding = 16.dp
    }
}
+124 −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.ui.viewmodel

import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Swipe
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.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
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.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val sceneInteractor = kosmos.sceneInteractor
    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor

    private val underTest = kosmos.quickSettingsShadeSceneViewModel

    @Test
    fun upTransitionSceneKey_deviceLocked_lockscreen() =
        testScope.runTest {
            val destinationScenes by collectLastValue(underTest.destinationScenes)
            lockDevice()

            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun upTransitionSceneKey_deviceUnlocked_gone() =
        testScope.runTest {
            val destinationScenes by collectLastValue(underTest.destinationScenes)
            lockDevice()
            unlockDevice()

            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone)
        }

    @Test
    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
        testScope.runTest {
            val destinationScenes by collectLastValue(underTest.destinationScenes)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.None
            )
            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")

            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
        testScope.runTest {
            val destinationScenes by collectLastValue(underTest.destinationScenes)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.None
            )
            runCurrent()
            sceneInteractor.changeScene(Scenes.Gone, "reason")

            assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone)
        }

    private fun TestScope.lockDevice() {
        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)

        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
        assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
        runCurrent()
    }

    private fun TestScope.unlockDevice() {
        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)

        kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
            SuccessFingerprintAuthenticationStatus(0, true)
        )
        assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
        sceneInteractor.changeScene(Scenes.Gone, "reason")
        runCurrent()
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -100,7 +100,13 @@ constructor(

                // Swiping down from the top edge goes to QS (or shade if in split shade mode).
                swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
                swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
                swipeDownFromTop(pointerCount = 2) to
                    // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones.
                    if (shadeMode is ShadeMode.Dual) {
                        Scenes.QuickSettingsShade
                    } else {
                        quickSettingsIfSingleShade
                    },

                // Swiping down, not from the edge, always navigates to the shade scene.
                swipeDown(pointerCount = 1) to shadeSceneKey,
+4 −1
Original line number Diff line number Diff line
@@ -85,7 +85,10 @@ constructor(
                    {
                        it.scene == Scenes.NotificationsShade || it.scene == Scenes.Shade
                    },
                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings },
                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to
                    {
                        it.scene == Scenes.QuickSettingsShade || it.scene == Scenes.QuickSettings
                    },
                SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer },
                SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
                    {
Loading