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

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

Merge "Add adb commands for instant shade expand/collapse" into main

parents e8378edc bc29e3fc
Loading
Loading
Loading
Loading
+150 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.shade

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.compose.animation.scene.OverlayKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.scene.data.repository.setSceneTransition
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.shade.domain.interactor.enableDualShade
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@EnableSceneContainer
@RunWith(AndroidJUnit4::class)
class ShadeInstantExpansionCommandsTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()

    @Test
    fun commandShadeShowNotifications_singleShade() =
        kosmos.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            shadeInstantExpansionCommands.start()
            commandRegistry.onShellCommand(
                PrintWriter(StringWriter()),
                arrayOf("expand-notifications-instant"),
            )
            assertThat(currentScene).isEqualTo(Scenes.Shade)
        }

    @Test
    fun commandShadeShowNotifications_dualShade() =
        kosmos.runTest {
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            shadeInstantExpansionCommands.start()
            commandRegistry.onShellCommand(
                PrintWriter(StringWriter()),
                arrayOf("expand-notifications-instant"),
            )
            assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
        }

    @Test
    fun commandShadeShowQs_singleShade() =
        kosmos.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            shadeInstantExpansionCommands.start()
            commandRegistry.onShellCommand(
                PrintWriter(StringWriter()),
                arrayOf("expand-settings-instant"),
            )
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
        }

    @Test
    fun commandShadeShowQs_dualShade() =
        kosmos.runTest {
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            shadeInstantExpansionCommands.start()
            commandRegistry.onShellCommand(
                PrintWriter(StringWriter()),
                arrayOf("expand-settings-instant"),
            )
            assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
        }

    @Test
    fun commandShadeHideNotifications_singleShade() =
        kosmos.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            shadeInstantExpansionCommands.start()
            sceneInteractor.changeScene(Scenes.Shade, "test")
            setSceneTransition(Idle(Scenes.Shade))
            commandRegistry.onShellCommand(PrintWriter(StringWriter()), arrayOf("collapse-instant"))
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun commandShadeHideNotifications_dualShade() =
        kosmos.runTest {
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            shadeInstantExpansionCommands.start()
            setOverlay(Overlays.NotificationsShade)
            commandRegistry.onShellCommand(PrintWriter(StringWriter()), arrayOf("collapse-instant"))
            assertThat(currentOverlays).isEmpty()
        }

    @Test
    fun commandShadeHideQs_singleShade() =
        kosmos.runTest {
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            shadeInstantExpansionCommands.start()
            sceneInteractor.changeScene(Scenes.QuickSettings, "test")
            setSceneTransition(Idle(Scenes.QuickSettings))
            commandRegistry.onShellCommand(PrintWriter(StringWriter()), arrayOf("collapse-instant"))
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun commandShadeHideQs_dualShade() =
        kosmos.runTest {
            enableDualShade()
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            setOverlay(Overlays.QuickSettingsShade)
            shadeInstantExpansionCommands.start()
            commandRegistry.onShellCommand(PrintWriter(StringWriter()), arrayOf("collapse-instant"))
            assertThat(currentOverlays).isEmpty()
        }

    private fun Kosmos.setOverlay(overlay: OverlayKey) {
        val currentScene by collectLastValue(sceneInteractor.currentScene)
        val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
        sceneInteractor.showOverlay(overlay, "test")
        setSceneTransition(Idle(checkNotNull(currentScene), checkNotNull(currentOverlays)))
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -165,8 +165,6 @@ public interface ShadeController extends CoreStartable {

    /**
     * Close the shade if it was open
     *
     * @return true if the shade was open, else false
     */
    void collapseShade();

+98 −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.shade

import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.TransitionKeys
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider

@SysUISingleton
class ShadeInstantExpansionCommands
@Inject
constructor(
    private val commandRegistry: CommandRegistry,
    private val instantExpandNotificationsCommand: Provider<InstantExpandNotificationsCommand>,
    private val instantExpandQsCommand: Provider<InstantExpandQsCommand>,
    private val instantCollapseShadeCommand: Provider<InstantCollapseShadeCommand>,
) : CoreStartable {

    override fun start() {
        if (SceneContainerFlag.isEnabled) {
            commandRegistry.registerCommand("expand-notifications-instant") {
                instantExpandNotificationsCommand.get()
            }
            commandRegistry.registerCommand("expand-settings-instant") {
                instantExpandQsCommand.get()
            }
            commandRegistry.registerCommand("collapse-instant") {
                instantCollapseShadeCommand.get()
            }
        }
    }
}

class InstantExpandNotificationsCommand
@Inject
constructor(private val shadeInteractor: ShadeInteractor) : Command {
    override fun execute(pw: PrintWriter, args: List<String>) {
        shadeInteractor.expandNotificationsShade("adb command", TransitionKeys.Instant)
        pw.println("Showing Notifications shade")
    }

    override fun help(pw: PrintWriter) {
        pw.println("expand-notifications-instant")
        pw.println("expands the Notifications shade without animating")
        pw.println()
    }
}

class InstantExpandQsCommand
@Inject
constructor(private val shadeInteractor: ShadeInteractor) : Command {
    override fun execute(pw: PrintWriter, args: List<String>) {
        shadeInteractor.expandQuickSettingsShade("adb command", TransitionKeys.Instant)
        pw.println("Showing Quick Settings shade")
    }

    override fun help(pw: PrintWriter) {
        pw.println("expand-settings-instant")
        pw.println("expands the Quick Settings shade without animating")
        pw.println()
    }
}

class InstantCollapseShadeCommand
@Inject
constructor(private val shadeInteractor: ShadeInteractor) : Command {
    override fun execute(pw: PrintWriter, args: List<String>) {
        shadeInteractor.collapseEitherShade("adb command", TransitionKeys.Instant)
        pw.println("hiding any expanded shade")
    }

    override fun help(pw: PrintWriter) {
        pw.println("collapse-instant")
        pw.println("collapses any expanded shade without animating")
        pw.println()
    }
}
+9 −2
Original line number Diff line number Diff line
@@ -39,10 +39,17 @@ internal abstract class StartShadeModule {
    @Binds
    @IntoMap
    @ClassKey(ShadeStartable::class)
    abstract fun provideShadeStartable(startable: ShadeStartable): CoreStartable
    abstract fun bindShadeStartable(startable: ShadeStartable): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(ShadeStateTraceLogger::class)
    abstract fun provideShadeStateTraceLogger(startable: ShadeStateTraceLogger): CoreStartable
    abstract fun bindShadeStateTraceLogger(startable: ShadeStateTraceLogger): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(ShadeInstantExpansionCommands::class)
    abstract fun bindShadeInstantExpansionCommands(
        startable: ShadeInstantExpansionCommands
    ): CoreStartable
}
+16 −7
Original line number Diff line number Diff line
@@ -149,9 +149,21 @@ constructor(
                loggingReason = loggingReason,
                transitionKey = transitionKey,
            )
        } else {
            changeSingeShadeScene(Scenes.Shade, transitionKey, loggingReason)
        }
    }

    private fun changeSingeShadeScene(
        sceneKey: SceneKey,
        transitionKey: TransitionKey?,
        loggingReason: String,
    ) {
        if (transitionKey == Instant) {
            sceneInteractor.snapToScene(sceneKey, loggingReason)
        } else {
            sceneInteractor.changeScene(
                toScene = Scenes.Shade,
                toScene = sceneKey,
                loggingReason = loggingReason,
                transitionKey =
                    transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade },
@@ -174,12 +186,9 @@ constructor(
                transitionKey = transitionKey,
            )
        } else {
            val isSplitShade = shadeModeInteractor.isSplitShade
            sceneInteractor.changeScene(
                toScene = if (isSplitShade) Scenes.Shade else Scenes.QuickSettings,
                loggingReason = loggingReason,
                transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade },
            )
            val toScene =
                if (shadeModeInteractor.isSplitShade) Scenes.Shade else Scenes.QuickSettings
            changeSingeShadeScene(toScene, transitionKey, loggingReason)
        }
    }

Loading