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

Commit c488e438 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Create `ObservableState` to replace some Consumers" into main

parents a330fa34 ab9afcba
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -147,7 +147,7 @@ constructor(
            animateFloatAsState(if (showBrightnessMirror) 0f else 1f)

        // Set the bounds to null when the QuickSettings overlay disappears.
        DisposableEffect(Unit) { onDispose { contentViewModel.onPanelShapeChanged(null) } }
        DisposableEffect(Unit) { onDispose { contentViewModel.onPanelShapeInWindowChanged(null) } }

        Box(modifier = modifier.graphicsLayer { alpha = contentAlphaFromBrightnessMirror }) {
            OverlayShade(
@@ -156,7 +156,7 @@ constructor(
                enableTransparency = quickSettingsContainerViewModel.isTransparencyEnabled,
                onScrimClicked = contentViewModel::onScrimClicked,
                onBackgroundPlaced = { bounds, topCornerRadius, bottomCornerRadius ->
                    contentViewModel.onPanelShapeChanged(
                    contentViewModel.onPanelShapeInWindowChanged(
                        ShadeScrimShape(
                            bounds = ShadeScrimBounds(bounds),
                            topRadius = topCornerRadius.roundToInt(),
+42 −45
Original line number Diff line number Diff line
@@ -20,16 +20,17 @@ import android.content.res.Configuration
import android.content.testableContext
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
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.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
@@ -47,16 +48,13 @@ import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.shade.domain.interactor.enableSplitShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.core.StatusBarForDesktop
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel
import com.android.systemui.statusbar.policy.configurationController
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
@@ -64,7 +62,6 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

@@ -73,21 +70,20 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
            usingMediaInComposeFragment = false // This is not for the compose fragment
        }

    private val testScope = kosmos.testScope
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val underTest by lazy { kosmos.quickSettingsShadeOverlayContentViewModel }
    private val Kosmos.underTest by Kosmos.Fixture { quickSettingsShadeOverlayContentViewModel }

    @Before
    fun setUp() {
        kosmos.sceneContainerStartable.start()
        kosmos.enableDualShade()
        kosmos.runCurrent()
    fun setUp() =
        with(kosmos) {
            sceneContainerStartable.start()
            enableDualShade()
            runCurrent()
            underTest.activateIn(testScope)
        }

    @Test
    fun onScrimClicked_hidesShade() =
        testScope.runTest {
        kosmos.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)
@@ -99,7 +95,7 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

    @Test
    fun deviceLocked_hidesShade() =
        testScope.runTest {
        kosmos.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            unlockDevice()
            sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
@@ -112,7 +108,7 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

    @Test
    fun shadeNotTouchable_hidesShade() =
        testScope.runTest {
        kosmos.runTest {
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable)
            assertThat(isShadeTouchable).isTrue()
@@ -126,17 +122,17 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

    @Test
    fun shadeModeChanged_single_switchesToQuickSettingsScene() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)

            kosmos.enableDualShade()
            kosmos.shadeInteractor.expandQuickSettingsShade("test")
            enableDualShade()
            shadeInteractor.expandQuickSettingsShade("test")
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)

            kosmos.enableSingleShade()
            enableSingleShade()
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
@@ -144,17 +140,17 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

    @Test
    fun shadeModeChanged_split_switchesToShadeScene() =
        testScope.runTest {
        kosmos.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)

            kosmos.enableDualShade()
            kosmos.shadeInteractor.expandQuickSettingsShade("test")
            enableDualShade()
            shadeInteractor.expandQuickSettingsShade("test")
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)

            kosmos.enableSplitShade()
            enableSplitShade()
            runCurrent()
            assertThat(currentScene).isEqualTo(Scenes.Shade)
            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
@@ -162,9 +158,12 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {

    @Test
    fun onPanelShapeChanged() =
        testScope.runTest {
        kosmos.runTest {
            var actual: ShadeScrimShape? = null
            kosmos.notificationScrollViewModel.setQsScrimShapeConsumer { shape -> actual = shape }
            val disposable =
                notificationStackAppearanceInteractor.qsPanelShapeInWindow.observe { shape ->
                    actual = shape
                }

            val expected =
                ShadeScrimShape(
@@ -173,14 +172,16 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
                    bottomRadius = 100,
                )

            underTest.onPanelShapeChanged(expected)
            underTest.onPanelShapeInWindowChanged(expected)

            assertThat(actual).isEqualTo(expected)

            disposable.dispose()
        }

    @Test
    fun showHeader_desktopFeatureSetDisabled_true() =
        testScope.runTest {
        kosmos.runTest {
            setEnableDesktopFeatureSet(false)
            assertThat(underTest.showHeader).isTrue()
        }
@@ -188,7 +189,7 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
    @Test
    @EnableFlags(StatusBarForDesktop.FLAG_NAME)
    fun showHeader_desktopFeatureSetEnabled_statusBarForDesktopEnabled_false() =
        testScope.runTest {
        kosmos.runTest {
            setEnableDesktopFeatureSet(true)
            assertThat(underTest.showHeader).isFalse()
        }
@@ -196,36 +197,32 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
    @Test
    @DisableFlags(StatusBarForDesktop.FLAG_NAME)
    fun showHeader_desktopFeatureSetEnabled_statusBarForDesktopDisabled_true() =
        testScope.runTest {
        kosmos.runTest {
            setEnableDesktopFeatureSet(true)
            assertThat(underTest.showHeader).isTrue()
        }

    private fun setEnableDesktopFeatureSet(enable: Boolean) {
        kosmos.testableContext.orCreateTestableResources.addOverride(
    private fun Kosmos.setEnableDesktopFeatureSet(enable: Boolean) {
        testableContext.orCreateTestableResources.addOverride(
            R.bool.config_enableDesktopFeatureSet,
            enable,
        )
        kosmos.configurationController.onConfigurationChanged(Configuration())
        configurationController.onConfigurationChanged(Configuration())
    }

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

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

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

        assertThat(currentScene).isEqualTo(Scenes.Gone)
+25 −23
Original line number Diff line number Diff line
@@ -19,8 +19,9 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -28,7 +29,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@@ -37,12 +37,11 @@ import org.junit.runner.RunWith
class NotificationStackAppearanceInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val underTest = kosmos.notificationStackAppearanceInteractor
    private val Kosmos.underTest by Kosmos.Fixture { notificationStackAppearanceInteractor }

    @Test
    fun stackNotificationScrimBounds() =
        testScope.runTest {
        kosmos.runTest {
            val stackBounds by collectLastValue(underTest.notificationShadeScrimBounds)

            val bounds1 = ShadeScrimBounds(top = 100f, bottom = 200f)
@@ -54,10 +53,11 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
            assertThat(stackBounds).isEqualTo(bounds2)
        }

    @Test
    fun setQsPanelShape() =
        testScope.runTest {
        kosmos.runTest {
            var actual: ShadeScrimShape? = null
            underTest.setQsPanelShapeConsumer { shape -> actual = shape }
            underTest.qsPanelShapeInWindow.observe { shape -> actual = shape }

            val expected1 =
                ShadeScrimShape(
@@ -65,47 +65,49 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
                    topRadius = 0,
                    bottomRadius = 10,
                )
            underTest.setQsPanelShapeInWindow(expected1.copy())
            assertThat(actual).isEqualTo(expected1)

            val expected2 = expected1.copy(topRadius = 10)
            assertThat(expected2).isEqualTo(actual)
            underTest.setQsPanelShapeInWindow(expected2.copy())
            assertThat(actual).isEqualTo(expected2)
        }

    @Test
    fun stackRounding() =
        testScope.runTest {
        kosmos.runTest {
            val stackRounding by collectLastValue(underTest.shadeScrimRounding)

            kosmos.shadeRepository.setShadeLayoutWide(false)
            shadeRepository.setShadeLayoutWide(false)
            assertThat(stackRounding)
                .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false))

            kosmos.shadeRepository.setShadeLayoutWide(true)
            shadeRepository.setShadeLayoutWide(true)
            assertThat(stackRounding)
                .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true))
        }

    @Test(expected = IllegalStateException::class)
    fun stackNotificationScrimBounds_withImproperBounds_throwsException() =
        testScope.runTest {
        kosmos.runTest {
            underTest.setNotificationShadeScrimBounds(ShadeScrimBounds(top = 100f, bottom = 99f))
        }

    @Test(expected = IllegalStateException::class)
    fun setQsPanelShape_withImproperBounds_throwsException() =
        testScope.runTest {
        kosmos.runTest {
            val invalidBounds = ShadeScrimBounds(top = 0f, bottom = -10f)
            underTest.sendQsPanelShape(
            underTest.setQsPanelShapeInWindow(
                ShadeScrimShape(bounds = invalidBounds, topRadius = 10, bottomRadius = 10)
            )
        }

    @Test
    fun shouldCloseGuts_userInputOngoing_currentGestureInGuts() =
        testScope.runTest {
        kosmos.runTest {
            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)

            kosmos.sceneInteractor.onSceneContainerUserInputStarted()
            sceneInteractor.onSceneContainerUserInputStarted()
            underTest.setCurrentGestureInGuts(true)

            assertThat(shouldCloseGuts).isFalse()
@@ -113,10 +115,10 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {

    @Test
    fun shouldCloseGuts_userInputOngoing_currentGestureNotInGuts() =
        testScope.runTest {
        kosmos.runTest {
            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)

            kosmos.sceneInteractor.onSceneContainerUserInputStarted()
            sceneInteractor.onSceneContainerUserInputStarted()
            underTest.setCurrentGestureInGuts(false)

            assertThat(shouldCloseGuts).isTrue()
@@ -124,10 +126,10 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {

    @Test
    fun shouldCloseGuts_userInputNotOngoing_currentGestureInGuts() =
        testScope.runTest {
        kosmos.runTest {
            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)

            kosmos.sceneInteractor.onUserInputFinished()
            sceneInteractor.onUserInputFinished()
            underTest.setCurrentGestureInGuts(true)

            assertThat(shouldCloseGuts).isFalse()
@@ -135,10 +137,10 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {

    @Test
    fun shouldCloseGuts_userInputNotOngoing_currentGestureNotInGuts() =
        testScope.runTest {
        kosmos.runTest {
            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)

            kosmos.sceneInteractor.onUserInputFinished()
            sceneInteractor.onUserInputFinished()
            underTest.setCurrentGestureInGuts(false)

            assertThat(shouldCloseGuts).isFalse()
+139 −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.statusbar.notification.stack.ui.viewmodel

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
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.scene.domain.startable.sceneContainerStartable
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.android.systemui.util.state.SynchronouslyObservableState
import com.android.systemui.util.state.observableStateOf
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
class NotificationScrollViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val Kosmos.underTest by Kosmos.Fixture { notificationScrollViewModel }

    @Before
    fun setUp() =
        with(kosmos) {
            sceneContainerStartable.start()
            enableDualShade()
            runCurrent()
            underTest.activateIn(testScope)
        }

    @Test
    fun getQsPanelScrim_clears() =
        kosmos.runTest {
            var actual: ShadeScrimShape? = null
            val viewLeft = observableStateOf(10)
            val disposable = underTest.getQsScrimShape(viewLeft).observe { shape -> actual = shape }

            assertThat(actual).isNull()
            notificationStackAppearanceInteractor.setQsPanelShapeInWindow(
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
                    topRadius = 0,
                    bottomRadius = 100,
                )
            )
            assertThat(actual).isNotNull()
            notificationStackAppearanceInteractor.setQsPanelShapeInWindow(null)
            assertThat(actual).isNull()

            disposable.dispose()
        }

    @Test
    fun getQsPanelScrim_includesLeftOffset() =
        kosmos.runTest {
            var actual: ShadeScrimShape? = null
            val viewLeft = observableStateOf(10)
            val disposable = underTest.getQsScrimShape(viewLeft).observe { shape -> actual = shape }

            val shapeInWindow =
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
                    topRadius = 0,
                    bottomRadius = 100,
                )

            val expected =
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(left = 0f, top = 0f, right = 700f, bottom = 600f),
                    topRadius = 0,
                    bottomRadius = 100,
                )

            notificationStackAppearanceInteractor.setQsPanelShapeInWindow(shapeInWindow)

            assertThat(actual).isEqualTo(expected)

            disposable.dispose()
        }

    @Test
    fun getQsPanelScrim_updatesWhenLeftOffsetUpdates() =
        kosmos.runTest {
            var actual: ShadeScrimShape? = null
            val viewLeft = SynchronouslyObservableState(0)
            val disposable = underTest.getQsScrimShape(viewLeft).observe { shape -> actual = shape }

            val shapeInWindow =
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
                    topRadius = 0,
                    bottomRadius = 100,
                )

            notificationStackAppearanceInteractor.setQsPanelShapeInWindow(shapeInWindow)

            assertThat(actual).isEqualTo(shapeInWindow)

            viewLeft.value = 10

            val expected =
                ShadeScrimShape(
                    bounds = ShadeScrimBounds(left = 0f, top = 0f, right = 700f, bottom = 600f),
                    topRadius = 0,
                    bottomRadius = 100,
                )

            assertThat(actual).isEqualTo(expected)

            disposable.dispose()
        }
}
+145 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading