diff options
author | 2025-03-14 14:38:44 -0700 | |
---|---|---|
committer | 2025-03-14 14:38:44 -0700 | |
commit | 4302e6ec866d7b0e3bc615e405c212b3c30ab4fd (patch) | |
tree | 9fe63f6349ae3de1f2cbe1f11f392d26954fb35d | |
parent | 5d93dc54cb2e152935a2b6b46f67bf0fbdb08d2b (diff) | |
parent | 0f4c171019b7365f993e90c1a532ed69ca86f779 (diff) |
Merge "Include bar outline for bubble drag indicators" into main
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) } } |