summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Liran Binyamin <liranb@google.com> 2024-11-14 16:32:02 -0500
committer Liran Binyamin <liranb@google.com> 2024-11-15 15:10:16 -0500
commitdfaaa276d65d87adb3c16c3392ec5051cc174f1a (patch)
tree58ad3d2f7804e591628050f81e6aead68fbefcc8
parentdd5840f5ecf99e91eaf723d64be3a13b101394f0 (diff)
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
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt97
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java88
5 files changed, 211 insertions, 35 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 0d742cc6e382..9a17693dd75e 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -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 }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 00d9a931cebe..6fca79876f12 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -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()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
index 68fc0c99abee..a517a2d550b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -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)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 74c3748dccaf..a313bd004a51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -161,6 +161,7 @@ public class BubbleBarAnimationHelper {
updateExpandedView();
bbev.setAnimating(true);
+ bbev.setSurfaceZOrderedOnTop(true);
bbev.setContentVisibility(false);
bbev.setAlpha(0f);
bbev.setTaskViewAlpha(0f);
@@ -171,28 +172,29 @@ public class BubbleBarAnimationHelper {
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
- mExpandedViewAlphaAnimator.start();
-
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .addUpdateListener((target, values) -> {
- bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
- })
- .withEndActions(() -> {
- bbev.setAnimationMatrix(null);
- updateExpandedView();
- bbev.setSurfaceZOrderedOnTop(false);
- if (afterAnimation != null) {
- afterAnimation.run();
- }
- })
- .start();
+ bbev.animateExpansionWhenTaskViewVisible(() -> {
+ mExpandedViewAlphaAnimator.start();
+
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleInSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleInSpringConfig)
+ .addUpdateListener((target, values) -> {
+ bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
+ })
+ .withEndActions(() -> {
+ bbev.setAnimationMatrix(null);
+ updateExpandedView();
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
+ })
+ .start();
+ });
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 272dfecb0bf9..e1dabdcc7f9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -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.
*