summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Liran Binyamin <liranb@google.com> 2024-12-18 12:30:22 -0500
committer Liran Binyamin <liranb@google.com> 2024-12-19 14:39:08 -0500
commitbe61c4e18b09e1e3d61e879c9b77cc4fd73b0eab (patch)
tree5129070296211d0c730c6cc334c6d9da058fef55
parent6d787e44bc7d734796f93568ee9a8cf1fc66d711 (diff)
Clip BubbleBarExpandedView for IME
Respond to IME positioning changes and clip the BubbleBarExpandedView to make sure there's no overlap with the IME. Next I'll add logic to translate the expanded view if there's enough space above it to reduce clipping. Flag: com.android.wm.shell.enable_bubble_bar Bug: 377329425 Test: atest BubbleBarExpandedViewTest Change-Id: I5136b37f1d4e2eaab077fe29c57cbf96777c3d5a
-rw-r--r--libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml5
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java7
7 files changed, 190 insertions, 12 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
index f8f8338e5f04..fd578a959e3b 100644
--- a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -3,6 +3,8 @@
<application android:debuggable="true" android:supportsRtl="true" >
<uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.wm.shell.bubbles.bar.BubbleBarAnimationHelperTest$TestActivity"
+ android:exported="true"/>
</application>
<instrumentation
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
index ffcd7d46fbae..bb111dbeffff 100644
--- a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
@@ -1,3 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell.multivalenttests">
+ <application>
+ <activity android:name="com.android.wm.shell.bubbles.bar.BubbleBarAnimationHelperTest$TestActivity"
+ android:exported="true"/>
+ </application>
</manifest>
+
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index 3e01256fd67c..957f0ca502a1 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -17,14 +17,19 @@
package com.android.wm.shell.bubbles.bar
import android.animation.AnimatorTestRule
+import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.graphics.Insets
+import android.graphics.Outline
import android.graphics.Rect
+import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
+import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -63,6 +68,7 @@ import org.mockito.kotlin.whenever
class BubbleBarAnimationHelperTest {
@get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
+ private lateinit var activityScenario: ActivityScenario<TestActivity>
companion object {
const val SCREEN_WIDTH = 2000
@@ -83,6 +89,8 @@ class BubbleBarAnimationHelperTest {
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
ProtoLog.init()
+ activityScenario = ActivityScenario.launch(TestActivity::class.java)
+ activityScenario.onActivity { activity -> container = activity.container }
val windowManager = context.getSystemService(WindowManager::class.java)
bubblePositioner = BubblePositioner(context, windowManager)
bubblePositioner.setShowingInBubbleBar(true)
@@ -102,8 +110,6 @@ class BubbleBarAnimationHelperTest {
mainExecutor = TestShellExecutor()
bgExecutor = TestShellExecutor()
- container = FrameLayout(context)
-
animationHelper = BubbleBarAnimationHelper(context, bubblePositioner)
}
@@ -121,7 +127,7 @@ class BubbleBarAnimationHelperTest {
val semaphore = Semaphore(0)
val after = Runnable { semaphore.release() }
- getInstrumentation().runOnMainSync {
+ activityScenario.onActivity {
animationHelper.animateSwitch(fromBubble, toBubble, after)
animatorTestRule.advanceTimeBy(1000)
}
@@ -145,7 +151,7 @@ class BubbleBarAnimationHelperTest {
.updateHandleColor(/* isRegionDark= */ true, /* animated= */ false)
val toBubble = createBubble(key = "to").initialize(container)
- getInstrumentation().runOnMainSync {
+ activityScenario.onActivity {
animationHelper.animateSwitch(fromBubble, toBubble, /* afterAnimation= */ null)
animatorTestRule.advanceTimeBy(1000)
}
@@ -161,7 +167,7 @@ class BubbleBarAnimationHelperTest {
val toBubbleTaskController = mock<TaskViewTaskController>()
val toBubble = createBubble("to", toBubbleTaskController).initialize(container)
- getInstrumentation().runOnMainSync {
+ activityScenario.onActivity {
animationHelper.animateSwitch(fromBubble, toBubble) {}
// Start the animation, but don't finish
animatorTestRule.advanceTimeBy(100)
@@ -183,7 +189,7 @@ class BubbleBarAnimationHelperTest {
val semaphore = Semaphore(0)
val after = Runnable { semaphore.release() }
- getInstrumentation().runOnMainSync {
+ activityScenario.onActivity {
animationHelper.animateSwitch(fromBubble, overflow, after)
animatorTestRule.advanceTimeBy(1000)
}
@@ -206,7 +212,7 @@ class BubbleBarAnimationHelperTest {
val semaphore = Semaphore(0)
val after = Runnable { semaphore.release() }
- getInstrumentation().runOnMainSync {
+ activityScenario.onActivity {
animationHelper.animateSwitch(overflow, toBubble, after)
animatorTestRule.advanceTimeBy(1000)
}
@@ -226,7 +232,7 @@ class BubbleBarAnimationHelperTest {
val taskController = mock<TaskViewTaskController>()
val bubble = createBubble("key", taskController).initialize(container)
- getInstrumentation().runOnMainSync {
+ activityScenario.onActivity {
animationHelper.animateExpansion(bubble) {}
animatorTestRule.advanceTimeBy(1000)
}
@@ -243,6 +249,80 @@ class BubbleBarAnimationHelperTest {
verify(taskController).setWindowBounds(any())
}
+ @Test
+ fun animateExpansion() {
+ val bubble = createBubble(key = "b1").initialize(container)
+ val bbev = bubble.bubbleBarExpandedView!!
+
+ val semaphore = Semaphore(0)
+ val after = Runnable { semaphore.release() }
+
+ activityScenario.onActivity {
+ bbev.onTaskCreated()
+ animationHelper.animateExpansion(bubble, after)
+ animatorTestRule.advanceTimeBy(1000)
+ }
+ getInstrumentation().waitForIdleSync()
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bbev.alpha).isEqualTo(1)
+ }
+
+ @Test
+ fun onImeTopChanged_noOverlap() {
+ val bubble = createBubble(key = "b1").initialize(container)
+ val bbev = bubble.bubbleBarExpandedView!!
+
+ val semaphore = Semaphore(0)
+ val after = Runnable { semaphore.release() }
+
+ activityScenario.onActivity {
+ bbev.onTaskCreated()
+ animationHelper.animateExpansion(bubble, after)
+ animatorTestRule.advanceTimeBy(1000)
+ }
+ getInstrumentation().waitForIdleSync()
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+
+ val bbevBottom = bbev.contentBottomOnScreen + bubblePositioner.insets.top
+ activityScenario.onActivity {
+ // notify that the IME top coordinate is greater than the bottom of the expanded view.
+ // there's no overlap so it should not be clipped.
+ animationHelper.onImeTopChanged(bbevBottom * 2)
+ }
+ val outline = Outline()
+ bbev.outlineProvider.getOutline(bbev, outline)
+ assertThat(outline.mRect.bottom).isEqualTo(bbev.height)
+ }
+
+ @Test
+ fun onImeTopChanged_overlapsWithExpandedView() {
+ val bubble = createBubble(key = "b1").initialize(container)
+ val bbev = bubble.bubbleBarExpandedView!!
+
+ val semaphore = Semaphore(0)
+ val after = Runnable { semaphore.release() }
+
+ activityScenario.onActivity {
+ bbev.onTaskCreated()
+ animationHelper.animateExpansion(bubble, after)
+ animatorTestRule.advanceTimeBy(1000)
+ }
+ getInstrumentation().waitForIdleSync()
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+
+ activityScenario.onActivity {
+ // notify that the IME top coordinate is less than the bottom of the expanded view,
+ // meaning it overlaps with it so we should be clipping the expanded view.
+ animationHelper.onImeTopChanged(bbev.contentBottomOnScreen - 10)
+ }
+ val outline = Outline()
+ bbev.outlineProvider.getOutline(bbev, outline)
+ assertThat(outline.mRect.bottom).isEqualTo(bbev.height - 10)
+ }
+
private fun createBubble(
key: String,
taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(),
@@ -273,14 +353,24 @@ class BubbleBarAnimationHelperTest {
}
private fun Bubble.initialize(container: ViewGroup): Bubble {
- getInstrumentation().runOnMainSync { container.addView(bubbleBarExpandedView) }
+ activityScenario.onActivity { container.addView(bubbleBarExpandedView) }
// Mark taskView's visible
bubbleBarExpandedView!!.onContentVisibilityChanged(true)
return this
}
private fun BubbleOverflow.initialize(container: ViewGroup): BubbleOverflow {
- getInstrumentation().runOnMainSync { container.addView(bubbleBarExpandedView) }
+ activityScenario.onActivity { container.addView(bubbleBarExpandedView) }
return this
}
+
+ class TestActivity : Activity() {
+ lateinit var container: FrameLayout
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ container = FrameLayout(applicationContext)
+ container.layoutParams = LayoutParams(50, 50)
+ setContentView(container)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 4fc8af126247..348f13a493b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2564,6 +2564,16 @@ public class BubbleController implements ConfigurationChangeListener,
return IME_ANIMATION_DEFAULT;
}
+
+ @Override
+ public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
+ if (mContext.getDisplayId() != displayId) {
+ return;
+ }
+ if (mLayerView != null) {
+ mLayerView.onImeTopChanged(imeTop);
+ }
+ }
}
/**
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 3188e5b9c6d2..de6d1f6c8852 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
@@ -30,6 +30,8 @@ import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.TASK_VIEW_A
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static java.lang.Math.max;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -375,7 +377,6 @@ public class BubbleBarAnimationHelper {
return animator;
}
-
/**
* Animate the expanded bubble when it is being dragged
*/
@@ -586,6 +587,18 @@ public class BubbleBarAnimationHelper {
}
}
+ /** Handles IME position changes. */
+ public void onImeTopChanged(int imeTop) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Bubble bar expanded view was null when IME top changed");
+ return;
+ }
+ int bbevBottom = bbev.getContentBottomOnScreen();
+ int clip = max(bbevBottom - imeTop, 0);
+ bbev.updateBottomClip(clip);
+ }
+
private @Nullable BubbleBarExpandedView getExpandedView() {
BubbleViewProvider bubble = mExpandedBubble;
if (bubble != null) {
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 65c929ab6fb4..e073b02dc630 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
@@ -137,6 +137,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
private Executor mBackgroundExecutor;
private final Rect mSampleRect = new Rect();
private final int[] mLoc = new int[2];
+ private final Rect mTempBounds = new Rect();
/** Height of the caption inset at the top of the TaskView */
private int mCaptionHeight;
@@ -161,6 +162,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
private boolean mIsAnimating;
private boolean mIsDragging;
+ private boolean mIsClipping = false;
+ private int mBottomClip = 0;
+
/** 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. */
@@ -203,7 +207,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCurrentCornerRadius);
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight() - mBottomClip,
+ mCurrentCornerRadius);
}
});
// Set a touch sink to ensure that clicks on the caption area do not propagate to the parent
@@ -661,6 +666,52 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
}
+ /** The y coordinate of the bottom of the expanded view. */
+ public int getContentBottomOnScreen() {
+ if (mOverflowView != null) {
+ mOverflowView.getBoundsOnScreen(mTempBounds);
+ }
+ if (mTaskView != null) {
+ mTaskView.getBoundsOnScreen(mTempBounds);
+ }
+ // return the bottom of the content rect, adjusted for insets so the result is in screen
+ // coordinate
+ return mTempBounds.bottom + mPositioner.getInsets().top;
+ }
+
+ /** Update the amount by which to clip the expanded view at the bottom. */
+ public void updateBottomClip(int bottomClip) {
+ mBottomClip = bottomClip;
+ onClipUpdate();
+ }
+
+ private void onClipUpdate() {
+ if (mBottomClip == 0) {
+ if (mIsClipping) {
+ mIsClipping = false;
+ if (mTaskView != null) {
+ mTaskView.setClipBounds(null);
+ mTaskView.setEnableSurfaceClipping(false);
+ }
+ invalidateOutline();
+ }
+ } else {
+ if (!mIsClipping) {
+ mIsClipping = true;
+ if (mTaskView != null) {
+ mTaskView.setEnableSurfaceClipping(true);
+ }
+ }
+ invalidateOutline();
+ if (mTaskView != null) {
+ Rect clipBounds = new Rect(0, 0,
+ mTaskView.getWidth(),
+ mTaskView.getHeight() - mBottomClip);
+ mTaskView.setClipBounds(clipBounds);
+ }
+ }
+ }
+
private void recreateRegionSamplingHelper() {
if (mRegionSamplingHelper != null) {
mRegionSamplingHelper.stopAndDestroy();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 425afbed0742..6bca3d4ee5c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -424,6 +424,13 @@ public class BubbleBarLayerView extends FrameLayout
}
}
+ /** Handles IME position changes. */
+ public void onImeTopChanged(int imeTop) {
+ if (mIsExpanded) {
+ mAnimationHelper.onImeTopChanged(imeTop);
+ }
+ }
+
/**
* Log the event only if {@link #mExpandedBubble} is a {@link Bubble}.
* <p>