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

Commit 3fa30d35 authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Introduce ShadeDisplayPolicy

A policy defines how to select the display that should show the shade.

For now, the policy is set only by the the adb command.

In this cl only the "static display id" policy is introduced. See child cls for more policies.

Bug: 362719719
Bug: 380444270
Test: ShadePrimaryDisplayCommandTest, ShadeDisplaysRepositoryTest
Flag: com.android.systemui.shade_window_goes_around
Change-Id: I01abbef21f3c386aad169768c9cd01533dc4ecd2
parent 789ae288
Loading
Loading
Loading
Loading
+89 −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.data.repository

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.shade.display.SpecificDisplayIdPolicy
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeDisplaysRepositoryTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val defaultPolicy = SpecificDisplayIdPolicy(0)

    private val shadeDisplaysRepository =
        ShadeDisplaysRepositoryImpl(defaultPolicy, testScope.backgroundScope)

    @Test
    fun policy_changing_propagatedFromTheLatestPolicy() =
        testScope.runTest {
            val displayIds by collectValues(shadeDisplaysRepository.displayId)
            val policy1 = MutablePolicy()
            val policy2 = MutablePolicy()

            assertThat(displayIds).containsExactly(0)

            shadeDisplaysRepository.policy.value = policy1

            policy1.sendDisplayId(1)

            assertThat(displayIds).containsExactly(0, 1)

            policy1.sendDisplayId(2)

            assertThat(displayIds).containsExactly(0, 1, 2)

            shadeDisplaysRepository.policy.value = policy2

            assertThat(displayIds).containsExactly(0, 1, 2, 0)

            policy1.sendDisplayId(4)

            // Changes to the first policy don't affect the output now
            assertThat(displayIds).containsExactly(0, 1, 2, 0)

            policy2.sendDisplayId(5)

            assertThat(displayIds).containsExactly(0, 1, 2, 0, 5)
        }

    private class MutablePolicy : ShadeDisplayPolicy {
        fun sendDisplayId(id: Int) {
            _displayId.value = id
        }

        private val _displayId = MutableStateFlow(0)
        override val name: String
            get() = "mutable_policy"

        override val displayId: StateFlow<Int>
            get() = _displayId
    }
}
+56 −3
Original line number Diff line number Diff line
@@ -23,12 +23,17 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.ShadePrimaryDisplayCommand
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -37,15 +42,26 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope
    private val commandRegistry = kosmos.commandRegistry
    private val displayRepository = kosmos.displayRepository
    private val shadeDisplaysRepository = ShadeDisplaysRepositoryImpl()
    private val defaultPolicy = kosmos.defaultShadeDisplayPolicy
    private val policy1 = makePolicy("policy_1")
    private val shadeDisplaysRepository = kosmos.shadeDisplaysRepository
    private val pw = PrintWriter(StringWriter())

    private val policies =
        setOf(defaultPolicy, policy1, makePolicy("policy_2"), makePolicy("policy_3"))

    private val underTest =
        ShadePrimaryDisplayCommand(commandRegistry, displayRepository, shadeDisplaysRepository)
        ShadePrimaryDisplayCommand(
            commandRegistry,
            displayRepository,
            shadeDisplaysRepository,
            policies,
            defaultPolicy,
        )

    @Before
    fun setUp() {
@@ -96,4 +112,41 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {

            assertThat(displayId).isEqualTo(newDisplayId)
        }

    @Test
    fun policies_listsAllPolicies() =
        testScope.runTest {
            val stringWriter = StringWriter()
            commandRegistry.onShellCommand(
                PrintWriter(stringWriter),
                arrayOf("shade_display_override", "policies"),
            )
            val result = stringWriter.toString()

            assertThat(result).containsAllIn(policies.map { it.name })
        }

    @Test
    fun policies_setsSpecificPolicy() =
        testScope.runTest {
            val policy by collectLastValue(shadeDisplaysRepository.policy)

            commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", policy1.name))

            assertThat(policy!!.name).isEqualTo(policy1.name)
        }

    private fun makePolicy(policyName: String): ShadeDisplayPolicy {
        return object : ShadeDisplayPolicy {
            override val name: String
                get() = policyName

            override val displayId: StateFlow<Int>
                get() = MutableStateFlow(0)
        }
    }
}

private fun StringSubject.containsAllIn(strings: List<String>) {
    strings.forEach { contains(it) }
}
+12 −1
Original line number Diff line number Diff line
@@ -32,8 +32,10 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorI
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.display.ShadeDisplayPolicyModule
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -58,7 +60,7 @@ import javax.inject.Provider
 * By using this dedicated module, we ensure the notification shade window always utilizes the
 * correct display context and resources, regardless of the display it's on.
 */
