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

Commit b63eb4bb authored by Ahmed Mehfooz's avatar Ahmed Mehfooz Committed by Android (Google) Code Review
Browse files

Merge "Fix crash in SystemEventChipAnimationController" into main

parents 2b9be040 b1662cb2
Loading
Loading
Loading
Loading
+81 −42
Original line number Diff line number Diff line
@@ -20,9 +20,11 @@ import android.content.Context
import android.graphics.Insets
import android.graphics.Rect
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import androidx.compose.ui.platform.ComposeView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -55,6 +57,7 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() {
    @Mock private lateinit var sbWindowController: StatusBarWindowController
    @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider

    private val statusbarFake = FrameLayout(mContext)
    private var testView = TestView(mContext)
    private var viewCreator: ViewCreator = { testView }

@@ -64,7 +67,6 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() {
        // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
        // ensure that the chip view is added to a parent view
        whenever(sbWindowController.addViewToWindow(any(), any())).then {
            val statusbarFake = FrameLayout(mContext)
            statusbarFake.layout(
                portraitArea.left,
                portraitArea.top,
@@ -108,18 +110,25 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() {

    @Test
    fun prepareChipAnimation_positionsChip() {
        try {
            ViewUtils.attachView(statusbarFake)
            TestableLooper.get(this).processAllMessages()
            controller.prepareChipAnimation(viewCreator)
        val chipRect = controller.chipBounds

        // SB area = 10, 10, 990, 100
        // chip size = 0, 0, 100, 50
            val chipRect = controller.chipBounds
            assertThat(chipRect).isEqualTo(Rect(890, 30, 990, 80))
        } finally {
            ViewUtils.detachView(statusbarFake)
        }
    }

    @Test
    fun prepareChipAnimation_rotation_repositionsChip() {
        controller.prepareChipAnimation(viewCreator)
        try {
            ViewUtils.attachView(statusbarFake)
            TestableLooper.get(this).processAllMessages()

            controller.prepareChipAnimation(viewCreator)
            // Chip has been prepared, and is located at (890, 30, 990, 75)
            // Rotation should put it into its landscape location:
            // SB area = 10, 10, 1990, 80
@@ -131,18 +140,45 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() {

            val chipRect = controller.chipBounds
            assertThat(chipRect).isEqualTo(Rect(1890, 20, 1990, 70))
        } finally {
            ViewUtils.detachView(statusbarFake)
        }
    }

    /** regression test for b/294462223. */
    @Test
    fun prepareChipAnimation_withComposeView_doesNotCrash() {
        val composeViewCreator: ViewCreator = {
            object : BackgroundAnimatableView {
                override val view = ComposeView(mContext)

                override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {}
            }
        }

        try {
            ViewUtils.attachView(statusbarFake)
            // Make sure prepareChipAnimation does not crash when it adds the chip to a ComposeView.
            controller.prepareChipAnimation(composeViewCreator)
        } finally {
            ViewUtils.detachView(statusbarFake)
        }
    }

    /** regression test for (b/289378932) */
    @Test
    fun fullScreenStatusBar_positionsChipAtTop_withTopGravity() {
        // In the case of a fullscreen status bar window, the content insets area is still correct
        try {
            ViewUtils.attachView(statusbarFake)
            TestableLooper.get(this).processAllMessages()

            // In the case of a fullscreen status bar window, the content insets area is still
            // correct
            // (because it uses the dimens), but the window can be full screen. This seems to happen
            // when launching an app from the ongoing call chip.

            // GIVEN layout the status bar window fullscreen portrait
            whenever(sbWindowController.addViewToWindow(any(), any())).then {
            val statusbarFake = FrameLayout(mContext)
                statusbarFake.layout(
                    fullScreenSb.left,
                    fullScreenSb.top,
@@ -166,6 +202,9 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() {
            // THEN it still aligns the chip to the content area provided by the insets provider
            val chipRect = controller.chipBounds
            assertThat(chipRect).isEqualTo(Rect(890, 30, 990, 80))
        } finally {
            ViewUtils.detachView(statusbarFake)
        }
    }

    private class TestView(context: Context) : View(context), BackgroundAnimatableView {
+34 −16
Original line number Diff line number Diff line
@@ -124,23 +124,32 @@ constructor(
                    ),
                )
                it.view.alpha = 0f
                // For some reason, the window view's measured width is always 0 here, so use the
                // parent (status bar)
                it.view.measure(
                    View.MeasureSpec.makeMeasureSpec(
                        (animationWindowView.parent as View).width,
                        AT_MOST,
                    ),
                    View.MeasureSpec.makeMeasureSpec(
                        (animationWindowView.parent as View).height,
                        AT_MOST,
                    ),
                )

                // b/294462223: We are not guaranteed to be attached to a window at this point so we
                // need this check to prevent a crash.
                if (it.view.isAttachedToWindow) {
                    measure(it.view)
                    updateChipBounds(
                        it,
                        contentInsetsProvider.getStatusBarContentAreaForCurrentRotation(),
                    )
                } else {
                    it.view.addOnAttachStateChangeListener(
                        object : View.OnAttachStateChangeListener {
                            override fun onViewAttachedToWindow(v: View) {
                                measure(v)
                                updateChipBounds(
                                    it,
                                    contentInsetsProvider
                                        .getStatusBarContentAreaForCurrentRotation(),
                                )
                                v.removeOnAttachStateChangeListener(this)
                            }

                            override fun onViewDetachedFromWindow(v: View) {}
                        }
                    )
                }
            }
    }

@@ -376,6 +385,15 @@ constructor(
        animRect.set(chipBounds)
    }

    private fun measure(v: View) {
        // For some reason, the window view's measured width is always 0 here, so use the parent
        // (status bar)
        v.measure(
            View.MeasureSpec.makeMeasureSpec((animationWindowView.parent as View).width, AT_MOST),
            View.MeasureSpec.makeMeasureSpec((animationWindowView.parent as View).height, AT_MOST),
        )
    }

    private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
        FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
            it.gravity = Gravity.END or Gravity.CENTER_VERTICAL