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

Commit 76582919 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[bc25] Collapse the shade when switching to bouncer." into main

parents 6b09d0b0 e46d53bb
Loading
Loading
Loading
Loading
+85 −4
Original line number Diff line number Diff line
@@ -21,19 +21,34 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
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.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -47,18 +62,84 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {

    private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel }

    @Before
    fun setUp() {
        kosmos.sceneContainerStartable.start()
        underTest.activateIn(testScope)
    }

    @Test
    fun onScrimClicked_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            sceneInteractor.showOverlay(
                overlay = Overlays.NotificationsShade,
                loggingReason = "test",
            )
            sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
            assertThat(currentOverlays).contains(Overlays.NotificationsShade)

            underTest.onScrimClicked()

            assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
        }

    @Test
    fun deviceLocked_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            unlockDevice()
            sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
            assertThat(currentOverlays).contains(Overlays.NotificationsShade)

            lockDevice()

            assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
        }

    @Test
    fun bouncerShown_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            lockDevice()
            sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
            assertThat(currentOverlays).contains(Overlays.NotificationsShade)

            sceneInteractor.changeScene(Scenes.Bouncer, "test")
            runCurrent()

            assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
        }

    @Test
    fun shadeNotTouchable_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable)
            assertThat(isShadeTouchable).isTrue()
            sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
            assertThat(currentOverlays).contains(Overlays.NotificationsShade)

            lockDevice()
            assertThat(isShadeTouchable).isFalse()
            assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
        }

    private fun TestScope.lockDevice() {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        kosmos.powerInteractor.setAsleepForTest()
        runCurrent()

        assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
    }

    private suspend fun TestScope.unlockDevice() {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        kosmos.powerInteractor.setAwakeForTest()
        runCurrent()
        assertThat(
                kosmos.authenticationInteractor.authenticate(
                    FakeAuthenticationRepository.DEFAULT_PIN
                )
            )
            .isEqualTo(AuthenticationResult.SUCCEEDED)

        assertThat(currentScene).isEqualTo(Scenes.Gone)
    }
}
+85 −4
Original line number Diff line number Diff line
@@ -21,18 +21,33 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
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.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -46,18 +61,84 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

    private val underTest by lazy { kosmos.quickSettingsShadeOverlayContentViewModel }

    @Before
    fun setUp() {
        kosmos.sceneContainerStartable.start()
        underTest.activateIn(testScope)
    }

    @Test
    fun onScrimClicked_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            sceneInteractor.showOverlay(
                overlay = Overlays.QuickSettingsShade,
                loggingReason = "test",
            )
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)

            underTest.onScrimClicked()

            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
        }

    @Test
    fun deviceLocked_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            unlockDevice()
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)

            lockDevice()

            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun bouncerShown_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            lockDevice()
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)

            sceneInteractor.changeScene(Scenes.Bouncer, "test")
            runCurrent()

            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
        }

    @Test
    fun shadeNotTouchable_hidesShade() =
        testScope.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable)
            assertThat(isShadeTouchable).isTrue()
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)

            lockDevice()
            assertThat(isShadeTouchable).isFalse()
            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
        }

    private fun TestScope.lockDevice() {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        kosmos.powerInteractor.setAsleepForTest()
        runCurrent()

        assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
    }

    private suspend fun TestScope.unlockDevice() {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        kosmos.powerInteractor.setAwakeForTest()
        runCurrent()
        assertThat(
                kosmos.authenticationInteractor.authenticate(
                    FakeAuthenticationRepository.DEFAULT_PIN
                )
            )
            .isEqualTo(AuthenticationResult.SUCCEEDED)

        assertThat(currentScene).isEqualTo(Scenes.Gone)
    }
}
+40 −2
Original line number Diff line number Diff line
@@ -16,11 +16,19 @@

package com.android.systemui.notifications.ui.viewmodel

import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch

/**
 * Models UI state used to render the content of the notifications shade overlay.
@@ -33,10 +41,40 @@ class NotificationsShadeOverlayContentViewModel
constructor(
    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
    val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
    val sceneInteractor: SceneInteractor,
    private val shadeInteractor: ShadeInteractor,
) {
) : ExclusiveActivatable() {

    override suspend fun onActivated(): Nothing {
        coroutineScope {
            launch {
                sceneInteractor.currentScene.collect { currentScene ->
                    when (currentScene) {
                        // TODO(b/369513770): The ShadeSession should be preserved in this scenario.
                        Scenes.Bouncer ->
                            shadeInteractor.collapseNotificationsShade(
                                loggingReason = "bouncer shown while shade is open"
                            )
                    }
                }
            }

            launch {
                shadeInteractor.isShadeTouchable
                    .distinctUntilChanged()
                    .filter { !it }
                    .collect {
                        shadeInteractor.collapseNotificationsShade(
                            loggingReason = "device became non-interactive"
                        )
                    }
            }
        }
        awaitCancellation()
    }

    fun onScrimClicked() {
        shadeInteractor.collapseNotificationsShade(loggingReason = "Shade scrim clicked")
        shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked")
    }

    @AssistedFactory
+41 −2
Original line number Diff line number Diff line
@@ -16,10 +16,18 @@

package com.android.systemui.qs.ui.viewmodel

import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch

/**
 * Models UI state used to render the content of the quick settings shade overlay.
@@ -31,11 +39,42 @@ class QuickSettingsShadeOverlayContentViewModel
@AssistedInject
constructor(
    val shadeInteractor: ShadeInteractor,
    val sceneInteractor: SceneInteractor,
    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
) {
) : ExclusiveActivatable() {

    override suspend fun onActivated(): Nothing {
        coroutineScope {
            launch {
                sceneInteractor.currentScene.collect { currentScene ->
                    when (currentScene) {
                        // TODO(b/369513770): The ShadeSession should be preserved in this scenario.
                        Scenes.Bouncer ->
                            shadeInteractor.collapseQuickSettingsShade(
                                loggingReason = "bouncer shown while shade is open"
                            )
                    }
                }
            }

            launch {
                shadeInteractor.isShadeTouchable
                    .distinctUntilChanged()
                    .filter { !it }
                    .collect {
                        shadeInteractor.collapseQuickSettingsShade(
                            loggingReason = "device became non-interactive"
                        )
                    }
            }
        }

        awaitCancellation()
    }

    fun onScrimClicked() {
        shadeInteractor.collapseQuickSettingsShade(loggingReason = "Shade scrim clicked")
        shadeInteractor.collapseQuickSettingsShade(loggingReason = "shade scrim clicked")
    }

    @AssistedFactory
+2 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.qs.ui.viewmodel

import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory

@@ -24,6 +25,7 @@ val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayC
    Kosmos.Fixture {
        QuickSettingsShadeOverlayContentViewModel(
            shadeInteractor = shadeInteractor,
            sceneInteractor = sceneInteractor,
            shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
            quickSettingsContainerViewModel = quickSettingsContainerViewModel,
        )
Loading