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

Commit 98842b5a authored by Liran Binyamin's avatar Liran Binyamin Committed by Android (Google) Code Review
Browse files

Merge "Fix flickers when switching bubbles with IME open" into main

parents 60f66360 099f148e
Loading
Loading
Loading
Loading
+161 −1
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubbleStackView.SurfaceSynchronizer
import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
@@ -75,6 +77,7 @@ class BubbleStackViewTest {
    private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
    private lateinit var bubbleData: BubbleData
    private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
    private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer
    private var sysuiProxy = mock<SysuiProxy>()

    @Before
@@ -108,13 +111,14 @@ class BubbleStackViewTest {
        bubbleStackViewManager = FakeBubbleStackViewManager()
        expandedViewManager = FakeBubbleExpandedViewManager()
        bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
        surfaceSynchronizer = FakeSurfaceSynchronizer()
        bubbleStackView =
            BubbleStackView(
                context,
                bubbleStackViewManager,
                positioner,
                bubbleData,
                null,
                surfaceSynchronizer,
                FloatingContentCoordinator(),
                { sysuiProxy },
                shellExecutor
@@ -309,6 +313,7 @@ class BubbleStackViewTest {

    @Test
    fun tapDifferentBubble_shouldReorder() {
        surfaceSynchronizer.isActive = false
        val bubble1 = createAndInflateChatBubble(key = "bubble1")
        val bubble2 = createAndInflateChatBubble(key = "bubble2")
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -378,6 +383,147 @@ class BubbleStackViewTest {
            .inOrder()
    }

    @Test
    fun tapDifferentBubble_imeVisible_shouldWaitForIme() {
        val bubble1 = createAndInflateChatBubble(key = "bubble1")
        val bubble2 = createAndInflateChatBubble(key = "bubble2")
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble1)
            bubbleStackView.addBubble(bubble2)
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()

        assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
        assertThat(bubbleData.bubbles).hasSize(2)
        assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
        assertThat(bubble2.iconView).isNotNull()

        val expandListener = FakeBubbleExpandListener()
        bubbleStackView.setExpandListener(expandListener)

        var lastUpdate: BubbleData.Update? = null
        val semaphore = Semaphore(0)
        val listener =
            BubbleData.Listener { update ->
                lastUpdate = update
                semaphore.release()
            }
        bubbleData.setListener(listener)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubble2.iconView!!.performClick()
            assertThat(bubbleData.isExpanded).isTrue()

            bubbleStackView.setSelectedBubble(bubble2)
            bubbleStackView.isExpanded = true
            shellExecutor.flushAll()
        }

        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
        assertThat(lastUpdate!!.expanded).isTrue()
        assertThat(lastUpdate!!.bubbles.map { it.key })
            .containsExactly("bubble2", "bubble1")
            .inOrder()

        // wait for idle to allow the animation to start
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        // wait for the expansion animation to complete before interacting with the bubbles
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
            AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)

        // make the IME visible and tap on bubble1 to select it
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            positioner.setImeVisible(true, 100)
            bubble1.iconView!!.performClick()
            // we have to set the selected bubble in the stack view manually because we don't have a
            // listener wired up.
            bubbleStackView.setSelectedBubble(bubble1)
            shellExecutor.flushAll()
        }

        val onImeHidden = bubbleStackViewManager.onImeHidden
        assertThat(onImeHidden).isNotNull()

        assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true))

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            onImeHidden!!.run()
            shellExecutor.flushAll()
        }

        assertThat(expandListener.bubblesExpandedState)
            .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
        assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
    }

    @Test
    fun tapDifferentBubble_imeHidden_updatesImmediately() {
        val bubble1 = createAndInflateChatBubble(key = "bubble1")
        val bubble2 = createAndInflateChatBubble(key = "bubble2")
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble1)
            bubbleStackView.addBubble(bubble2)
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()

        assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
        assertThat(bubbleData.bubbles).hasSize(2)
        assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
        assertThat(bubble2.iconView).isNotNull()

        val expandListener = FakeBubbleExpandListener()
        bubbleStackView.setExpandListener(expandListener)

        var lastUpdate: BubbleData.Update? = null
        val semaphore = Semaphore(0)
        val listener =
            BubbleData.Listener { update ->
                lastUpdate = update
                semaphore.release()
            }
        bubbleData.setListener(listener)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubble2.iconView!!.performClick()
            assertThat(bubbleData.isExpanded).isTrue()

            bubbleStackView.setSelectedBubble(bubble2)
            bubbleStackView.isExpanded = true
            shellExecutor.flushAll()
        }

        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
        assertThat(lastUpdate!!.expanded).isTrue()
        assertThat(lastUpdate!!.bubbles.map { it.key })
            .containsExactly("bubble2", "bubble1")
            .inOrder()

        // wait for idle to allow the animation to start
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        // wait for the expansion animation to complete before interacting with the bubbles
        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
            AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)

        // make the IME hidden and tap on bubble1 to select it
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            positioner.setImeVisible(false, 0)
            bubble1.iconView!!.performClick()
            // we have to set the selected bubble in the stack view manually because we don't have a
            // listener wired up.
            bubbleStackView.setSelectedBubble(bubble1)
            shellExecutor.flushAll()
        }

        val onImeHidden = bubbleStackViewManager.onImeHidden
        assertThat(onImeHidden).isNull()

        assertThat(expandListener.bubblesExpandedState)
            .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
        assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
    }

    @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
    @Test
    fun testCreateStackView_noOverflowContents_noOverflow() {
@@ -563,4 +709,18 @@ class BubbleStackViewTest {
            this.onImeHidden = onImeHidden
        }
    }

    private class FakeBubbleExpandListener : BubbleExpandListener {
        val bubblesExpandedState = mutableMapOf<String, Boolean>()
        override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) {
            bubblesExpandedState[key] = isExpanding
        }
    }

    private class FakeSurfaceSynchronizer : SurfaceSynchronizer {
        var isActive = true
        override fun syncSurfaceAndRun(callback: Runnable) {
            if (isActive) callback.run()
        }
    }
}
+29 −24
Original line number Diff line number Diff line
@@ -2183,8 +2183,7 @@ public class BubbleStackView extends FrameLayout
        ProtoLog.d(WM_SHELL_BUBBLES, "showNewlySelectedBubble b=%s, previouslySelected=%s,"
                        + " mIsExpanded=%b", newlySelectedKey, previouslySelectedKey, mIsExpanded);
        if (mIsExpanded) {
            hideCurrentInputMethod();

            Runnable onImeHidden = () -> {
                if (Flags.enableRetrievableBubbles()) {
                    if (mBubbleData.getBubbles().size() == 1) {
                        // First bubble, check if overflow visibility needs to change
@@ -2192,9 +2191,9 @@ public class BubbleStackView extends FrameLayout
                    }
                }

            // Make the container of the expanded view transparent before removing the expanded view
            // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
            // expanded view becomes visible on the screen. See b/126856255
                // Make the container of the expanded view transparent before removing the expanded
                // view from it. Otherwise a punch hole created by {@link android.view.SurfaceView}
                // in the expanded view becomes visible on the screen. See b/126856255
                mExpandedViewContainer.setAlpha(0.0f);
                mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
                    if (previouslySelected != null) {
@@ -2211,6 +2210,12 @@ public class BubbleStackView extends FrameLayout
                    notifyExpansionChanged(previouslySelected, false /* expanded */);
                    notifyExpansionChanged(bubbleToSelect, true /* expanded */);
                });
            };
            if (mPositioner.isImeVisible()) {
                hideCurrentInputMethod(onImeHidden);
            } else {
                onImeHidden.run();
            }
        }
    }