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

Commit c9047248 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Update statusbar insets when switching displays

Caches statusbar insets for each display and notifies
about changes if the insets has changed

Fixes: 195087279
Test: checked statusbar size on foldable devices in different rotations on both screens
atest: com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderTest
Change-Id: I6c3ee7eeafd1940902d9b17cbb749ba525ebf97f
parent 9fbbc907
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Rect
import android.os.LocaleList
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -29,6 +30,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
    private val lastConfig = Configuration()
    private var density: Int = 0
    private var smallestScreenWidth: Int = 0
    private var maxBounds: Rect? = null
    private var fontScale: Float = 0.toFloat()
    private val inCarMode: Boolean
    private var uiMode: Int = 0
@@ -85,6 +87,14 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
            }
        }

        val maxBounds = newConfig.windowConfiguration.maxBounds
        if (maxBounds != this.maxBounds) {
            this.maxBounds = maxBounds
            listeners.filterForEach({ this.listeners.contains(it) }) {
                it.onMaxBoundsChanged()
            }
        }

        val localeList = newConfig.locales
        if (localeList != this.localeList) {
            this.localeList = localeList
+44 −40
Original line number Diff line number Diff line
@@ -19,10 +19,10 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.util.LruCache
import android.util.Pair
import android.view.DisplayCutout
import android.view.View.LAYOUT_DIRECTION_RTL
import android.view.WindowManager
import android.view.WindowMetrics
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
@@ -37,6 +37,7 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
import java.io.FileDescriptor
import java.io.PrintWriter
import java.lang.Math.max
@@ -60,13 +61,14 @@ import javax.inject.Inject
class StatusBarContentInsetsProvider @Inject constructor(
    val context: Context,
    val configurationController: ConfigurationController,
    val windowManager: WindowManager,
    val dumpManager: DumpManager
) : CallbackController<StatusBarContentInsetsChangedListener>,
        ConfigurationController.ConfigurationListener,
        Dumpable {
    // Indexed by @Rotation
    private val insetsByCorner = arrayOfNulls<Rect>(4)

    // Limit cache size as potentially we may connect large number of displays
    // (e.g. network displays)
    private val insetsCache = LruCache<CacheKey, Rect>(MAX_CACHE_SIZE)
    private val listeners = mutableSetOf<StatusBarContentInsetsChangedListener>()

    init {
@@ -90,12 +92,12 @@ class StatusBarContentInsetsProvider @Inject constructor(
        clearCachedInsets()
    }

    private fun clearCachedInsets() {
        insetsByCorner[0] = null
        insetsByCorner[1] = null
        insetsByCorner[2] = null
        insetsByCorner[3] = null
    override fun onMaxBoundsChanged() {
        notifyInsetsChanged()
    }

    private fun clearCachedInsets() {
        insetsCache.evictAll()
        notifyInsetsChanged()
    }

@@ -110,10 +112,10 @@ class StatusBarContentInsetsProvider @Inject constructor(
     * dot in the coordinates relative to the given rotation.
     */
    fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int): Rect {
        var insets = insetsByCorner[rotation]
        val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
        var insets = insetsCache[getCacheKey(rotation = rotation)]
        val rotatedResources = getResourcesForRotation(rotation, context)
        if (insets == null) {
            insets = getAndSetInsetsForRotation(rotation, rotatedResources)
            insets = getStatusBarContentInsetsForRotation(rotation, rotatedResources)
        }

        val dotWidth = rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter)
@@ -128,24 +130,16 @@ class StatusBarContentInsetsProvider @Inject constructor(
     * Calculates the necessary left and right locations for the status bar contents invariant of
     * the current device rotation, in the target rotation's coordinates
     */
    fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Rect {
        var insets = insetsByCorner[rotation]
        if (insets == null) {
            val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
            insets = getAndSetInsetsForRotation(rotation, rotatedResources)
        }

        return insets
    }

    private fun getAndSetInsetsForRotation(
        @Rotation rot: Int,
        rotatedResources: Resources
    @JvmOverloads
    fun getStatusBarContentInsetsForRotation(
        @Rotation rotation: Int,
        rotatedResources: Resources = getResourcesForRotation(rotation, context)
    ): Rect {
        val insets = getCalculatedInsetsForRotation(rot, rotatedResources)
        insetsByCorner[rot] = insets

        return insets
        val key = getCacheKey(rotation = rotation)
        return insetsCache[key] ?: getCalculatedInsetsForRotation(rotation, rotatedResources)
            .also {
                insetsCache.put(key, it)
            }
    }

    private fun getCalculatedInsetsForRotation(
@@ -175,17 +169,29 @@ class StatusBarContentInsetsProvider @Inject constructor(
                currentRotation,
                targetRotation,
                dc,
                windowManager.maximumWindowMetrics,
                context.resources.configuration.windowConfiguration.maxBounds,
                rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height),
                minLeft,
                minRight)
    }

    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
        insetsByCorner.forEachIndexed { index, rect ->
            pw.println("${RotationUtils.toString(index)} -> $rect")
        insetsCache.snapshot().forEach { (key, rect) ->
            pw.println("$key -> $rect")
        }
        pw.println(insetsCache)
    }

    private fun getCacheKey(@Rotation rotation: Int): CacheKey =
        CacheKey(
            uniqueDisplayId = context.display.uniqueId,
            rotation = rotation
        )

    private data class CacheKey(
        val uniqueDisplayId: String,
        @Rotation val rotation: Int
    )
}

interface StatusBarContentInsetsChangedListener {
@@ -193,10 +199,9 @@ interface StatusBarContentInsetsChangedListener {
}

private const val TAG = "StatusBarInsetsProvider"
private const val MAX_CACHE_SIZE = 16

private fun getRotationZeroDisplayBounds(wm: WindowMetrics, @Rotation exactRotation: Int): Rect {
    val bounds = wm.bounds

private fun getRotationZeroDisplayBounds(bounds: Rect, @Rotation exactRotation: Int): Rect {
    if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
        return bounds
    }
@@ -242,7 +247,7 @@ fun calculateInsetsForRotationWithRotatedResources(
    @Rotation currentRotation: Int,
    @Rotation targetRotation: Int,
    displayCutout: DisplayCutout?,
    windowMetrics: WindowMetrics,
    maxBounds: Rect,
    statusBarHeight: Int,
    minLeft: Int,
    minRight: Int
@@ -253,16 +258,15 @@ fun calculateInsetsForRotationWithRotatedResources(
    val right = if (isRtl) paddingStart else paddingEnd
     */

    val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation)
    val currentBounds = windowMetrics.bounds
    val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)

    val sbLeftRight = getStatusBarLeftRight(
            displayCutout,
            statusBarHeight,
            rotZeroBounds.right,
            rotZeroBounds.bottom,
            currentBounds.width(),
            currentBounds.height(),
            maxBounds.width(),
            maxBounds.height(),
            minLeft,
            minRight,
            targetRotation,
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ public interface ConfigurationController extends CallbackController<Configuratio
        default void onConfigChanged(Configuration newConfig) {}
        default void onDensityOrFontScaleChanged() {}
        default void onSmallestScreenWidthChanged() {}
        default void onMaxBoundsChanged() {}
        default void onOverlayChanged() {}
        default void onUiModeChanged() {}
        default void onThemeChanged() {}
+100 −24
Original line number Diff line number Diff line
@@ -16,32 +16,54 @@

package com.android.systemui.statusbar.phone

import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
import android.test.suitebuilder.annotation.SmallTest
import android.view.Display
import android.view.DisplayCutout
import android.view.WindowMetrics
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations

@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {

    @Mock private lateinit var dc: DisplayCutout
    @Mock private lateinit var windowMetrics: WindowMetrics
    @Mock private lateinit var contextMock: Context
    @Mock private lateinit var display: Display
    private lateinit var configurationController: ConfigurationController

    private val configuration = Configuration()

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        `when`(contextMock.display).thenReturn(display)

        context.ensureTestableResources()
        `when`(contextMock.resources).thenReturn(context.resources)
        `when`(contextMock.resources.configuration).thenReturn(configuration)
        `when`(contextMock.createConfigurationContext(any())).thenAnswer {
            context.createConfigurationContext(it.arguments[0] as Configuration)
        }

        configurationController = ConfigurationControllerImpl(contextMock)
    }

    @Test
@@ -55,15 +77,13 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
        val chipWidth = 30
        val dotWidth = 10

        `when`(windowMetrics.bounds).thenReturn(screenBounds)

        var isRtl = false
        var targetRotation = ROTATION_NONE
        var bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                null,
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -92,7 +112,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -127,7 +147,6 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
        val sbHeightLandscape = 60
        val currentRotation = ROTATION_NONE

        `when`(windowMetrics.bounds).thenReturn(screenBounds)
        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))

        // THEN rotations which share a short side should use the greater value between rounded
@@ -142,7 +161,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -159,7 +178,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -178,7 +197,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -196,7 +215,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -219,7 +238,6 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
        val sbHeightLandscape = 60
        val currentRotation = ROTATION_NONE

        `when`(windowMetrics.bounds).thenReturn(screenBounds)
        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))

        // THEN only the landscape/seascape rotations should avoid the cutout area because of the
@@ -234,7 +252,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -251,7 +269,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -268,7 +286,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -285,7 +303,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -303,8 +321,6 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
        val sbHeightPortrait = 100
        val sbHeightLandscape = 60

        `when`(windowMetrics.bounds).thenReturn(screenBounds)

        // THEN content insets should only use rounded corner padding
        var targetRotation = ROTATION_NONE
        var expectedBounds = Rect(minLeftPadding,
@@ -316,7 +332,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -332,7 +348,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -348,7 +364,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -364,7 +380,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                screenBounds,
                sbHeightLandscape,
                minLeftPadding,
                minRightPadding)
@@ -382,7 +398,6 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
        val sbHeightLandscape = 60
        val currentRotation = ROTATION_NONE

        `when`(windowMetrics.bounds).thenReturn(screenBounds)
        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))

        // THEN left should be set to the display cutout width, and right should use the minRight
@@ -396,7 +411,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                screenBounds,
                sbHeightPortrait,
                minLeftPadding,
                minRightPadding)
