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

Commit 0825ca60 authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Move shade to the default display when the keyguard is visible

This adds a flag in the dagger graph to decide whether to move the notification stack to the default display once the keyguard becomes visible.

It's kept as a boolean constant in the dagger graph as it's not clear what the final behaviour will be there, and we want to be able to change it easily if needed.

Bug: 362719719
Bug: 381258683
Test: StatusBarTouchShadeDisplayPolicyTest
Flag: com.android.systemui.shade_window_goes_around
Change-Id: I028208cf33b167c35fce9149a0f269c63cbfce77
parent 835ae299
Loading
Loading
Loading
Loading
+54 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.display
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
@@ -38,17 +39,31 @@ import org.junit.runner.RunWith
class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope
    private val keyguardRepository = kosmos.fakeKeyguardRepository
    private val displayRepository = kosmos.displayRepository
    val underTest = StatusBarTouchShadeDisplayPolicy(displayRepository, testScope.backgroundScope)

    private fun createUnderTest(
        shadeOnDefaultDisplayWhenLocked: Boolean = false
    ): StatusBarTouchShadeDisplayPolicy {
        return StatusBarTouchShadeDisplayPolicy(
            displayRepository,
            keyguardRepository,
            testScope.backgroundScope,
            shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
        )
    }

    @Test
    fun displayId_defaultToDefaultDisplay() {
        val underTest = createUnderTest()

        assertThat(underTest.displayId.value).isEqualTo(Display.DEFAULT_DISPLAY)
    }

    @Test
    fun onStatusBarTouched_called_updatesDisplayId() =
        testScope.runTest {
            val underTest = createUnderTest()
            val displayId by collectLastValue(underTest.displayId)

            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
@@ -60,6 +75,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
    @Test
    fun onStatusBarTouched_notExistentDisplay_displayIdNotUpdated() =
        testScope.runTest {
            val underTest = createUnderTest()
            val displayIds by collectValues(underTest.displayId)
            assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))

@@ -72,6 +88,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
    @Test
    fun onStatusBarTouched_afterDisplayRemoved_goesBackToDefaultDisplay() =
        testScope.runTest {
            val underTest = createUnderTest()
            val displayId by collectLastValue(underTest.displayId)

            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
@@ -83,4 +100,40 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {

            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
        }

    @Test
    fun onStatusBarTouched_afterKeyguardVisible_goesBackToDefaultDisplay() =
        testScope.runTest {
            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
            val displayId by collectLastValue(underTest.displayId)

            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
            underTest.onStatusBarTouched(2)

            assertThat(displayId).isEqualTo(2)

            keyguardRepository.setKeyguardShowing(true)

            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
        }

    @Test
    fun onStatusBarTouched_afterKeyguardHides_goesBackToPreviousDisplay() =
        testScope.runTest {
            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
            val displayId by collectLastValue(underTest.displayId)

            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
            underTest.onStatusBarTouched(2)

            assertThat(displayId).isEqualTo(2)

            keyguardRepository.setKeyguardShowing(true)

            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)

            keyguardRepository.setKeyguardShowing(false)

            assertThat(displayId).isEqualTo(2)
        }
}
+18 −1
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Provider
import javax.inject.Qualifier

/**
 * Module responsible for managing display-specific components and resources for the notification
@@ -203,7 +204,9 @@ object ShadeDisplayAwareModule {
    @Provides
    @IntoMap
    @ClassKey(ShadePrimaryDisplayCommand::class)
    fun provideShadePrimaryDisplayCommand(impl: Provider<ShadePrimaryDisplayCommand>): CoreStartable {
    fun provideShadePrimaryDisplayCommand(
        impl: Provider<ShadePrimaryDisplayCommand>
    ): CoreStartable {
        return if (ShadeWindowGoesAround.isEnabled) {
            impl.get()
        } else {
@@ -221,9 +224,23 @@ object ShadeDisplayAwareModule {
            CoreStartable.NOP
        }
    }

    @Provides
    @ShadeOnDefaultDisplayWhenLocked
    fun provideShadeOnDefaultDisplayWhenLocked(): Boolean = true
}

@Module
internal interface OptionalShadeDisplayAwareBindings {
    @BindsOptionalOf fun bindOptionalOfWindowRootView(): WindowRootView
}

/**
 * Annotates the boolean value that defines whether the shade window should go back to the default
 * display when the keyguard is visible.
 *
 * As of today (Dec 2024), This is a configuration parameter provided in the dagger graph as the
 * final policy around keyguard display is still under discussion, and will be evaluated based on
 * how well this solution behaves from the performance point of view.
 */
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ShadeOnDefaultDisplayWhenLocked
+28 −7
Original line number Diff line number Diff line
@@ -22,34 +22,55 @@ import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/**
 * Moves the shade on the last display that received a status bar touch.
 *
 * If the display is removed, falls back to the default one.
 * If the display is removed, falls back to the default one. When [shadeOnDefaultDisplayWhenLocked]
 * is true, the shade falls back to the default display when the keyguard is visible.
 */
@SysUISingleton
class StatusBarTouchShadeDisplayPolicy
@Inject
constructor(displayRepository: DisplayRepository, @Background val backgroundScope: CoroutineScope) :
    ShadeDisplayPolicy {
    override val name: String
        get() = "status_bar_latest_touch"
constructor(
    displayRepository: DisplayRepository,
    keyguardRepository: KeyguardRepository,
    @Background val backgroundScope: CoroutineScope,
    @ShadeOnDefaultDisplayWhenLocked val shadeOnDefaultDisplayWhenLocked: Boolean,
) : ShadeDisplayPolicy {
    override val name: String = "status_bar_latest_touch"

    private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
    private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds

    override val displayId: StateFlow<Int>
        get() = currentDisplayId
    override val displayId: StateFlow<Int> =
        if (shadeOnDefaultDisplayWhenLocked) {
            keyguardRepository.isKeyguardShowing
                .combine(currentDisplayId) { isKeyguardShowing, currentDisplayId ->
                    if (isKeyguardShowing) {
                        Display.DEFAULT_DISPLAY
                    } else {
                        currentDisplayId
                    }
                }
                .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), currentDisplayId.value)
        } else {
            currentDisplayId
        }

    private var removalListener: Job? = null