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

Commit b8dc234f authored by Ahmed Mehfooz's avatar Ahmed Mehfooz Committed by Android (Google) Code Review
Browse files

Merge "[Flexi][DualShade] Add support to toggle QS and notification shades" into main

parents 250ccd76 f906766d
Loading
Loading
Loading
Loading
+144 −1
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith

@@ -236,6 +237,58 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
            assertThat(actual).isFalse()
        }

    @Test
    fun toggleNotificationsShade_singleShade_throwsException() =
        kosmos.runTest {
            // GIVEN single shade is enabled
            enableSingleShade()

            // WHEN the notifications shade is toggled
            // THEN an IllegalStateException is thrown
            assertThrows(IllegalStateException::class.java) {
                underTest.toggleNotificationsShade("reason")
            }
        }

    @Test
    fun toggleNotificationsShade_splitShade_throwsException() =
        kosmos.runTest {
            // GIVEN split shade is enabled
            enableSplitShade()

            // WHEN the notifications shade is toggled
            // THEN an IllegalStateException is thrown
            assertThrows(IllegalStateException::class.java) {
                underTest.toggleNotificationsShade("reason")
            }
        }

    @Test
    fun toggleQuickSettingsShade_singleShade_throwsException() =
        kosmos.runTest {
            // GIVEN single shade is enabled
            enableSingleShade()

            // WHEN the quick settings shade is toggled
            // THEN an IllegalStateException is thrown
            assertThrows(IllegalStateException::class.java) {
                underTest.toggleQuickSettingsShade("reason")
            }
        }

    @Test
    fun toggleQuickSettingsShade_splitShade_throwsException() =
        kosmos.runTest {
            // GIVEN split shade is enabled
            enableSplitShade()

            // WHEN the quick settings shade is toggled
            // THEN an IllegalStateException is thrown
            assertThrows(IllegalStateException::class.java) {
                underTest.toggleQuickSettingsShade("reason")
            }
        }

    @Test
    fun lockscreenShadeExpansion_idle_onScene() =
        kosmos.runTest {
@@ -862,6 +915,96 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun toggleNotificationsShade_dualShade_showsNotificationsOverlay() =
        kosmos.runTest {
            // GIVEN dual shade is enabled and no overlays are open
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            assertThat(currentOverlays).isEmpty()

            // WHEN the notifications shade is toggled
            underTest.toggleNotificationsShade("reason")

            // THEN the notifications overlay is now visible
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
        }

    @Test
    fun toggleNotificationsShade_dualShadeWithNotificationsOpen_hidesOverlay() =
        kosmos.runTest {
            // GIVEN dual shade is enabled and the notifications overlay is open
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            openShade(Overlays.NotificationsShade)

            // WHEN the notifications shade is toggled
            underTest.toggleNotificationsShade("reason")

            // THEN all overlays are hidden
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun toggleNotificationsShade_dualShadeWithQsOpen_replacesWithNotificationsOverlay() =
        kosmos.runTest {
            // GIVEN dual shade is enabled and the QS overlay is open
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            openShade(Overlays.QuickSettingsShade)

            // WHEN the notifications shade is toggled
            underTest.toggleNotificationsShade("reason")

            // THEN the QS overlay is replaced by the notifications overlay
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
        }

    @Test
    fun toggleQuickSettingsShade_dualShade_showsQsOverlay() =
        kosmos.runTest {
            // GIVEN dual shade is enabled and no overlays are open
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            assertThat(currentOverlays).isEmpty()

            // WHEN the QS shade is toggled
            underTest.toggleQuickSettingsShade("reason")

            // THEN the QS overlay is now visible
            assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
        }

    @Test
    fun toggleQuickSettingsShade_dualShadeWithQsOpen_hidesOverlay() =
        kosmos.runTest {
            // GIVEN dual shade is enabled and the QS overlay is open
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            openShade(Overlays.QuickSettingsShade)

            // WHEN the QS shade is toggled
            underTest.toggleQuickSettingsShade("reason")

            // THEN all overlays are hidden
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun toggleQuickSettingsShade_dualShadeWithNotificationsOpen_replacesWithQsOverlay() =
        kosmos.runTest {
            // GIVEN dual shade is enabled and the notifications overlay is open
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            openShade(Overlays.NotificationsShade)

            // WHEN the QS shade is toggled
            underTest.toggleQuickSettingsShade("reason")

            // THEN the notifications overlay is replaced by the QS overlay
            assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
        }

    private fun Kosmos.openShade(overlay: OverlayKey) {
        val shadeMode by collectLastValue(shadeMode)
        val isAnyExpanded by collectLastValue(underTest.isAnyExpanded)
+9 −0
Original line number Diff line number Diff line
@@ -77,6 +77,9 @@ interface BaseShadeInteractor {
    /** The amount [0-1] that the Notifications Shade has been opened. */
    val shadeExpansion: StateFlow<Float>

    /** Whether the Notifications Shade is expanded a non-zero amount. */
    val isNotificationsExpanded: StateFlow<Boolean>

    /**
     * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
     * report 0f. If split shade is enabled, value matches shadeExpansion.
@@ -141,6 +144,12 @@ interface BaseShadeInteractor {
        bypassNotificationsShade: Boolean = false,
    )

    /** Toggles the Notifications shade. Will replace the QuickSettings shade if it's open. */
    fun toggleNotificationsShade(loggingReason: String, transitionKey: TransitionKey? = null)

    /** Toggles the Quick Settings shade. Will replace the Notifications shade if it's open. */
    fun toggleQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey? = null)

    /**
     * Triggers the collapse (closing) of the notifications shade or quick settings shade, whichever
     * is open. If both are already collapsed, this has no effect.
+5 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
    override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean
    override val isQsEnabled: StateFlow<Boolean> = inactiveFlowBoolean
    override val shadeExpansion: StateFlow<Float> = inactiveFlowFloat
    override val isNotificationsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
    override val isShadeAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
    override val qsExpansion: StateFlow<Float> = inactiveFlowFloat
    override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
@@ -59,5 +60,9 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
        bypassNotificationsShade: Boolean,
    ) {}

    override fun toggleNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}

    override fun toggleQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {}

    override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {}
}
+16 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -82,6 +83,9 @@ constructor(
            .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
            .stateIn(scope, SharingStarted.Eagerly, 0f)

    @Deprecated("Do not use. isNotificationsExpanded is only relevant in SceneContainer")
    override val isNotificationsExpanded: StateFlow<Boolean> = MutableStateFlow(false)

    override val qsExpansion: StateFlow<Float> = repository.qsExpansion

    override val isQsExpanded: StateFlow<Boolean> = repository.legacyIsQsExpanded
@@ -138,6 +142,18 @@ constructor(
        )
    }

    override fun toggleNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
        throw UnsupportedOperationException(
            "toggleNotificationShade() is not supported in legacy shade"
        )
    }

    override fun toggleQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
        throw UnsupportedOperationException(
            "toggleQuickSettingsShade() is not supported in legacy shade"
        )
    }

    override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {
        throw UnsupportedOperationException(
            "collapseEitherShade() is not supported in legacy shade"
+32 −1
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ import kotlinx.coroutines.flow.stateIn
class ShadeInteractorSceneContainerImpl
@Inject
constructor(
    @Application scope: CoroutineScope,
    @Application private val scope: CoroutineScope,
    private val sceneInteractor: SceneInteractor,
    private val shadeModeInteractor: ShadeModeInteractor,
) : BaseShadeInteractor {
@@ -67,6 +67,9 @@ constructor(
            .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
            .stateIn(scope, SharingStarted.Eagerly, 0f)

    override val isNotificationsExpanded: StateFlow<Boolean> =
        shadeExpansion.map { it > 0 }.stateIn(scope, SharingStarted.Eagerly, false)

    override val qsExpansion: StateFlow<Float> =
        shadeModeInteractor.shadeMode
            .flatMapLatest { shadeMode -> transitionProgressExpansion(shadeMode.qsContentKey) }
@@ -261,6 +264,34 @@ constructor(
        }
    }

    override fun toggleNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
        check(shadeModeInteractor.isDualShade) {
            "toggleNotificationsShade should only be called when dualShade is enabled."
        }
        if (isNotificationsExpanded.value) {
            android.util.Log.e("amehfooz", "Collapse notification shade")
            collapseNotificationsShade(loggingReason, transitionKey)
        } else {
            android.util.Log.e("amehfooz", "Expand Notifications Shade")
            expandNotificationsShade(loggingReason, transitionKey)
        }
    }

    override fun toggleQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
        check(shadeModeInteractor.isDualShade) {
            "toggleQuickSettingsShade should only be called when dualShade is enabled."
        }
        if (isQsExpanded.value) {
            collapseQuickSettingsShade(
                loggingReason = loggingReason,
                transitionKey = transitionKey,
                bypassNotificationsShade = true,
            )
        } else {
            expandQuickSettingsShade(loggingReason, transitionKey)
        }
    }

    override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {
        // Note: The notifications shade and QS shade may be both partially expanded simultaneously,
        // so we don't use an 'else' clause here.