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

Commit 991328ef authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Fix IME flicker when dismissing stack

When the bubbles stack is collapsed or expanded and the IME is visible,
we request to hide it and start the expand or collapse animation at
the same time. This causes a flicker.

This change first hides the IME and waits for it to be hidden before
starting the expand or collapse animations.

Demo: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/cxDr6MipIW3dC5By64LchE

Flag: EXEMPT bug fix
Bug: 379474417
Test: atest BubbleStackViewTest
Change-Id: Ic14b97b40a6482abf93b22c48be99639bf177824
parent e3873301
Loading
Loading
Loading
Loading
+137 −1
Original line number Original line Diff line number Diff line
@@ -49,7 +49,9 @@ import org.junit.Rule
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verify
import java.util.concurrent.Semaphore
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit
@@ -174,6 +176,137 @@ class BubbleStackViewTest {
        assertThat(lastUpdate!!.expanded).isTrue()
        assertThat(lastUpdate!!.expanded).isTrue()
    }
    }


    @Test
    fun expandStack_imeHidden() {
        val bubble = createAndInflateBubble()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
        }

        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        assertThat(bubbleStackView.bubbleCount).isEqualTo(1)

        positioner.setImeVisible(false, 0)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            // simulate a request from the bubble data listener to expand the stack
            bubbleStackView.isExpanded = true
            verify(sysuiProxy).onStackExpandChanged(true)
            shellExecutor.flushAll()
        }

        assertThat(bubbleStackViewManager.onImeHidden).isNull()
    }

    @Test
    fun collapseStack_imeHidden() {
        val bubble = createAndInflateBubble()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
        }

        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        assertThat(bubbleStackView.bubbleCount).isEqualTo(1)

        positioner.setImeVisible(false, 0)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            // simulate a request from the bubble data listener to expand the stack
            bubbleStackView.isExpanded = true
            verify(sysuiProxy).onStackExpandChanged(true)
            shellExecutor.flushAll()
        }

        assertThat(bubbleStackViewManager.onImeHidden).isNull()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            // simulate a request from the bubble data listener to collapse the stack
            bubbleStackView.isExpanded = false
            verify(sysuiProxy).onStackExpandChanged(false)
            shellExecutor.flushAll()
        }

        assertThat(bubbleStackViewManager.onImeHidden).isNull()
    }

    @Test
    fun expandStack_waitsForIme() {
        val bubble = createAndInflateBubble()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
        }

        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        assertThat(bubbleStackView.bubbleCount).isEqualTo(1)

        positioner.setImeVisible(true, 100)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            // simulate a request from the bubble data listener to expand the stack
            bubbleStackView.isExpanded = true
        }

        val onImeHidden = bubbleStackViewManager.onImeHidden
        assertThat(onImeHidden).isNotNull()
        verify(sysuiProxy, never()).onStackExpandChanged(any())
        positioner.setImeVisible(false, 0)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            onImeHidden!!.run()
            verify(sysuiProxy).onStackExpandChanged(true)
            shellExecutor.flushAll()
        }
    }

    @Test
    fun collapseStack_waitsForIme() {
        val bubble = createAndInflateBubble()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
        }

        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        assertThat(bubbleStackView.bubbleCount).isEqualTo(1)

        positioner.setImeVisible(true, 100)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            // simulate a request from the bubble data listener to expand the stack
            bubbleStackView.isExpanded = true
        }

        var onImeHidden = bubbleStackViewManager.onImeHidden
        assertThat(onImeHidden).isNotNull()
        verify(sysuiProxy, never()).onStackExpandChanged(any())
        positioner.setImeVisible(false, 0)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            onImeHidden!!.run()
            verify(sysuiProxy).onStackExpandChanged(true)
            shellExecutor.flushAll()
        }

        bubbleStackViewManager.onImeHidden = null
        positioner.setImeVisible(true, 100)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            // simulate a request from the bubble data listener to collapse the stack
            bubbleStackView.isExpanded = false
        }

        onImeHidden = bubbleStackViewManager.onImeHidden
        assertThat(onImeHidden).isNotNull()
        verify(sysuiProxy, never()).onStackExpandChanged(false)
        positioner.setImeVisible(false, 0)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            onImeHidden!!.run()
            verify(sysuiProxy).onStackExpandChanged(false)
            shellExecutor.flushAll()
        }
    }

    @Test
    @Test
    fun tapDifferentBubble_shouldReorder() {
    fun tapDifferentBubble_shouldReorder() {
        val bubble1 = createAndInflateChatBubble(key = "bubble1")
        val bubble1 = createAndInflateChatBubble(key = "bubble1")
@@ -418,6 +551,7 @@ class BubbleStackViewTest {
    }
    }


    private class FakeBubbleStackViewManager : BubbleStackViewManager {
    private class FakeBubbleStackViewManager : BubbleStackViewManager {
        var onImeHidden: Runnable? = null


        override fun onAllBubblesAnimatedOut() {}
        override fun onAllBubblesAnimatedOut() {}


@@ -425,6 +559,8 @@ class BubbleStackViewTest {


        override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {}
        override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {}


        override fun hideCurrentInputMethod() {}
        override fun hideCurrentInputMethod(onImeHidden: Runnable?) {
            this.onImeHidden = onImeHidden
        }
    }
    }
}
}
+9 −2
Original line number Original line Diff line number Diff line
@@ -213,6 +213,8 @@ public class BubbleController implements ConfigurationChangeListener,
    private final BubblePositioner mBubblePositioner;
    private final BubblePositioner mBubblePositioner;
    private Bubbles.SysuiProxy mSysuiProxy;
    private Bubbles.SysuiProxy mSysuiProxy;


    @Nullable private Runnable mOnImeHidden;

    // Tracks the id of the current (foreground) user.
    // Tracks the id of the current (foreground) user.
    private int mCurrentUserId;
    private int mCurrentUserId;
    // Current profiles of the user (e.g. user with a workprofile)
    // Current profiles of the user (e.g. user with a workprofile)
