summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ats Jenk <atsjenk@google.com> 2025-03-14 14:38:44 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-14 14:38:44 -0700
commit4302e6ec866d7b0e3bc615e405c212b3c30ab4fd (patch)
tree9fe63f6349ae3de1f2cbe1f11f392d26954fb35d
parent5d93dc54cb2e152935a2b6b46f67bf0fbdb08d2b (diff)
parent0f4c171019b7365f993e90c1a532ed69ca86f779 (diff)
Merge "Include bar outline for bubble drag indicators" into main
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt129
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt110
5 files changed, 247 insertions, 19 deletions
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index e1bf6638a9b2..e68680219349 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -294,6 +294,8 @@
<dimen name="bubble_bar_expanded_view_drop_target_padding_top">60dp</dimen>
<dimen name="bubble_bar_expanded_view_drop_target_padding_bottom">24dp</dimen>
<dimen name="bubble_bar_expanded_view_drop_target_padding_horizontal">48dp</dimen>
+ <dimen name="bubble_bar_drop_target_width">84dp</dimen>
+ <dimen name="bubble_bar_drop_target_height">48dp</dimen>
<!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
<dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
<!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt
index 9bee11a92430..84e0fbe96de2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt
@@ -26,4 +26,9 @@ interface BubbleDropTargetBoundsProvider {
* Get bubble bar expanded view visual drop target bounds on screen
*/
fun getBubbleBarExpandedViewDropTargetBounds(onLeft: Boolean): Rect
+
+ /**
+ * Get the bar visual drop target bounds on screen
+ */
+ fun getBarDropTargetBounds(onLeft: Boolean): Rect
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 03d6b0a8075d..0b45b086e13c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -106,6 +106,8 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
private int mBarExpViewDropTargetPaddingTop;
private int mBarExpViewDropTargetPaddingBottom;
private int mBarExpViewDropTargetPaddingHorizontal;
+ private int mBarDropTargetWidth;
+ private int mBarDropTargetHeight;
private PointF mRestingStackPosition;
@@ -181,6 +183,8 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
R.dimen.bubble_bar_expanded_view_drop_target_padding_bottom);
mBarExpViewDropTargetPaddingHorizontal = res.getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_drop_target_padding_horizontal);
+ mBarDropTargetWidth = res.getDimensionPixelSize(R.dimen.bubble_bar_drop_target_width);
+ mBarDropTargetHeight = res.getDimensionPixelSize(R.dimen.bubble_bar_drop_target_height);
if (mShowingInBubbleBar) {
mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth;
@@ -1003,4 +1007,20 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
);
return bounds;
}
+
+ @NonNull
+ @Override
+ public Rect getBarDropTargetBounds(boolean onLeft) {
+ Rect bounds = getBubbleBarExpandedViewDropTargetBounds(onLeft);
+ bounds.top = getBubbleBarTopOnScreen();
+ bounds.bottom = bounds.top + mBarDropTargetHeight;
+ if (onLeft) {
+ // Keep the left edge from expanded view
+ bounds.right = bounds.left + mBarDropTargetWidth;
+ } else {
+ // Keep the right edge from expanded view
+ bounds.left = bounds.right - mBarDropTargetWidth;
+ }
+ return bounds;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
index 23562388b3e5..5e4122ba14ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.app.ActivityManager
@@ -32,6 +33,8 @@ import android.view.View
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.view.animation.DecelerateInterpolator
+import android.widget.FrameLayout
+import androidx.core.animation.doOnEnd
import com.android.internal.annotations.VisibleForTesting
import com.android.window.flags.Flags
import com.android.wm.shell.R
@@ -42,6 +45,7 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.shared.annotations.ShellDesktopThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider
import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
@@ -64,6 +68,8 @@ constructor(
private val snapEventHandler: SnapEventHandler,
) {
@VisibleForTesting var indicatorView: View? = null
+ // Optional extra indicator showing the outline of the bubble bar
+ private var barIndicatorView: View? = null
private var indicatorViewHost: SurfaceControlViewHost? = null
// Below variables and the SyncTransactionQueue are the only variables that should
// be accessed from shell main thread. Everything else should be used exclusively
@@ -93,7 +99,12 @@ constructor(
screenWidth = metrics.widthPixels
screenHeight = metrics.heightPixels
}
- indicatorView = View(context)
+ indicatorView =
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ FrameLayout(context)
+ } else {
+ View(context)
+ }
val leash =
indicatorBuilder
.setName("Desktop Mode Visual Indicator")
@@ -183,23 +194,50 @@ constructor(
)
} else {
val animStartType = IndicatorType.valueOf(currentType.name)
- val animator =
- indicatorView?.let {
- VisualIndicatorAnimator.animateIndicatorType(
- it,
- layout,
- animStartType,
- newType,
- bubbleBoundsProvider,
- taskInfo.displayId,
- snapEventHandler,
- )
- } ?: return@execute
+ val indicator = indicatorView ?: return@execute
+ var animator: Animator =
+ VisualIndicatorAnimator.animateIndicatorType(
+ indicator,
+ layout,
+ animStartType,
+ newType,
+ bubbleBoundsProvider,
+ taskInfo.displayId,
+ snapEventHandler,
+ )
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ if (currentType.isBubbleType() || newType.isBubbleType()) {
+ animator = addBarIndicatorAnimation(animator, currentType, newType)
+ }
+ }
animator.start()
}
}
}
+ private fun addBarIndicatorAnimation(
+ visualIndicatorAnimator: Animator,
+ currentType: IndicatorType,
+ newType: IndicatorType,
+ ): Animator {
+ if (newType.isBubbleType()) {
+ getOrCreateBubbleBarIndicator(newType)?.let { bar ->
+ return AnimatorSet().apply {
+ playTogether(visualIndicatorAnimator, fadeBarIndicatorIn(bar))
+ }
+ }
+ }
+ if (currentType.isBubbleType()) {
+ barIndicatorView?.let { bar ->
+ barIndicatorView = null
+ return AnimatorSet().apply {
+ playTogether(visualIndicatorAnimator, fadeBarIndicatorOut(bar))
+ }
+ }
+ }
+ return visualIndicatorAnimator
+ }
+
/**
* Fade indicator in as provided type.
*
@@ -223,17 +261,20 @@ constructor(
snapEventHandler: SnapEventHandler,
) {
desktopExecutor.assertCurrentThread()
- indicatorView?.let {
- it.setBackgroundResource(R.drawable.desktop_windowing_transition_background)
- val animator =
+ indicatorView?.let { indicator ->
+ indicator.setBackgroundResource(R.drawable.desktop_windowing_transition_background)
+ var animator: Animator =
VisualIndicatorAnimator.fadeBoundsIn(
- it,
+ indicator,
type,
layout,
bubbleBoundsProvider,
displayId,
snapEventHandler,
)
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ animator = addBarIndicatorAnimation(animator, IndicatorType.NO_INDICATOR, type)
+ }
animator.start()
}
}
@@ -259,7 +300,7 @@ constructor(
desktopExecutor.execute {
indicatorView?.let {
val animStartType = IndicatorType.valueOf(currentType.name)
- val animator =
+ var animator: Animator =
VisualIndicatorAnimator.fadeBoundsOut(
it,
animStartType,
@@ -268,6 +309,10 @@ constructor(
displayId,
snapEventHandler,
)
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ animator =
+ addBarIndicatorAnimation(animator, currentType, IndicatorType.NO_INDICATOR)
+ }
animator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
@@ -302,6 +347,38 @@ constructor(
isReleased = true
}
+ private fun getOrCreateBubbleBarIndicator(type: IndicatorType): View? {
+ val container = indicatorView as? FrameLayout ?: return null
+ val onLeft = type == IndicatorType.TO_BUBBLE_LEFT_INDICATOR
+ val bounds = bubbleBoundsProvider?.getBarDropTargetBounds(onLeft) ?: return null
+ val lp = FrameLayout.LayoutParams(bounds.width(), bounds.height())
+ lp.leftMargin = bounds.left
+ lp.topMargin = bounds.top
+ if (barIndicatorView == null) {
+ val indicator = View(container.context)
+ indicator.setBackgroundResource(R.drawable.desktop_windowing_transition_background)
+ container.addView(indicator, lp)
+ barIndicatorView = indicator
+ } else {
+ barIndicatorView?.layoutParams = lp
+ }
+ return barIndicatorView
+ }
+
+ private fun fadeBarIndicatorIn(barIndicator: View): Animator {
+ // Use layout bounds as the end bounds in case the view has not been laid out yet
+ val lp = barIndicator.layoutParams
+ val endBounds = Rect(0, 0, lp.width, lp.height)
+ return VisualIndicatorAnimator.fadeBoundsIn(barIndicator, endBounds)
+ }
+
+ private fun fadeBarIndicatorOut(barIndicator: View): Animator {
+ val startBounds = Rect(0, 0, barIndicator.width, barIndicator.height)
+ val barAnimator = VisualIndicatorAnimator.fadeBoundsOut(barIndicator, startBounds)
+ barAnimator.doOnEnd { (indicatorView as? FrameLayout)?.removeView(barIndicator) }
+ return barAnimator
+ }
+
/**
* Animator for Desktop Mode transitions which supports bounds and alpha animation. Functions
* should only be called from the desktop executor.
@@ -383,9 +460,13 @@ constructor(
displayId,
snapEventHandler,
)
+ return fadeBoundsIn(view, endBounds)
+ }
+
+ @ShellDesktopThread
+ fun fadeBoundsIn(view: View, endBounds: Rect): VisualIndicatorAnimator {
val startBounds = getMinBounds(endBounds)
view.background.bounds = startBounds
-
val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
animator.interpolator = DecelerateInterpolator()
setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM)
@@ -409,6 +490,11 @@ constructor(
displayId,
snapEventHandler,
)
+ return fadeBoundsOut(view, startBounds)
+ }
+
+ @ShellDesktopThread
+ fun fadeBoundsOut(view: View, startBounds: Rect): VisualIndicatorAnimator {
val endBounds = getMinBounds(startBounds)
view.background.bounds = startBounds
val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
@@ -571,4 +657,9 @@ constructor(
}
}
}
+
+ private fun IndicatorType.isBubbleType(): Boolean {
+ return this == IndicatorType.TO_BUBBLE_LEFT_INDICATOR ||
+ this == IndicatorType.TO_BUBBLE_RIGHT_INDICATOR
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
index 3983bfbb2080..75f8d9e819cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.animation.AnimatorTestRule
import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
@@ -29,6 +30,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.ShellTestCase
@@ -43,6 +45,7 @@ import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
+import org.junit.Rule
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -67,6 +70,9 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class VisualIndicatorViewContainerTest : ShellTestCase() {
+
+ @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
+
@Mock private lateinit var view: View
@Mock private lateinit var displayLayout: DisplayLayout
@Mock private lateinit var displayController: DisplayController
@@ -297,6 +303,95 @@ class VisualIndicatorViewContainerTest : ShellTestCase() {
verify(spyViewContainer, never()).fadeInIndicatorInternal(any(), any(), any(), any())
}
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testCreateView_bubblesEnabled_indicatorIsFrameLayout() {
+ val spyViewContainer = setupSpyViewContainer()
+ assertThat(spyViewContainer.indicatorView).isInstanceOf(FrameLayout::class.java)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testFadeInOutBubbleIndicator_addAndRemoveBarIndicator() {
+ setUpBubbleBoundsProvider()
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.fadeInIndicator(
+ displayLayout,
+ DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR,
+ DEFAULT_DISPLAY,
+ )
+ desktopExecutor.flushAll()
+ animatorTestRule.advanceTimeBy(200)
+ assertThat((spyViewContainer.indicatorView as FrameLayout).getChildAt(0)).isNotNull()
+
+ spyViewContainer.fadeOutIndicator(
+ displayLayout,
+ DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR,
+ finishCallback = null,
+ DEFAULT_DISPLAY,
+ snapEventHandler,
+ )
+ desktopExecutor.flushAll()
+ animatorTestRule.advanceTimeBy(250)
+ assertThat((spyViewContainer.indicatorView as FrameLayout).getChildAt(0)).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testTransitionIndicator_fullscreenToBubble_addBarIndicator() {
+ setUpBubbleBoundsProvider()
+ val spyViewContainer = setupSpyViewContainer()
+
+ spyViewContainer.transitionIndicator(
+ taskInfo,
+ displayController,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR,
+ )
+ desktopExecutor.flushAll()
+ animatorTestRule.advanceTimeBy(200)
+
+ assertThat((spyViewContainer.indicatorView as FrameLayout).getChildAt(0)).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testTransitionIndicator_bubbleToFullscreen_removeBarIndicator() {
+ setUpBubbleBoundsProvider()
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.fadeInIndicator(
+ displayLayout,
+ DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR,
+ DEFAULT_DISPLAY,
+ )
+ desktopExecutor.flushAll()
+ animatorTestRule.advanceTimeBy(200)
+ assertThat((spyViewContainer.indicatorView as FrameLayout).getChildAt(0)).isNotNull()
+
+ spyViewContainer.transitionIndicator(
+ taskInfo,
+ displayController,
+ DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ )
+ desktopExecutor.flushAll()
+ animatorTestRule.advanceTimeBy(200)
+
+ assertThat((spyViewContainer.indicatorView as FrameLayout).getChildAt(0)).isNull()
+ }
+
private fun setupSpyViewContainer(): VisualIndicatorViewContainer {
val viewContainer =
VisualIndicatorViewContainer(
@@ -331,7 +426,22 @@ class VisualIndicatorViewContainerTest : ShellTestCase() {
.build()
}
+ private fun setUpBubbleBoundsProvider() {
+ bubbleDropTargetBoundsProvider =
+ object : BubbleDropTargetBoundsProvider {
+ override fun getBubbleBarExpandedViewDropTargetBounds(onLeft: Boolean): Rect {
+ return BUBBLE_INDICATOR_BOUNDS
+ }
+
+ override fun getBarDropTargetBounds(onLeft: Boolean): Rect {
+ return BAR_INDICATOR_BOUNDS
+ }
+ }
+ }
+
companion object {
private val DISPLAY_BOUNDS = Rect(0, 0, 1000, 1000)
+ private val BUBBLE_INDICATOR_BOUNDS = Rect(800, 200, 900, 900)
+ private val BAR_INDICATOR_BOUNDS = Rect(880, 950, 900, 960)
}
}