diff options
| author | 2024-12-18 12:30:22 -0500 | |
|---|---|---|
| committer | 2024-12-19 14:39:08 -0500 | |
| commit | be61c4e18b09e1e3d61e879c9b77cc4fd73b0eab (patch) | |
| tree | 5129070296211d0c730c6cc334c6d9da058fef55 | |
| parent | 6d787e44bc7d734796f93568ee9a8cf1fc66d711 (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
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>  |