@@ -607,7 +609,8 @@ public class BubbleController implements ConfigurationChangeListener,
    /**
    /**
     * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
     * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
     */
     */
    void hideCurrentInputMethod() {
    void hideCurrentInputMethod(@Nullable Runnable onImeHidden) {
        mOnImeHidden = onImeHidden;
        mBubblePositioner.setImeVisible(false /* visible */, 0 /* height */);
        mBubblePositioner.setImeVisible(false /* visible */, 0 /* height */);
        int displayId = mWindowManager.getDefaultDisplay().getDisplayId();
        int displayId = mWindowManager.getDefaultDisplay().getDisplayId();
        try {
        try {
@@ -2250,7 +2253,7 @@ public class BubbleController implements ConfigurationChangeListener,
        if (mLayerView != null && mLayerView.isExpanded()) {
        if (mLayerView != null && mLayerView.isExpanded()) {
            if (mBubblePositioner.isImeVisible()) {
            if (mBubblePositioner.isImeVisible()) {
                // If we're collapsing, hide the IME
                // If we're collapsing, hide the IME
                hideCurrentInputMethod();
                hideCurrentInputMethod(null);
            }
            }
            mLayerView.collapse();
            mLayerView.collapse();
        }
        }
@@ -2523,6 +2526,10 @@ public class BubbleController implements ConfigurationChangeListener,
            mBubblePositioner.setImeVisible(imeVisible, imeHeight);
            mBubblePositioner.setImeVisible(imeVisible, imeHeight);
            if (mStackView != null) {
            if (mStackView != null) {
                mStackView.setImeVisible(imeVisible);
                mStackView.setImeVisible(imeVisible);
                if (!imeVisible && mOnImeHidden != null) {
                    mOnImeHidden.run();
                    mOnImeHidden = null;
                }
            }
            }
        }
        }
    }
    }
