Loading libs/WindowManager/Shell/res/values/dimen.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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 --> Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt +5 −0 Original line number Diff line number Diff line Loading @@ -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 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +20 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt +110 −19 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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") Loading Loading @@ -183,10 +194,10 @@ constructor( ) } else { val animStartType = IndicatorType.valueOf(currentType.name) val animator = indicatorView?.let { val indicator = indicatorView ?: return@execute var animator: Animator = VisualIndicatorAnimator.animateIndicatorType( it, indicator, layout, animStartType, newType, Loading @@ -194,12 +205,39 @@ constructor( taskInfo.displayId, snapEventHandler, ) } ?: return@execute 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. * Loading @@ -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() } } Loading @@ -259,7 +300,7 @@ constructor( desktopExecutor.execute { indicatorView?.let { val animStartType = IndicatorType.valueOf(currentType.name) val animator = var animator: Animator = VisualIndicatorAnimator.fadeBoundsOut( it, animStartType, Loading @@ -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) { Loading Loading @@ -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. Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -571,4 +657,9 @@ constructor( } } } private fun IndicatorType.isBubbleType(): Boolean { return this == IndicatorType.TO_BUBBLE_LEFT_INDICATOR || this == IndicatorType.TO_BUBBLE_RIGHT_INDICATOR } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt +110 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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( Loading Loading @@ -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) } } Loading
libs/WindowManager/Shell/res/values/dimen.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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 --> Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleDropTargetBoundsProvider.kt +5 −0 Original line number Diff line number Diff line Loading @@ -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
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +20 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt +110 −19 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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") Loading Loading @@ -183,10 +194,10 @@ constructor( ) } else { val animStartType = IndicatorType.valueOf(currentType.name) val animator = indicatorView?.let { val indicator = indicatorView ?: return@execute var animator: Animator = VisualIndicatorAnimator.animateIndicatorType( it, indicator, layout, animStartType, newType, Loading @@ -194,12 +205,39 @@ constructor( taskInfo.displayId, snapEventHandler, ) } ?: return@execute 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. * Loading @@ -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() } } Loading @@ -259,7 +300,7 @@ constructor( desktopExecutor.execute { indicatorView?.let { val animStartType = IndicatorType.valueOf(currentType.name) val animator = var animator: Animator = VisualIndicatorAnimator.fadeBoundsOut( it, animStartType, Loading @@ -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) { Loading Loading @@ -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. Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -571,4 +657,9 @@ constructor( } } } private fun IndicatorType.isBubbleType(): Boolean { return this == IndicatorType.TO_BUBBLE_LEFT_INDICATOR || this == IndicatorType.TO_BUBBLE_RIGHT_INDICATOR } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt +110 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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( Loading Loading @@ -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) } }