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

Commit ab5b8c68 authored by Justin Weir's avatar Justin Weir
Browse files

Shade back action handling for scenes

Extracts the shade's contract with the back action interactor from
ShadeViewController into its own interface. Adds a new implementation
of that interface backed by the scene container.

Test: added new test
Test: manual
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Fixes: 323579909

Change-Id: I27fc9c69ebd50641e02a5eb3eb9c1d958e7ef3e0
parent adc76590
Loading
Loading
Loading
Loading
+108 −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.domain.interactor

import android.content.applicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeBackActionInteractorImplTest : SysuiTestCase() {
    val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
    val testScope = kosmos.testScope
    val sceneInteractor = kosmos.sceneInteractor
    val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
    val underTest = kosmos.shadeBackActionInteractor

    @Before
    fun ignoreSplitShade() {
        Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext))
    }

    @Test
    fun animateCollapseQs_notOnQs() =
        testScope.runTest {
            setScene(SceneKey.Shade)
            underTest.animateCollapseQs(true)
            runCurrent()
            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
        }

    @Test
    fun animateCollapseQs_fullyCollapse_entered() =
        testScope.runTest {
            enterDevice()
            setScene(SceneKey.QuickSettings)
            underTest.animateCollapseQs(true)
            runCurrent()
            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
        }

    @Test
    fun animateCollapseQs_fullyCollapse_locked() =
        testScope.runTest {
            deviceEntryRepository.setUnlocked(false)
            setScene(SceneKey.QuickSettings)
            underTest.animateCollapseQs(true)
            runCurrent()
            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
        }

    @Test
    fun animateCollapseQs_notFullyCollapse() =
        testScope.runTest {
            setScene(SceneKey.QuickSettings)
            underTest.animateCollapseQs(false)
            runCurrent()
            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
        }

    private fun enterDevice() {
        deviceEntryRepository.setUnlocked(true)
        testScope.runCurrent()
        setScene(SceneKey.Gone)
    }

    private fun setScene(key: SceneKey) {
        sceneInteractor.changeScene(SceneModel(key), "test")
        sceneInteractor.setTransitionState(
            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
        )
        testScope.runCurrent()
    }
}
+10 −10
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -64,8 +64,8 @@ constructor(
                }

                override fun onBackProgressed(backEvent: BackEvent) {
                    if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
                        shadeViewController.onBackProgressed(backEvent.progress)
                    if (shouldBackBeHandled() && shadeBackActionInteractor.canBeCollapsed()) {
                        shadeBackActionInteractor.onBackProgressed(backEvent.progress)
                    }
                }
            }
@@ -77,12 +77,12 @@ constructor(
        get() =
            notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher

    private lateinit var shadeViewController: ShadeViewController
    private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
    private lateinit var qsController: QuickSettingsController

    fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
    fun setup(qsController: QuickSettingsController, svController: ShadeBackActionInteractor) {
        this.qsController = qsController
        this.shadeViewController = svController
        this.shadeBackActionInteractor = svController
    }

    override fun start() {
@@ -114,16 +114,16 @@ constructor(
            return true
        }
        if (qsController.expanded) {
            shadeViewController.animateCollapseQs(false)
            shadeBackActionInteractor.animateCollapseQs(false)
            return true
        }
        if (shadeViewController.closeUserSwitcherIfOpen()) {
        if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
            return true
        }
        if (shouldBackBeHandled()) {
            if (shadeViewController.canBeCollapsed()) {
            if (shadeBackActionInteractor.canBeCollapsed()) {
                // this is the Shade dismiss animation, so make sure QQS closes when it ends.
                shadeViewController.onBackPressed()
                shadeBackActionInteractor.onBackPressed()
                shadeController.animateCollapseShade()
            }
            return true
+5 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
import dagger.Binds
@@ -33,6 +34,10 @@ abstract class ShadeEmptyImplModule {
    @SysUISingleton
    abstract fun bindsShadeViewController(svc: ShadeViewControllerEmptyImpl): ShadeViewController

    @Binds
    @SysUISingleton
    abstract fun bindsShadeBack(sbai: ShadeViewControllerEmptyImpl): ShadeBackActionInteractor

    @Binds
    @SysUISingleton
    abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
+16 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -78,6 +80,20 @@ abstract class ShadeModule {
                sceneContainerOff.get()
            }
        }

        @Provides
        @SysUISingleton
        fun provideShadeBackActionInteractor(
            sceneContainerFlags: SceneContainerFlags,
            sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
            sceneContainerOff: Provider<NotificationPanelViewController>
        ): ShadeBackActionInteractor {
            return if (sceneContainerFlags.isEnabled()) {
                sceneContainerOn.get()
            } else {
                sceneContainerOff.get()
            }
        }
    }

    @Binds
+2 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.systemui.shade

import android.view.ViewPropertyAnimator
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -25,7 +26,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager
 * this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
 * pulled up into ShadeViewController.
 */
interface ShadeSurface : ShadeViewController {
interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor {
    /** Initialize objects instead of injecting to avoid circular dependencies. */
    fun initDependencies(
        centralSurfaces: CentralSurfaces,
Loading