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

Commit 5d81c166 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "Dinamically pick the correct DisplayStateRepository based on shade...

Merge "Dinamically pick the correct DisplayStateRepository based on shade window position" into main
parents 9abc6419 0b4733e4
Loading
Loading
Loading
Loading
+105 −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.domain.interactor

import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.createFakeDisplaySubcomponent
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.display.data.repository.displaySubcomponentPerDisplayRepository
import com.android.systemui.display.domain.interactor.createDisplayStateInteractor
import com.android.systemui.display.domain.interactor.displayStateInteractor
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
class ShadeDisplayStateInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val secondaryDisplayStateRepository = FakeDisplayStateRepository()
    private val secondaryDisplayStateInteractor =
        kosmos.createDisplayStateInteractor(secondaryDisplayStateRepository)
    private val defaultDisplayStateRepository = FakeDisplayStateRepository()
    private val defaultDisplayStateInteractor =
        kosmos.createDisplayStateInteractor(defaultDisplayStateRepository)

    private val underTest: ShadeDisplayStateInteractor by lazy {
        kosmos.shadeDisplayStateInteractor
    }

    @Before
    fun setUp() {
        kosmos.displaySubcomponentPerDisplayRepository.apply {
            add(
                DEFAULT_DISPLAY,
                kosmos.createFakeDisplaySubcomponent(
                    displayStateRepository = defaultDisplayStateRepository,
                    displayStateInteractor = defaultDisplayStateInteractor,
                ),
            )
            add(
                SECONDARY_DISPLAY,
                kosmos.createFakeDisplaySubcomponent(
                    displayStateRepository = secondaryDisplayStateRepository,
                    displayStateInteractor = secondaryDisplayStateInteractor,
                ),
            )
        }
    }

    @Test
    fun isWideScreen_afterDisplayChange_returnsCorrectValue() =
        kosmos.runTest {
            defaultDisplayStateRepository.setIsWideScreen(false)
            secondaryDisplayStateRepository.setIsWideScreen(true)

            fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)

            val isWideScreen by collectLastValue(underTest.isWideScreen)

            assertThat(isWideScreen).isFalse()

            fakeShadeDisplaysRepository.setDisplayId(SECONDARY_DISPLAY)

            assertThat(isWideScreen).isTrue()

            fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)

            assertThat(isWideScreen).isFalse()
        }

    private companion object {
        const val DEFAULT_DISPLAY = Display.DEFAULT_DISPLAY
        const val SECONDARY_DISPLAY = DEFAULT_DISPLAY + 1
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -116,6 +116,7 @@ constructor(
                    ),
            )

    // TODO: b/417956803 - This should use the configuration instead
    override val isLargeScreen: StateFlow<Boolean> =
        currentDisplayInfo
            .map {
@@ -125,6 +126,7 @@ constructor(
            }
            .stateIn(bgDisplayScope, started = SharingStarted.Eagerly, initialValue = false)

    // TODO: b/417956803 - This should use the configuration instead
    override val isWideScreen: StateFlow<Boolean> =
        currentDisplayInfo
            .map { it.logicalWidth.toDpi() >= LARGE_SCREEN_MIN_DPS }
+86 −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.domain.interactor

import android.util.Log
import com.android.app.displaylib.PerDisplayRepository
import com.android.app.tracing.FlowTracing.traceEach
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn

/**
 * Provides the bits of [DisplayStateInteractor] that are relevant for the shade.
 *
 * This is needed as the shade can change display, and we want to use the correct
 * [DisplayStateInteractor] (as there is a different instance per display).
 */
@SysUISingleton
class ShadeDisplayStateInteractor
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    private val displayStateInteractor: DisplayStateInteractor,
    shadeDisplaysInteractor: Lazy<ShadeDisplaysInteractor>,
    private val displaySubcomponentRepository: PerDisplayRepository<SystemUIDisplaySubcomponent>,
) {
    val isWideScreen: StateFlow<Boolean> =
        if (ShadeWindowGoesAround.isEnabled) {
            shadeDisplaysInteractor
                .get()
                .displayId
                .flatMapLatest { displayId -> getIsWideScreenFlowForDisplay(displayId) }
                .distinctUntilChanged()
                .traceEach("$TAG#isWideScreen", logcat = true)
                .stateIn(
                    applicationScope,
                    SharingStarted.Eagerly,
                    initialValue = displayStateInteractor.isWideScreen.value,
                )
        } else {
            displayStateInteractor.isWideScreen
        }

    private fun getIsWideScreenFlowForDisplay(displayId: Int): StateFlow<Boolean> {
        val displaySpecificInteractor =
            displaySubcomponentRepository[displayId]?.displayStateInteractor
        return if (displaySpecificInteractor != null) {
            displaySpecificInteractor.isWideScreen
        } else {
            Log.e(
                TAG,
                "Couldn't get displayStateInteractor for display $displayId. " +
                    "\"isWideScreen\" might be wrong.",
            )
            displayStateInteractor.isWideScreen
        }
    }

    private companion object {
        const val TAG = "ShadeDisplayStateInteractor"
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -31,6 +30,7 @@ import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeDisplayStateInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
@@ -67,10 +67,10 @@ constructor(
    private val sceneInteractorProvider: Provider<SceneInteractor>,
    private val shadeExpansionStateManager: ShadeExpansionStateManager,
    private val pulseExpansionHandler: PulseExpansionHandler,
    private val displayStateInteractor: DisplayStateInteractor,
    private val nsslc: NotificationStackScrollLayoutController,
    private val scrimController: ScrimController,
    private val depthController: NotificationShadeDepthController,
    private val shadeDisplayStateInteractor: ShadeDisplayStateInteractor,
) : CoreStartable {

    override fun start() {
@@ -114,7 +114,7 @@ constructor(
        applicationScope.launch {
            // TODO(b/354926927): `isShadeLayoutWide` should be deprecated in SceneContainer.
            if (SceneContainerFlag.isEnabled) {
                    displayStateInteractor.isWideScreen
                    shadeDisplayStateInteractor.isWideScreen
                } else {
                    configurationRepository.onAnyConfigurationChange
                        // Force initial collection.
@@ -136,7 +136,7 @@ constructor(
        if (SceneContainerFlag.isEnabled) {
            val shadeModeInteractor = shadeModeInteractorProvider.get()
            applicationScope.launch {
                combine(shadeModeInteractor.shadeMode, displayStateInteractor.isWideScreen) {
                combine(shadeModeInteractor.shadeMode, shadeDisplayStateInteractor.isWideScreen) {
                        shadeMode,
                        isWideScreen ->
                        when (shadeMode) {
+3 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.view.mockIWindowManager
import com.android.app.displaylib.fakes.FakePerDisplayRepository
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.display.domain.interactor.displayStateInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
@@ -41,8 +42,8 @@ val Kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners by Fixture {

fun Kosmos.createFakeDisplaySubcomponent(
    coroutineScope: CoroutineScope = testScope.backgroundScope,
    displayStateRepository: DisplayStateRepository = mock<DisplayStateRepository>(),
    displayStateInteractor: DisplayStateInteractor = mock<DisplayStateInteractor>(),
    displayStateRepository: DisplayStateRepository = this.displayStateRepository,
    displayStateInteractor: DisplayStateInteractor = this.displayStateInteractor,
    statusbarIconRefreshInteractorFromConstructor: StatusBarIconRefreshInteractor =
        this.statusBarIconRefreshInteractor,
): SystemUIDisplaySubcomponent {
Loading