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

Commit 28270e75 authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Provide a configuration controller for each display and use it for...

Provide a configuration controller for each display and use it for DisplayStateRepository (isWideScreen)

DisplayStateRepositoryImpl was previously using display values, which caused flickers as the "OnDisplayChanged" event was being received a few frames after than expected (as it's received and propagated in the background). This resulted in a jump of shade window background when the window moved between displays.

On the other hand, the new config change is guaranteed to be received at least before the frame that is expected to have the correct dimensions.

Also, the class was getting the display info without checking which display changed. Now it's filtering by display id associated with the context.

Now we're using ComponentCallbacks to reliably get the configuration change related to each display.

Bug: 362719719
Bug: 417956803
Test: DisplayStateRepositoryImplTest, ConfigurationControllerDelegateTest
Flag: com.android.systemui.shade_window_goes_around
Change-Id: I2d6f651de3b1303088b95c56c19810fbdc503333
parent caf1a88a
Loading
Loading
Loading
Loading
+99 −42
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package com.android.systemui.display.data.repository

import android.content.res.Configuration
import android.content.testableContext
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.util.DisplayMetrics
import android.util.Size
import android.view.Display
@@ -25,65 +28,54 @@ import android.view.DisplayInfo
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
import com.android.systemui.display.shared.model.DisplayRotation
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.spy

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DisplayStateRepositoryImplTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val display = mock<Display>()
    private val testScope = TestScope(StandardTestDispatcher())
    private val fakeDeviceStateRepository = FakeDeviceStateRepository()
    private val fakeDisplayRepository = FakeDisplayRepository()
    private val configuration = Configuration()
    private val context = kosmos.testableContext

    private lateinit var underTest: DisplayStateRepository
    private val underTest by lazy { kosmos.realDisplayStateRepository }

    @Before
    fun setUp() {
        mContext.orCreateTestableResources.addOverride(
            com.android.internal.R.bool.config_reverseDefaultRotation,
            false,
        )
        context.display = display
        context.orCreateTestableResources.apply {
            addOverride(com.android.internal.R.bool.config_reverseDefaultRotation, false)
            overrideConfiguration(configuration)
        }

        // Set densityDpi such that pixels and DP are the same; Makes it easier to read and write
        // tests.
        configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT

        mContext = spy(mContext)
        whenever(mContext.display).thenReturn(display)
        whenever(mContext.resources.configuration).thenReturn(configuration)

        underTest =
            DisplayStateRepositoryImpl(
                testScope.backgroundScope,
                mContext,
                fakeDeviceStateRepository,
                fakeDisplayRepository,
            )
    }

    @Test
    fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() =
        testScope.runTest {
        kosmos.runTest {
            val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode)
            runCurrent()

            fakeDeviceStateRepository.emit(DeviceState.FOLDED)
            assertThat(isInRearDisplayMode).isFalse()
@@ -94,9 +86,8 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {

    @Test
    fun updatesCurrentRotation_whenDisplayStateChanges() =
        testScope.runTest {
        kosmos.runTest {
            val currentRotation by collectLastValue(underTest.currentRotation)
            runCurrent()

            whenever(display.getDisplayInfo(any())).then {
                val info = it.getArgument<DisplayInfo>(0)
@@ -104,7 +95,7 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                return@then true
            }

            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)

            whenever(display.getDisplayInfo(any())).then {
@@ -113,15 +104,14 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                return@then true
            }

            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
        }

    @Test
    fun updatesCurrentSize_whenDisplayStateChanges() =
        testScope.runTest {
        kosmos.runTest {
            val currentSize by collectLastValue(underTest.currentDisplaySize)
            runCurrent()

            whenever(display.getDisplayInfo(any())).then {
                val info = it.getArgument<DisplayInfo>(0)
@@ -130,7 +120,7 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                info.logicalHeight = 200
                return@then true
            }
            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(currentSize).isEqualTo(Size(100, 200))

            whenever(display.getDisplayInfo(any())).then {
@@ -140,15 +130,15 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                info.logicalHeight = 200
                return@then true
            }
            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(currentSize).isEqualTo(Size(200, 100))
        }

    @Test
    @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    fun updatesIsLargeScreen_whenDisplayStateChanges() =
        testScope.runTest {
        kosmos.runTest {
            val isLargeScreen by collectLastValue(underTest.isLargeScreen)
            runCurrent()

            whenever(display.getDisplayInfo(any())).then {
                val info = it.getArgument<DisplayInfo>(0)
@@ -157,7 +147,7 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                info.logicalHeight = 700
                return@then true
            }
            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(isLargeScreen).isFalse()

            whenever(display.getDisplayInfo(any())).then {
@@ -167,15 +157,15 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                info.logicalHeight = 700
                return@then true
            }
            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(isLargeScreen).isTrue()
        }

    @Test
    @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    fun updatesIsWideScreen_whenDisplayStateChanges() =
        testScope.runTest {
        kosmos.runTest {
            val isWideScreen by collectLastValue(underTest.isWideScreen)
            runCurrent()

            whenever(display.getDisplayInfo(any())).then {
                val info = it.getArgument<DisplayInfo>(0)
@@ -184,7 +174,7 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                info.logicalHeight = 700
                return@then true
            }
            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(isWideScreen).isFalse()

            whenever(display.getDisplayInfo(any())).then {
@@ -194,7 +184,74 @@ class DisplayStateRepositoryImplTest : SysuiTestCase() {
                info.logicalHeight = 200
                return@then true
            }
            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(isWideScreen).isTrue()
        }

    @Test
    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    fun currentRotation_anotherDisplaychanged_noChange() =
        kosmos.runTest {
            val currentRotation by collectLastValue(underTest.currentRotation)

            whenever(display.getDisplayInfo(any())).then {
                val info = it.getArgument<DisplayInfo>(0)
                info.rotation = Surface.ROTATION_90
                return@then true
            }

            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
            assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)

            whenever(display.getDisplayInfo(any())).then {
                val info = it.getArgument<DisplayInfo>(0)
                info.rotation = Surface.ROTATION_180
                return@then true
            }

            displayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY + 1)
            // Still the previous one!
            assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)
        }

    @Test
    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    fun isWideScreen_fromConfiguration() =
        kosmos.runTest {
            val isWideScreen by collectLastValue(underTest.isWideScreen)

            val smallScreenConfig = Configuration().apply { screenWidthDp = SMALL_SCREEN_WIDTH_DP }
            kosmos.fakeConfigurationRepository.onConfigurationChange(smallScreenConfig)

            assertThat(isWideScreen).isFalse()

            val wideScreenConfig = Configuration().apply { screenWidthDp = LARGE_SCREEN_WIDTH_DP }
            kosmos.fakeConfigurationRepository.onConfigurationChange(wideScreenConfig)

            assertThat(isWideScreen).isTrue()
        }

    @Test
    @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
    fun isLargeScreen_fromConfiguration() =
        kosmos.runTest {
            val isLargeScreen by collectLastValue(underTest.isLargeScreen)

            val smallScreenConfig =
                Configuration().apply { smallestScreenWidthDp = SMALL_SCREEN_WIDTH_DP }
            kosmos.fakeConfigurationRepository.onConfigurationChange(smallScreenConfig)

            assertThat(isLargeScreen).isFalse()

            val wideScreenConfig =
                Configuration().apply { smallestScreenWidthDp = LARGE_SCREEN_WIDTH_DP }
            kosmos.fakeConfigurationRepository.onConfigurationChange(wideScreenConfig)

            assertThat(isLargeScreen).isTrue()
        }

    private companion object {
        const val SMALL_SCREEN_WIDTH_DP = 1
        const val LARGE_SCREEN_WIDTH_DP = 1000000
    }
}
+87 −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.common.ui

import android.content.ComponentCallbacks
import android.content.res.Configuration
import android.window.WindowContext
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationControllerDelegate
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

/**
 * Simple proxy class that acts as a [ConfigurationController] using [ComponentCallbacks] for a
 * [WindowContext].
 *
 * This is usually needed if we want to receive configuration changes associated to a specific
 * window.
 */
interface WindowContextConfigurationController : ConfigurationController {
    /** Starts listening and propagating config changes. */
    fun start()

    /** Stops listening and propagating config changes. */
    fun stop()
}

class WindowContextConfigurationControllerImpl
@AssistedInject
constructor(
    @Assisted private val windowContext: WindowContext,
    @Assisted private val delegate: ConfigurationControllerDelegate,
    configurationControllerFactory: ConfigurationControllerImpl.Factory,
) : WindowContextConfigurationController, ConfigurationController by delegate {

    init {
        delegate.setDelegate(configurationControllerFactory.create(windowContext))
    }

    private val configurationForwarder: ConfigurationForwarder = delegate

    private val componentCallback =
        object : ComponentCallbacks {
            override fun onConfigurationChanged(newConfig: Configuration) {
                configurationForwarder.onConfigurationChanged(newConfig)
            }

            @Deprecated("See ComponentCallbacks") override fun onLowMemory() {}
        }

    override fun start() {
        windowContext.registerComponentCallbacks(componentCallback)
    }

    override fun stop() {
        windowContext.unregisterComponentCallbacks(componentCallback)
    }

    @AssistedFactory
    interface Factory {
        /**
         * Creates a [com.android.systemui.common.ui.WindowContextConfigurationControllerImpl] that
         * gets config changes using [WindowContext.registerComponentCallbacks].
         */
        fun create(
            windowContext: WindowContext,
            delegate: ConfigurationControllerDelegate = ConfigurationControllerDelegate(),
        ): WindowContextConfigurationControllerImpl
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope

/** Module providing common dependencies for per-display singletons. */
@Module(includes = [StatusBarPerDisplayModule::class])
@Module(includes = [StatusBarPerDisplayModule::class, PerDisplayConfigurationModule::class])
interface PerDisplayCommonModule {

    @Multibinds
+164 −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.display.dagger

import android.view.Display
import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
import android.window.WindowContext
import com.android.systemui.common.ui.WindowContextConfigurationController
import com.android.systemui.common.ui.WindowContextConfigurationControllerImpl
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.DisplayAware
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.DisplayId
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.PerDisplaySingleton
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet

/**
 * Additional module to provide a [ConfigurationController] related to a specific display.
 *
 * While this is not tied to any UI object, it but uses a [TYPE_STATUS_BAR] window context type, and
 * gets the configuration using [android.content.ComponentCallbacks].
 *
 * The [WindowContext] bound here is from [DisplayWindowPropertiesRepository], and it's essentially
 * the same as the one used by the status bar (so, no new context creation happen as part of this).
 *
 * Note: for the default display it just binds the global configuration objects with the
 * `@DisplayAware` annotation, to keep the same behaviour as before and avoiding creating new
 * instances.
 */
@Module
class PerDisplayConfigurationModule {

    @Provides
    @PerDisplaySingleton
    @DisplayAware
    fun provideStatusBarWindowContext(
        @DisplayId displayId: Int,
        displayPropertiesRepository: DisplayWindowPropertiesRepository,
    ): WindowContext {
        if (displayId == Display.DEFAULT_DISPLAY) {
            error(
                """If you're receiving this error it either means something in
                    | PerDisplayConfigurationModule is wrong, or that you're injecting a
                    | @DisplayAware window context in a class used by the default display. This is
                    | not possible as the statusbar window context is used for this binding, but for
                    | the default display we're not creating a new window context."""
                    .trimMargin()
            )
        }
        return displayPropertiesRepository.get(displayId, TYPE_STATUS_BAR)?.context
            as? WindowContext
            ?: error(
                """Unable to cast status bar context to WindowContext. I
                    |f the statusbar is not using WindowContext, this will not work and you should
                    | remove PerDisplayConfigurationModule from your dagger graph and any dependency
                    | on its classes."""
                    .trimMargin()
            )
    }

    @Provides
    @PerDisplaySingleton
    @DisplayAware
    fun provideWindowContextDisplayConfigurationController(
        @DisplayAware statusbarWindowContext: WindowContext,
        windowContextConfigurationController: WindowContextConfigurationControllerImpl.Factory,
    ): WindowContextConfigurationController =
        windowContextConfigurationController.create(statusbarWindowContext)

    @Provides
    @PerDisplaySingleton
    @DisplayAware
    fun provideDisplayConfigurationController(
        @DisplayAware displayConfigurationController: Lazy<WindowContextConfigurationController>,
        globalConfigController: ConfigurationController,
        @DisplayAware displayId: Int,
    ): ConfigurationController {
        // We should remove this condition and just create also the instance for the default display
        // in the same way. This is not possible right now as we're not using a WindowContext for
        // the default display statusbar.
        return if (displayId == Display.DEFAULT_DISPLAY) {
            globalConfigController
        } else {
            displayConfigurationController.get()
        }
    }

    /**
     * The lifecycle listener is only needed if we're on an external display, as we can assume the
     * default display will always be there.
     */
    @Provides
    @PerDisplaySingleton
    @DisplayAware
    @IntoSet
    fun provideDisplayWindowContextConfigurationControllerLifecycleObserver(
        @DisplayAware displayConfigurationController: Lazy<WindowContextConfigurationController>,
        @DisplayAware displayId: Int,
    ): SystemUIDisplaySubcomponent.LifecycleListener =
        object : SystemUIDisplaySubcomponent.LifecycleListener {
            override fun start() {
                if (displayId != Display.DEFAULT_DISPLAY) {
                    displayConfigurationController.get().start()
                }
            }

            override fun stop() {
                if (displayId != Display.DEFAULT_DISPLAY) {
                    displayConfigurationController.get().stop()
                }
            }
        }

    @Provides
    @PerDisplaySingleton
    @DisplayAware
    fun provideConfigurationRepository(
        @DisplayAware configurationController: Lazy<ConfigurationController>,
        @DisplayAware context: Lazy<WindowContext>,
        configurationRepositoryFactory: ConfigurationRepositoryImpl.Factory,
        @DisplayAware displayId: Int,
        globalConfigurationRepository: ConfigurationRepository,
    ): ConfigurationRepository =
        if (displayId == Display.DEFAULT_DISPLAY) {
            globalConfigurationRepository
        } else {
            configurationRepositoryFactory.create(context.get(), configurationController.get())
        }

    @Provides
    @PerDisplaySingleton
    @DisplayAware
    fun provideConfigurationInteractor(
        @DisplayAware configurationRepository: Lazy<ConfigurationRepository>,
        @DisplayAware displayId: Int,
        globalConfigurationInteractor: ConfigurationInteractor,
    ): ConfigurationInteractor =
        if (displayId == Display.DEFAULT_DISPLAY) {
            globalConfigurationInteractor
        } else {
            ConfigurationInteractorImpl(configurationRepository.get())
        }
}
+26 −10
Original line number Diff line number Diff line
@@ -20,16 +20,19 @@ import android.content.Context
import android.util.DisplayMetrics
import android.util.Size
import android.view.DisplayInfo
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.DisplayAware
import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.PerDisplaySingleton
import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY
import com.android.systemui.display.shared.model.DisplayRotation
import com.android.systemui.display.shared.model.toDisplayRotation
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import javax.inject.Inject
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@@ -74,6 +77,7 @@ class DisplayStateRepositoryImpl
constructor(
    @DisplayAware bgDisplayScope: CoroutineScope,
    @DisplayAware val context: Context,
    @DisplayAware val configurationRepository: ConfigurationRepository,
    deviceStateRepository: DeviceStateRepository,
    displayRepository: DisplayRepository,
) : DisplayStateRepository {
@@ -86,7 +90,11 @@ constructor(
            .stateIn(bgDisplayScope, started = SharingStarted.Eagerly, initialValue = false)

    private val currentDisplayInfo: StateFlow<DisplayInfo> =
        if (ShadeWindowGoesAround.isEnabled) {
                displayRepository.displayChangeEvent.filter { it == context.displayId }
            } else {
                displayRepository.displayChangeEvent
            }
            .map { getDisplayInfo() }
            .stateIn(
                bgDisplayScope,
@@ -116,20 +124,28 @@ constructor(
                    ),
            )

    // TODO: b/417956803 - This should use the configuration instead
    override val isLargeScreen: StateFlow<Boolean> =
        currentDisplayInfo
            .map {
        if (ShadeWindowGoesAround.isEnabled) {
                configurationRepository.configurationValues.map {
                    it.smallestScreenWidthDp >= LARGE_SCREEN_MIN_DPS
                }
            } else {
                currentDisplayInfo.map {
                    // copied from systemui/shared/...Utilities.java
                    val smallestWidth = min(it.logicalWidth, it.logicalHeight).toDpi()
                    smallestWidth >= LARGE_SCREEN_MIN_DPS
                }
            }
            .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 }
        if (ShadeWindowGoesAround.isEnabled) {
                configurationRepository.configurationValues.map {
                    it.screenWidthDp >= LARGE_SCREEN_MIN_DPS
                }
            } else {
                currentDisplayInfo.map { it.logicalWidth.toDpi() >= LARGE_SCREEN_MIN_DPS }
            }
            .stateIn(bgDisplayScope, started = SharingStarted.Eagerly, initialValue = false)

    private fun getDisplayInfo(): DisplayInfo {
Loading