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

Commit dfaaa276 authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Fix bubble bar expanded view flicker on unfold

Track the task view visibility state in the expanded view and wait
for it to be visible with a surface created or recreated before
starting the expansion animation.

Previously we removed the task view from its previous parent and
immediately added it to the new expanded view and made it visible.
This caused the task view to flicker and the expanded view background
to be visible while the surface was destroyed and recreated.

Flag: com.android.wm.shell.enable_bubble_bar
Fixes: 374640759
Test: manual
       - have a floating bubble
       - expand it and unfold
       - observe expanded view animates correctly (with a delay)
       - add a new bubble to the bubble bar
       - expand it and observe animation
       - switch bubbles and observe anination
       - fold and add a bubble without expanding it
       - unfold, expand and observe animation
Change-Id: I128eddd5df9ef3b722dc449e214f0961e6ac96cb
parent dd5840f5
Loading
Loading
Loading
Loading
+93 −4
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles.bar

import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.ShortcutInfo
import android.graphics.Insets
@@ -24,6 +25,7 @@ import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -110,9 +112,9 @@ class BubbleBarExpandedViewTest {

        regionSamplingProvider = TestRegionSamplingProvider()

        bubbleExpandedView = (inflater.inflate(
        bubbleExpandedView = inflater.inflate(
            R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
        ) as BubbleBarExpandedView)
        ) as BubbleBarExpandedView
        bubbleExpandedView.initialize(
            expandedViewManager,
            positioner,
@@ -124,11 +126,11 @@ class BubbleBarExpandedViewTest {
            regionSamplingProvider,
        )

        getInstrumentation().runOnMainSync(Runnable {
        getInstrumentation().runOnMainSync {
            bubbleExpandedView.onAttachedToWindow()
            // Helper should be created once attached to window
            testableRegionSamplingHelper = regionSamplingProvider!!.helper
        })
        }

        bubble = Bubble(
            "key",
@@ -254,6 +256,93 @@ class BubbleBarExpandedViewTest {
        assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
    }

    @Test
    fun animateExpansion_waitsUntilTaskCreated() {
        var animated = false
        bubbleExpandedView.animateExpansionWhenTaskViewVisible { animated = true }
        assertThat(animated).isFalse()
        bubbleExpandedView.onTaskCreated()
        assertThat(animated).isTrue()
    }

    @Test
    fun animateExpansion_taskViewAttachedAndVisible() {
        val inflater = LayoutInflater.from(context)
        val expandedView = inflater.inflate(
            R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
        ) as BubbleBarExpandedView
        val taskView = FakeBubbleTaskViewFactory().create()
        val taskViewParent = FrameLayout(context)
        taskViewParent.addView(taskView.taskView)
        taskView.listener.onTaskCreated(666, ComponentName(context, "BubbleBarExpandedViewTest"))
        assertThat(taskView.isVisible).isTrue()

        expandedView.initialize(
            expandedViewManager,
            positioner,
            BubbleLogger(uiEventLoggerFake),
            false /* isOverflow */,
            taskView,
            mainExecutor,
            bgExecutor,
            regionSamplingProvider,
        )

        // the task view should be removed from its parent
        assertThat(taskView.taskView.parent).isNull()

        var animated = false
        expandedView.animateExpansionWhenTaskViewVisible { animated = true }
        assertThat(animated).isFalse()

        // send an invisible signal to simulate the surface getting destroyed
        expandedView.onContentVisibilityChanged(false)

        // send a visible signal to simulate a new surface getting created
        expandedView.onContentVisibilityChanged(true)

        assertThat(taskView.taskView.parent).isEqualTo(expandedView)
        assertThat(animated).isTrue()
    }

    @Test
    fun animateExpansion_taskViewAttachedAndInvisible() {
        val inflater = LayoutInflater.from(context)
        val expandedView = inflater.inflate(
            R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
        ) as BubbleBarExpandedView
        val taskView = FakeBubbleTaskViewFactory().create()
        val taskViewParent = FrameLayout(context)
        taskViewParent.addView(taskView.taskView)
        taskView.listener.onTaskCreated(666, ComponentName(context, "BubbleBarExpandedViewTest"))
        assertThat(taskView.isVisible).isTrue()
        taskView.listener.onTaskVisibilityChanged(666, false)
        assertThat(taskView.isVisible).isFalse()

        expandedView.initialize(
            expandedViewManager,
            positioner,
            BubbleLogger(uiEventLoggerFake),
            false /* isOverflow */,
            taskView,
            mainExecutor,
            bgExecutor,
            regionSamplingProvider,
        )

        // the task view should be added to the expanded view
        assertThat(taskView.taskView.parent).isEqualTo(expandedView)

        var animated = false
        expandedView.animateExpansionWhenTaskViewVisible { animated = true }
        assertThat(animated).isFalse()

        // send a visible signal to simulate a new surface getting created
        expandedView.onContentVisibilityChanged(true)

        assertThat(animated).isTrue()
    }

    private fun BubbleBarExpandedView.menuView(): BubbleBarMenuView {
        return findViewByPredicate { it is BubbleBarMenuView }
    }
+2 −0
Original line number Diff line number Diff line
@@ -253,6 +253,7 @@ class BubbleBarLayerViewTest {

        getInstrumentation().runOnMainSync {
            bubbleBarLayerView.showExpandedView(bubble)
            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
        }
        waitForExpandedViewAnimation()

@@ -276,6 +277,7 @@ class BubbleBarLayerViewTest {

        getInstrumentation().runOnMainSync {
            bubbleBarLayerView.showExpandedView(bubble)
            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
        }
        waitForExpandedViewAnimation()

+13 −0
Original line number Diff line number Diff line
@@ -42,6 +42,16 @@ class BubbleTaskView(val taskView: TaskView, executor: Executor) {
    var componentName: ComponentName? = null
      private set

    /**
     * Whether the task view is visible and has a surface. Note that this does not check the alpha
     * value of the task view.
     *
     * When this is `true` it is safe to start showing the task view. Otherwise if this is `false`
     * callers should wait for it to be visible which will be indicated either by a call to
     * [TaskView.Listener.onTaskCreated] or [TaskView.Listener.onTaskVisibilityChanged]. */
    var isVisible = false
      private set

    /** [TaskView.Listener] for users of this class. */
    var delegateListener: TaskView.Listener? = null

@@ -61,9 +71,12 @@ class BubbleTaskView(val taskView: TaskView, executor: Executor) {
            this@BubbleTaskView.taskId = taskId
            isCreated = true
            componentName = name
            // when the task is created it is visible
            isVisible = true
        }

        override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
            this@BubbleTaskView.isVisible = visible
            delegateListener?.onTaskVisibilityChanged(taskId, visible)
        }

+24 −22
Original line number Diff line number Diff line
@@ -161,6 +161,7 @@ public class BubbleBarAnimationHelper {

        updateExpandedView();
        bbev.setAnimating(true);
        bbev.setSurfaceZOrderedOnTop(true);
        bbev.setContentVisibility(false);
        bbev.setAlpha(0f);
        bbev.setTaskViewAlpha(0f);
@@ -171,6 +172,7 @@ public class BubbleBarAnimationHelper {

        bbev.setAnimationMatrix(mExpandedViewContainerMatrix);

        bbev.animateExpansionWhenTaskViewVisible(() -> {
            mExpandedViewAlphaAnimator.start();

            PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
@@ -187,12 +189,12 @@ public class BubbleBarAnimationHelper {
                    .withEndActions(() -> {
                        bbev.setAnimationMatrix(null);
                        updateExpandedView();
                    bbev.setSurfaceZOrderedOnTop(false);
                        if (afterAnimation != null) {
                            afterAnimation.run();
                        }
                    })
                    .start();
        });
    }

    /**
+79 −9
Original line number Diff line number Diff line
@@ -131,6 +131,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    /** Current corner radius */
    private float mCurrentCornerRadius = 0f;

    /** A runnable to start the expansion animation as soon as the task view is made visible. */
    @Nullable
    private Runnable mAnimateExpansion = null;
    private TaskViewVisibilityState mVisibilityState = TaskViewVisibilityState.INVISIBLE;

    /**
     * Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
     * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
@@ -140,6 +145,18 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    private boolean mIsAnimating;
    private boolean mIsDragging;

    /** An enum value that tracks the visibility state of the task view */
    private enum TaskViewVisibilityState {
        /** The task view is going away, and we're waiting for the surface to be destroyed. */
        PENDING_INVISIBLE,
        /** The task view is invisible and does not have a surface. */
        INVISIBLE,
        /** The task view is in the process of being added to a surface. */
        PENDING_VISIBLE,
        /** The task view is visible and has a surface. */
        VISIBLE
    }

    public BubbleBarExpandedView(Context context) {
        this(context, null);
    }
@@ -206,16 +223,27 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
                    /* listener= */ this, bubbleTaskView,
                    /* viewParent= */ this);

            // if the task view is already attached to a parent we need to remove it
            if (mTaskView.getParent() != null) {
                // it's possible that the task view is visible, e.g. if we're unfolding, in which
                // case removing it will trigger a visibility change. we have to wait for that
                // signal before we can add it to this expanded view, otherwise the signal will be
                // incorrect because the task view will have a surface.
                // if the task view is not visible, then it has no surface and removing it will not
                // trigger any visibility change signals.
                if (bubbleTaskView.isVisible()) {
                    mVisibilityState = TaskViewVisibilityState.PENDING_INVISIBLE;
                }
                ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
            }
            FrameLayout.LayoutParams lp =
                    new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
            addView(mTaskView, lp);
            mTaskView.setEnableSurfaceClipping(true);
            mTaskView.setCornerRadius(mCurrentCornerRadius);
            mTaskView.setVisibility(VISIBLE);
            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));

            // if we're invisible it's safe to setup the task view and then await on the visibility
            // signal.
            if (mVisibilityState == TaskViewVisibilityState.INVISIBLE) {
                mVisibilityState = TaskViewVisibilityState.PENDING_VISIBLE;
                setupTaskView();
            }

            // Handle view needs to draw on top of task view.
            bringChildToFront(mHandleView);
@@ -269,6 +297,16 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
        });
    }

    private void setupTaskView() {
        FrameLayout.LayoutParams lp =
                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
        addView(mTaskView, lp);
        mTaskView.setEnableSurfaceClipping(true);
        mTaskView.setCornerRadius(mCurrentCornerRadius);
        mTaskView.setVisibility(VISIBLE);
        mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
    }

    public BubbleBarHandleView getHandleView() {
        return mHandleView;
    }
@@ -326,15 +364,28 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView

    @Override
    public void onTaskCreated() {
        setContentVisibility(true);
        if (mTaskView != null) {
            mTaskView.setAlpha(0);
        }
        if (mListener != null) {
            mListener.onTaskCreated();
        }
        // when the task is created we're visible
        onTaskViewVisible();
    }

    @Override
    public void onContentVisibilityChanged(boolean visible) {
        setContentVisibility(visible);
        if (mVisibilityState == TaskViewVisibilityState.PENDING_INVISIBLE && !visible) {
            // the surface is now destroyed. set up the task view and wait for the visibility
            // signal.
            mVisibilityState = TaskViewVisibilityState.PENDING_VISIBLE;
            setupTaskView();
            return;
        }
        if (visible) {
            onTaskViewVisible();
        }
    }

    @Override
@@ -350,6 +401,25 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
        mListener.onBackPressed();
    }

    void animateExpansionWhenTaskViewVisible(Runnable animateExpansion) {
        if (mVisibilityState == TaskViewVisibilityState.VISIBLE) {
            animateExpansion.run();
        } else {
            mAnimateExpansion = animateExpansion;
        }
    }

    private void onTaskViewVisible() {
        // if we're waiting to be visible, start the expansion animation if it's pending.
        if (mVisibilityState == TaskViewVisibilityState.PENDING_VISIBLE) {
            mVisibilityState = TaskViewVisibilityState.VISIBLE;
            if (mAnimateExpansion != null) {
                mAnimateExpansion.run();
                mAnimateExpansion = null;
            }
        }
    }

    /**
     * Set whether this view is currently being dragged.
     *