@@ -404,6 +419,67 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
    }

    @Test
    fun testDisplayChanged_returnsUpdatedInsets() {
        // GIVEN: get insets on the first display and switch to the second display
        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
            mock(DumpManager::class.java))

        givenDisplay(
            screenBounds = Rect(0, 0, 1080, 2160),
            displayUniqueId = "1"
        )
        val firstDisplayInsets = provider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
        givenDisplay(
            screenBounds = Rect(0, 0, 800, 600),
            displayUniqueId = "2"
        )
        configurationController.onConfigurationChanged(configuration)

        // WHEN: get insets on the second display
        val secondDisplayInsets = provider.getStatusBarContentInsetsForRotation(ROTATION_NONE)

        // THEN: insets are updated
        assertThat(firstDisplayInsets).isNotEqualTo(secondDisplayInsets)
    }

    @Test
    fun testDisplayChangedAndReturnedBack_returnsTheSameInsets() {
        // GIVEN: get insets on the first display, switch to the second display,
        // get insets and switch back
        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
            mock(DumpManager::class.java))
        givenDisplay(
            screenBounds = Rect(0, 0, 1080, 2160),
            displayUniqueId = "1"
        )
        val firstDisplayInsetsFirstCall = provider
            .getStatusBarContentInsetsForRotation(ROTATION_NONE)
        givenDisplay(
            screenBounds = Rect(0, 0, 800, 600),
            displayUniqueId = "2"
        )
        configurationController.onConfigurationChanged(configuration)
        provider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
        givenDisplay(
            screenBounds = Rect(0, 0, 1080, 2160),
            displayUniqueId = "1"
        )
        configurationController.onConfigurationChanged(configuration)

        // WHEN: get insets on the first display again
        val firstDisplayInsetsSecondCall = provider
            .getStatusBarContentInsetsForRotation(ROTATION_NONE)

        // THEN: insets for the first and second calls for the first display are the same
        assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
    }

    private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
        `when`(display.uniqueId).thenReturn(displayUniqueId)
        configuration.windowConfiguration.maxBounds = screenBounds
    }

    private fun assertRects(
        expected: Rect,
        actual: Rect,