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

Commit a5b59595 authored by Evan Laird's avatar Evan Laird
Browse files

Tests for StatusBarContentInsetsProvider

Reworked some functions for the status bar content insets calculations
and added tests for the content rectangle.

Test: atest StatusBarContentInsetsProviderTest
Bug: 187973222
Change-Id: I3eb473929944c19888f8c256b4c45ddd99948b88
parent dff37ee6
Loading
Loading
Loading
Loading
+43 −39
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ 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
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
@@ -118,17 +120,8 @@ class StatusBarContentInsetsProvider @Inject constructor(
        val chipWidth = rotatedResources.getDimensionPixelSize(
                R.dimen.ongoing_appops_chip_max_width)

        return if (context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) {
            Rect(insets.left - dotWidth,
                    insets.top,
                    insets.left + chipWidth,
                    insets.bottom)
        } else {
            Rect(insets.right - chipWidth,
                    insets.top,
                    insets.right + dotWidth,
                    insets.bottom)
        }
        val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
        return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl)
    }

    /**
@@ -139,8 +132,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
        var insets = insetsByCorner[rotation]
        if (insets == null) {
            val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
            insets = getCalculatedInsetsForRotation(rotation, rotatedResources)
            insetsByCorner[rotation] = insets
            insets = getAndSetInsetsForRotation(rotation, rotatedResources)
        }

        return insets
@@ -157,13 +149,19 @@ class StatusBarContentInsetsProvider @Inject constructor(
    }

    private fun getCalculatedInsetsForRotation(
        @Rotation rotation: Int,
        @Rotation targetRotation: Int,
        rotatedResources: Resources
    ): Rect {
        val dc = context.display.cutout
        val currentRotation = RotationUtils.getExactRotation(context)

        return calculateInsetsForRotationWithRotatedResources(
                rotation, rotatedResources, dc, windowManager, context)
                currentRotation,
                targetRotation,
                dc,
                windowManager.maximumWindowMetrics,
                rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height),
                rotatedResources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding))
    }

    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
@@ -179,8 +177,8 @@ interface StatusBarContentInsetsChangedListener {

private const val TAG = "StatusBarInsetsProvider"

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

    if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
        return bounds
@@ -190,9 +188,24 @@ private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotat
    return Rect(0, 0, bounds.bottom, bounds.right)
}

private fun getCurrentDisplayBounds(wm: WindowManager): Rect {
    val bounds = wm.maximumWindowMetrics.bounds
    return bounds
@VisibleForTesting
fun getPrivacyChipBoundingRectForInsets(
    contentRect: Rect,
    dotWidth: Int,
    chipWidth: Int,
    isRtl: Boolean
): Rect {
    return if (isRtl) {
        Rect(contentRect.left - dotWidth,
                contentRect.top,
                contentRect.left + chipWidth,
                contentRect.bottom)
    } else {
        Rect(contentRect.right - chipWidth,
                contentRect.top,
                contentRect.right + dotWidth,
                contentRect.bottom)
    }
}

/**
@@ -206,41 +219,32 @@ private fun getCurrentDisplayBounds(wm: WindowManager): Rect {
 * @see [RotationUtils#getResourcesForRotation]
 */
fun calculateInsetsForRotationWithRotatedResources(
    @Rotation currentRotation: Int,
    @Rotation targetRotation: Int,
    rotatedResources: Resources,
    displayCutout: DisplayCutout?,
    windowmanager: WindowManager,
    context: Context
    windowMetrics: WindowMetrics,
    statusBarHeight: Int,
    roundedCornerPadding: Int
): Rect {
    val rtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL

    val exactRotation = RotationUtils.getExactRotation(context)
    val height = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height)

    /*
    TODO: Check if this is ever used for devices with no rounded corners
    val paddingStart = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_start)
    val paddingEnd = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_end)
    val left = if (rtl) paddingEnd else paddingStart
    val right = if(rtl) paddingStart else paddingEnd
    val left = if (isRtl) paddingEnd else paddingStart
    val right = if (isRtl) paddingStart else paddingEnd
     */

    val roundedCornerPadding = rotatedResources.getDimensionPixelSize(
            R.dimen.rounded_corner_content_padding)

    val rotZeroBounds = getRotationZeroDisplayBounds(windowmanager, exactRotation)
    val currentBounds = getCurrentDisplayBounds(windowmanager)
    val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation)
    val currentBounds = windowMetrics.bounds

    val sbLeftRight = getStatusBarLeftRight(
            displayCutout,
            height,
            statusBarHeight,
            rotZeroBounds.right,
            rotZeroBounds.bottom,
            currentBounds.width(),
            currentBounds.height(),
            roundedCornerPadding,
            targetRotation,
            exactRotation)
            currentRotation)

    return sbLeftRight
}
+368 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.statusbar.phone

import android.graphics.Rect
import android.test.suitebuilder.annotation.SmallTest
import android.view.DisplayCutout
import android.view.WindowMetrics
import com.android.systemui.SysuiTestCase
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 junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations

