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

Commit 36c731bf authored by burakov's avatar burakov
Browse files

[Dual Shade] Fix blur and depth effects in scene container.

`NotificationShadeDepthController` is the class responsible for
controlling the blur and depth effects for the wallpaper. While it is
part of the legacy code (pre-scene container), it is still active in
scene container too, yet its state was partially left stale in the
latter.

BONUS: Remove `legacyPanelExpansion` from `PanelExpansionInteractor` and
`NotificationPanelViewController`, as both are no longer used after this
CL.

Summary of the depth controller state that is being changed in this CL:

- `legacyShadeExpansion` was marking the shade as fully expanded when
  transitioning to the lockscreen, which meant we were not unblurring
  during that transition. It is now replaced with `shadeExpansion`.

- `qsPanelExpansion` was only being set from the legacy
  `QuickSettingsControllerImpl`, which isn't active in scene container.
  It is now set based on `shadeInteractor`.

- `transitionToFullShadeProgress` was only being set from the legacy
  `LockscreenShadeTransitionController`, which isn't active in scene
  container. It is now set based on `shadeInteractor`.

Bug: 388470180
Bug: 370556579
Test: Updated unit tests.
Test: Manually by opening the notifications shade and quick settings
 shade over both lock screen and launcher, and verifying correct blur
 behavior during the transitions, incl. switching between the shades.
Flag: com.android.systemui.scene_container
Change-Id: Ie66ea3e358441c14d9ef22373ac4e5a976d15638
parent 2b5a8e70
Loading
Loading
Loading
Loading
+2 −189
Original line number Diff line number Diff line
@@ -19,24 +19,17 @@ package com.android.systemui.shade.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.ObservableTransitionState.Transition.ShowOrHideOverlay
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
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.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -46,13 +39,13 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class PanelExpansionInteractorImplTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor }
    private val transitionState =
@@ -68,118 +61,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
        sceneInteractor.setTransitionState(transitionState)
    }

    @Test
    @EnableSceneContainer
    fun legacyPanelExpansion_whenIdle_whenLocked() =
        testScope.runTest {
            underTest = kosmos.panelExpansionInteractorImpl
            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)

            changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            showOverlay(Overlays.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.Shade) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.QuickSettings) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)
        }

    @Test
    @EnableSceneContainer
    fun legacyPanelExpansion_whenIdle_whenUnlocked() =
        testScope.runTest {
            underTest = kosmos.panelExpansionInteractorImpl
            val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()

            assertThat(unlockStatus)
                .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint))

            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)

            changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
            assertThat(panelExpansion).isEqualTo(0f)

            changeScene(Scenes.Shade) { progress -> assertThat(panelExpansion).isEqualTo(progress) }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.QuickSettings) {
                // Shade's already expanded, so moving to QS should also be 1f.
                assertThat(panelExpansion).isEqualTo(1f)
            }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)
        }

    @Test
    @EnableSceneContainer
    fun legacyPanelExpansion_dualShade_whenIdle_whenLocked() =
        testScope.runTest {
            underTest = kosmos.panelExpansionInteractorImpl
            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)

            changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            showOverlay(Overlays.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            showOverlay(Overlays.NotificationsShade) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            showOverlay(Overlays.QuickSettingsShade) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)
        }

    @Test
    @EnableSceneContainer
    fun legacyPanelExpansion_dualShade_whenIdle_whenUnlocked() =
        testScope.runTest {
            underTest = kosmos.panelExpansionInteractorImpl
            val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()

            assertThat(unlockStatus)
                .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint))

            val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)

            changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
            assertThat(panelExpansion).isEqualTo(0f)

            showOverlay(Overlays.NotificationsShade) { progress ->
                assertThat(panelExpansion).isEqualTo(progress)
            }
            assertThat(panelExpansion).isEqualTo(1f)

            showOverlay(Overlays.QuickSettingsShade) {
                // Notification shade is already expanded, so moving to QS shade should also be 1f.
                assertThat(panelExpansion).isEqualTo(1f)
            }
            assertThat(panelExpansion).isEqualTo(1f)

            changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
            assertThat(panelExpansion).isEqualTo(1f)
        }

    @Test
    @EnableSceneContainer
    fun shouldHideStatusBarIconsWhenExpanded_goneScene() =
