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

Commit fccc553a authored by Nick Chameyev's avatar Nick Chameyev Committed by Automerger Merge Worker
Browse files

Merge "Update statusbar insets when switching displays" into sc-v2-dev am: 2c5de364

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15640805

Change-Id: Idb4673e1e16d6cfe36aad1b36df3764b93ae1f8e
parents a6cc5dcb 2c5de364
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 −41
Original line number Diff line number Diff line
@@ -19,11 +19,10 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.util.Log
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
@@ -38,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
@@ -61,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 {
@@ -91,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()
    }

@@ -111,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)
@@ -129,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(
@@ -176,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 {
@@ -194,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
    }
@@ -243,7 +247,7 @@ fun calculateInsetsForRotationWithRotatedResources(
    @Rotation currentRotation: Int,
    @Rotation targetRotation: Int,
    displayCutout: DisplayCutout?,
    windowMetrics: WindowMetrics,
    maxBounds: Rect,
    statusBarHeight: Int,
    minLeft: Int,
    minRight: Int
@@ -254,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,