@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {
    @Mock private lateinit var dc: DisplayCutout
    @Mock private lateinit var windowMetrics: WindowMetrics

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
    }

    @Test
    fun testGetBoundingRectForPrivacyChipForRotation_noCutout() {
        val screenBounds = Rect(0, 0, 1080, 2160)
        val roundedCornerPadding = 20
        val sbHeightPortrait = 100
        val sbHeightLandscape = 60
        val currentRotation = ROTATION_NONE
        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,
                sbHeightPortrait,
                roundedCornerPadding)

        var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
        /* 1080 - 20 (rounded corner) - 30 (chip),
        *  0 (sb top)
        *  1080 - 20 (rounded corner) + 10 ( dot),
        *  100 (sb height portrait)
        */
        var expected = Rect(1030, 0, 1070, 100)
        assertRects(expected, chipBounds, currentRotation, targetRotation)
        isRtl = true
        chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
        /* 0 + 20 (rounded corner) - 10 (dot),
        *  0 (sb top)
        *  0 + 20 (rounded corner) + 30 (chip),
        *  100 (sb height portrait)
        */
        expected = Rect(10, 0, 50, 100)
        assertRects(expected, chipBounds, currentRotation, targetRotation)

        isRtl = false
        targetRotation = ROTATION_LANDSCAPE
        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)

        chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
        /* 2160 - 20 (rounded corner) - 30 (chip),
        *  0 (sb top)
        *  2160 - 20 (rounded corner) + 10 ( dot),
        *  60 (sb height landscape)
        */
        expected = Rect(2110, 0, 2150, 60)
        assertRects(expected, chipBounds, currentRotation, targetRotation)
        isRtl = true
        chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
        /* 0 + 20 (rounded corner) - 10 (dot),
        *  0 (sb top)
        *  0 + 20 (rounded corner) + 30 (chip),
        *  60 (sb height landscape)
        */
        expected = Rect(10, 0, 50, 60)
        assertRects(expected, chipBounds, currentRotation, targetRotation)
    }

    @Test
    fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
        // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
        val screenBounds = Rect(0, 0, 1080, 2160)
        val dcBounds = Rect(0, 0, 100, 100)
        val roundedCornerPadding = 20
        val sbHeightPortrait = 100
        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
        // corner padding and the display cutout's size
        var targetRotation = ROTATION_NONE
        var expectedBounds = Rect(dcBounds.right,
                0,
                screenBounds.right - roundedCornerPadding,
                sbHeightPortrait)

        var bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightPortrait,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_LANDSCAPE
        expectedBounds = Rect(dcBounds.height(),
                0,
                screenBounds.height() - roundedCornerPadding,
                sbHeightLandscape)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        // THEN the side that does NOT share a short side with the display cutout ignores the
        // display cutout bounds
        targetRotation = ROTATION_UPSIDE_DOWN
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.width() - roundedCornerPadding,
                sbHeightPortrait)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightPortrait,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        // Phone in portrait, seascape (rot_270) bounds
        targetRotation = ROTATION_SEASCAPE
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.height() - dcBounds.height(),
                sbHeightLandscape)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
    }

    @Test
    fun testCalculateInsetsForRotationWithRotatedResources_nonCornerCutout() {
        // GIVEN phone in portrait mode, where width < height and the cutout is not in the corner
        // the assumption here is that if the cutout does NOT touch the corner then we have room to
        // layout the status bar in the given space.

        val screenBounds = Rect(0, 0, 1080, 2160)
        // cutout centered at the top
        val dcBounds = Rect(490, 0, 590, 100)
        val roundedCornerPadding = 20
        val sbHeightPortrait = 100
        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
        // potential letterboxing
        var targetRotation = ROTATION_NONE
        var expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.right - roundedCornerPadding,
                sbHeightPortrait)

        var bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightPortrait,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_LANDSCAPE
        expectedBounds = Rect(dcBounds.height(),
                0,
                screenBounds.height() - roundedCornerPadding,
                sbHeightLandscape)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_UPSIDE_DOWN
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.right - roundedCornerPadding,
                sbHeightPortrait)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightPortrait,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_SEASCAPE
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.height() - dcBounds.height(),
                sbHeightLandscape)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                dc,
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)

        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
    }

    @Test
    fun testCalculateInsetsForRotationWithRotatedResources_noCutout() {
        // GIVEN device in portrait mode, where width < height and no cutout
        val currentRotation = ROTATION_NONE
        val screenBounds = Rect(0, 0, 1080, 2160)
        val roundedCornerPadding = 20
        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(roundedCornerPadding,
                0,
                screenBounds.right - roundedCornerPadding,
                sbHeightPortrait)

        var bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                sbHeightPortrait,
                roundedCornerPadding)
        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_LANDSCAPE
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.height() - roundedCornerPadding,
                sbHeightLandscape)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)
        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_UPSIDE_DOWN
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.width() - roundedCornerPadding,
                sbHeightPortrait)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                sbHeightPortrait,
                roundedCornerPadding)
        assertRects(expectedBounds, bounds, currentRotation, targetRotation)

        targetRotation = ROTATION_LANDSCAPE
        expectedBounds = Rect(roundedCornerPadding,
                0,
                screenBounds.height() - roundedCornerPadding,
                sbHeightLandscape)

        bounds = calculateInsetsForRotationWithRotatedResources(
                currentRotation,
                targetRotation,
                null, /* no cutout */
                windowMetrics,
                sbHeightLandscape,
                roundedCornerPadding)
        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
    }

    private fun assertRects(
        expected: Rect,
        actual: Rect,
        @Rotation currentRotation: Int,
        @Rotation targetRotation: Int
    ) {
        assertTrue(
                "Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" +
                " targetRotation=${RotationUtils.toString(targetRotation)}" +
                " expected=$expected actual=$actual",
                expected.equals(actual))
    }
}
 No newline at end of file