+1 −1
Original line number Original line Diff line number Diff line
@@ -82,7 +82,7 @@ interface BubbleExpandedViewManager {
                override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar
                override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar


                override fun hideCurrentInputMethod() {
                override fun hideCurrentInputMethod() {
                    controller.hideCurrentInputMethod()
                    controller.hideCurrentInputMethod(null)
                }
                }


                override fun updateBubbleBarLocation(
                override fun updateBubbleBarLocation(
+42 −21
Original line number Original line Diff line number Diff line
@@ -2233,19 +2233,22 @@ public class BubbleStackView extends FrameLayout


        boolean wasExpanded = mIsExpanded;
        boolean wasExpanded = mIsExpanded;


        hideCurrentInputMethod();
        // Do the actual expansion/collapse after the IME is hidden if it's currently visible in

        // order to avoid flickers
        Runnable onImeHidden = () -> {
            mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);
            mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);


            if (wasExpanded) {
            if (wasExpanded) {
                stopMonitoringSwipeUpGesture();
                stopMonitoringSwipeUpGesture();
                animateCollapse();
                animateCollapse();
                showManageMenu(false);
                showManageMenu(false);
            logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
                logBubbleEvent(mExpandedBubble,
                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
            } else {
            } else {
                animateExpansion();
                animateExpansion();
                // TODO: move next line to BubbleData
                // TODO: move next line to BubbleData
            logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
                logBubbleEvent(mExpandedBubble,
                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
                logBubbleEvent(mExpandedBubble,
                logBubbleEvent(mExpandedBubble,
                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
                mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
                mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
@@ -2256,6 +2259,14 @@ public class BubbleStackView extends FrameLayout
            }
            }
            notifyExpansionChanged(mExpandedBubble, mIsExpanded);
            notifyExpansionChanged(mExpandedBubble, mIsExpanded);
            announceExpandForAccessibility(mExpandedBubble, mIsExpanded);
            announceExpandForAccessibility(mExpandedBubble, mIsExpanded);
        };

        if (mPositioner.isImeVisible()) {
            hideCurrentInputMethod(onImeHidden);
        } else {
            // the IME is already hidden, so run the runnable immediately
            onImeHidden.run();
        }
    }
    }


    /**
    /**
@@ -2373,7 +2384,17 @@ public class BubbleStackView extends FrameLayout
     * not.
     * not.
     */
     */
    void hideCurrentInputMethod() {
    void hideCurrentInputMethod() {
        mManager.hideCurrentInputMethod();
        mManager.hideCurrentInputMethod(null);
    }

    /**
     * Hides the IME similar to {@link #hideCurrentInputMethod()} but also runs {@code onImeHidden}
     * after after the IME is hidden.
     *
     * @see #hideCurrentInputMethod()
     */
    void hideCurrentInputMethod(Runnable onImeHidden) {
        mManager.hideCurrentInputMethod(onImeHidden);
    }
    }


    /** Set the stack position to whatever the positioner says. */
    /** Set the stack position to whatever the positioner says. */
+3 −3
Original line number Original line Diff line number Diff line
@@ -34,7 +34,7 @@ interface BubbleStackViewManager {
    fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
    fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)


    /** Requests to hide the current input method. */
    /** Requests to hide the current input method. */
    fun hideCurrentInputMethod()
    fun hideCurrentInputMethod(onImeHidden: Runnable?)


    companion object {
    companion object {


@@ -52,8 +52,8 @@ interface BubbleStackViewManager {
                controller.isNotificationPanelExpanded(callback)
                controller.isNotificationPanelExpanded(callback)
            }
            }


            override fun hideCurrentInputMethod() {
            override fun hideCurrentInputMethod(onImeHidden: Runnable?) {
                controller.hideCurrentInputMethod()
                controller.hideCurrentInputMethod(onImeHidden)
            }
            }
        }
        }
    }
    }