@@ -250,72 +131,4 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {

        assertThat(currentScene).isEqualTo(toScene)
    }

    private fun TestScope.showOverlay(
        toOverlay: OverlayKey,
        assertDuringProgress: ((progress: Float) -> Unit) = {},
    ) {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
        val progressFlow = MutableStateFlow(0f)
        transitionState.value =
            if (checkNotNull(currentOverlays).isEmpty()) {
                ShowOrHideOverlay(
                    overlay = toOverlay,
                    fromContent = checkNotNull(currentScene),
                    toContent = toOverlay,
                    currentScene = checkNotNull(currentScene),
                    currentOverlays = flowOf(emptySet()),
                    progress = progressFlow,
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            } else {
                ObservableTransitionState.Transition.ReplaceOverlay(
                    fromOverlay = checkNotNull(currentOverlays).first(),
                    toOverlay = toOverlay,
                    currentScene = checkNotNull(currentScene),
                    currentOverlays = flowOf(emptySet()),
                    progress = progressFlow,
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            }
        runCurrent()
        assertDuringProgress(progressFlow.value)

        progressFlow.value = 0.2f
        runCurrent()
        assertDuringProgress(progressFlow.value)

        progressFlow.value = 0.6f
        runCurrent()
        assertDuringProgress(progressFlow.value)

        progressFlow.value = 1f
        runCurrent()
        assertDuringProgress(progressFlow.value)

        transitionState.value =
            ObservableTransitionState.Idle(
                currentScene = checkNotNull(currentScene),
                currentOverlays = setOf(toOverlay),
            )
        if (checkNotNull(currentOverlays).isEmpty()) {
            fakeSceneDataSource.showOverlay(toOverlay)
        } else {
            fakeSceneDataSource.replaceOverlay(
                from = checkNotNull(currentOverlays).first(),
                to = toOverlay,
            )
        }
        runCurrent()
        assertDuringProgress(progressFlow.value)

        assertThat(currentOverlays).containsExactly(toOverlay)
    }
}
+62 −20
Original line number Diff line number Diff line
@@ -17,13 +17,13 @@
package com.android.systemui.shade.domain.startable

import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
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.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.flags.EnableSceneContainer
@@ -31,29 +31,33 @@ import com.android.systemui.flags.parameterizeSceneContainerFlag
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.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.shade.domain.interactor.enableSplitShade
import com.android.systemui.shade.domain.interactor.shadeMode
import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.statusbar.notificationShadeDepthController
import com.android.systemui.statusbar.phone.scrimController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
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
import org.mockito.kotlin.verify
@@ -62,16 +66,20 @@ import platform.test.runner.parameterized.Parameters

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val shadeExpansionStateManager by lazy { kosmos.shadeExpansionStateManager }
    private val fakeConfigurationRepository by lazy { kosmos.fakeConfigurationRepository }
    private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
    private val shadeDepthController by lazy { kosmos.notificationShadeDepthController }
    private val shadeExpansionStateManager by lazy {
        kosmos.shadeExpansionStateManager.also { it.addExpansionListener(shadeDepthController) }
    }

    private val underTest: ShadeStartable = kosmos.shadeStartable
    private lateinit var underTest: ShadeStartable

    companion object {
        @JvmStatic
@@ -85,43 +93,50 @@ class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
        mSetFlagsRule.setFlagsParameterization(flags)
    }

    @Before
    fun setUp() {
        underTest = kosmos.shadeStartable
    }

    @Test
    fun hydrateShadeMode_dualShadeDisabled() =
        testScope.runTest {
            overrideResource(R.bool.config_use_split_notification_shade, false)
            kosmos.disableDualShade()
            val shadeMode by collectLastValue(kosmos.shadeMode)

            val isShadeLayoutWide by collectLastValue(kosmos.shadeModeInteractor.isShadeLayoutWide)
            underTest.start()

            kosmos.enableSingleShade()
            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
            assertThat(isShadeLayoutWide).isFalse()

            overrideResource(R.bool.config_use_split_notification_shade, true)
            fakeConfigurationRepository.onAnyConfigurationChange()
            kosmos.enableSplitShade()
            assertThat(shadeMode).isEqualTo(ShadeMode.Split)
            assertThat(isShadeLayoutWide).isTrue()

            overrideResource(R.bool.config_use_split_notification_shade, false)
            fakeConfigurationRepository.onAnyConfigurationChange()
            kosmos.enableSingleShade()
            assertThat(shadeMode).isEqualTo(ShadeMode.Single)
            assertThat(isShadeLayoutWide).isFalse()
        }

    @Test
    @EnableSceneContainer
    fun hydrateShadeMode_dualShadeEnabled() =
        testScope.runTest {
            overrideResource(R.bool.config_use_split_notification_shade, false)
            kosmos.enableDualShade()
            val shadeMode by collectLastValue(kosmos.shadeMode)

            val isShadeLayoutWide by collectLastValue(kosmos.shadeModeInteractor.isShadeLayoutWide)
            underTest.start()

            kosmos.enableDualShade(wideLayout = false)
            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
            assertThat(isShadeLayoutWide).isFalse()

            overrideResource(R.bool.config_use_split_notification_shade, true)
            fakeConfigurationRepository.onAnyConfigurationChange()
            kosmos.enableDualShade(wideLayout = true)
            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
            assertThat(isShadeLayoutWide).isTrue()

            overrideResource(R.bool.config_use_split_notification_shade, false)
            fakeConfigurationRepository.onAnyConfigurationChange()
            kosmos.enableDualShade(wideLayout = false)
            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
            assertThat(isShadeLayoutWide).isFalse()
        }

    @Test
@@ -163,7 +178,34 @@ class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {

            changeScene(Scenes.Shade, transitionState) { progress ->
                assertThat(latestChangeEvent?.fraction).isEqualTo(progress)
                assertThat(shadeDepthController.qsPanelExpansion).isZero()
                assertThat(shadeDepthController.shadeExpansion).isEqualTo(progress)
                assertThat(shadeDepthController.transitionToFullShadeProgress).isEqualTo(progress)
            }
            assertThat(currentScene).isEqualTo(Scenes.Shade)

            changeScene(Scenes.QuickSettings, transitionState) { progress ->
                assertThat(latestChangeEvent?.fraction).isEqualTo(1 - progress)
                assertThat(shadeDepthController.qsPanelExpansion).isEqualTo(progress)
                assertThat(shadeDepthController.shadeExpansion).isEqualTo(1 - progress)
                assertThat(shadeDepthController.transitionToFullShadeProgress)
                    .isEqualTo(
                        max(
                            shadeDepthController.qsPanelExpansion,
                            shadeDepthController.shadeExpansion,
                        )
                    )
            }
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)

            changeScene(Scenes.Lockscreen, transitionState) { progress ->
                assertThat(latestChangeEvent?.fraction).isZero()
                assertThat(shadeDepthController.qsPanelExpansion).isEqualTo(1 - progress)
                assertThat(shadeDepthController.shadeExpansion).isZero()
                assertThat(shadeDepthController.transitionToFullShadeProgress)
                    .isEqualTo(1 - progress)
            }
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
+0 −6
Original line number Diff line number Diff line
@@ -210,7 +210,6 @@ import dagger.Lazy;
import kotlin.Unit;

import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.flow.StateFlow;

@@ -3123,11 +3122,6 @@ public final class NotificationPanelViewController implements
        return mShadeRepository.getUdfpsTransitionToFullShadeProgress();
    }

    @Override
    public Flow<Float> getLegacyPanelExpansion() {
        return  mShadeRepository.getLegacyShadeExpansion();
    }

    @Override
    public boolean isFullyExpanded() {
        return mExpandedHeight >= getMaxPanelTransitionDistance();
+1 −5
Original line number Diff line number Diff line
@@ -29,11 +29,7 @@ import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject

/**
 * A class responsible for managing the notification panel's current state.
 *
 * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
 */
/** A class responsible for managing the notification panel's current state. */
@SysUISingleton
@Deprecated("Use ShadeInteractor instead")
class ShadeExpansionStateManager @Inject constructor() {
+0 −3
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf

/** Empty implementation of ShadeViewController for variants with no shade. */
open class ShadeViewControllerEmptyImpl @Inject constructor() :
@@ -111,8 +110,6 @@ open class ShadeViewControllerEmptyImpl @Inject constructor() :

    override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
    override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
    @Deprecated("Use SceneInteractor.currentScene instead.")
    override val legacyPanelExpansion = flowOf(0f)
    override val udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
}

Loading