@Module(includes = [OptionalShadeDisplayAwareBindings::class])
@Module(includes = [OptionalShadeDisplayAwareBindings::class, ShadeDisplayPolicyModule::class])
object ShadeDisplayAwareModule {

    /** Creates a new context for the shade window. */
@@ -181,6 +183,15 @@ object ShadeDisplayAwareModule {
        return impl
    }

    @SysUISingleton
    @Provides
    fun provideMutableShadePositionRepository(
        impl: ShadeDisplaysRepositoryImpl
    ): MutableShadeDisplaysRepository {
        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
        return impl
    }

    @Provides
    @IntoMap
    @ClassKey(ShadePrimaryDisplayCommand::class)
+37 −14
Original line number Diff line number Diff line
@@ -20,11 +20,14 @@ import android.view.Display
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.shade.display.SpecificDisplayIdPolicy
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.text.toIntOrNull

@SysUISingleton
class ShadePrimaryDisplayCommand
@@ -32,7 +35,9 @@ class ShadePrimaryDisplayCommand
constructor(
    private val commandRegistry: CommandRegistry,
    private val displaysRepository: DisplayRepository,
    private val positionRepository: ShadeDisplaysRepository,
    private val positionRepository: MutableShadeDisplaysRepository,
    private val policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
    private val defaultPolicy: ShadeDisplayPolicy,
) : Command, CoreStartable {

    override fun start() {
@@ -40,8 +45,11 @@ constructor(
    }

    override fun help(pw: PrintWriter) {
        pw.println("shade_display_override <displayId> ")
        pw.println("Set the display which is holding the shade.")
        pw.println("shade_display_override (<displayId>|<policyName>) ")
        pw.println("Set the display which is holding the shade, or the policy that defines it.")
        pw.println()
        pw.println("shade_display_override policies")
        pw.println("Lists available policies")
        pw.println()
        pw.println("shade_display_override reset ")
        pw.println("Reset the display which is holding the shade.")
@@ -68,21 +76,27 @@ constructor(
                "reset" -> reset()
                "list",
                "status" -> printStatus()
                "policies" -> printPolicies()
                "any_external" -> anyExternal()
                else -> {
                    val cmdAsInteger = command?.toIntOrNull()
                    if (cmdAsInteger != null) {
                        changeDisplay(displayId = cmdAsInteger)
                    } else {
                        help(pw)
                null -> help(pw)
                else -> parsePolicy(command)
            }
        }

        private fun parsePolicy(policyIdentifier: String) {
            val displayId = policyIdentifier.toIntOrNull()
            when {
                displayId != null -> changeDisplay(displayId = displayId)
                policies.any { it.name == policyIdentifier } -> {
                    positionRepository.policy.value = policies.first { it.name == policyIdentifier }
                }
                else -> help(pw)
            }
        }

        private fun reset() {
            positionRepository.resetDisplayId()
            pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
            positionRepository.policy.value = defaultPolicy
            pw.println("Reset shade display policy to default policy: ${defaultPolicy.name}")
        }

        private fun printStatus() {
@@ -95,6 +109,15 @@ constructor(
            }
        }

        private fun printPolicies() {
            val currentPolicyName = positionRepository.policy.value.name
            pw.println("Available policies: ")
            policies.forEach {
                pw.print(" - ${it.name}")
                pw.println(if (currentPolicyName == it.name) " (Current policy)" else "")
            }
        }

        private fun anyExternal() {
            val anyExternalDisplay =
                displaysRepository.displays.value.firstOrNull {
@@ -116,7 +139,7 @@ constructor(
        }

        private fun setDisplay(id: Int) {
            positionRepository.setDisplayId(id)
            positionRepository.policy.value = SpecificDisplayIdPolicy(id)
            pw.println("New shade primary display id is $id")
        }
    }
+2 −2
Original line number Diff line number Diff line
@@ -23,14 +23,14 @@ import kotlinx.coroutines.flow.StateFlow
class FakeShadeDisplayRepository : ShadeDisplaysRepository {
    private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)

    override fun setDisplayId(displayId: Int) {
    fun setDisplayId(displayId: Int) {
        _displayId.value = displayId
    }

    override val displayId: StateFlow<Int>
        get() = _displayId

    override fun resetDisplayId() {
    fun resetDisplayId() {
        _displayId.value = Display.DEFAULT_DISPLAY
    }
}
Loading