Loading libs/WindowManager/Shell/res/values/dimen.xml +11 −0 Original line number Diff line number Diff line Loading @@ -503,6 +503,17 @@ fullscreen if dragged until the top bound of the task is within the area. --> <dimen name="desktop_mode_transition_area_height">16dp</dimen> <!-- The width of the area where a desktop task will transition to fullscreen. --> <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen> <!-- The height of the area where a desktop task will transition to fullscreen. --> <dimen name="desktop_mode_fullscreen_from_desktop_height">40dp</dimen> <!-- The height on the screen where drag to the left or right edge will result in a desktop task snapping to split size. The empty space between this and the top is to allow for corner drags without transition. --> <dimen name="desktop_mode_split_from_desktop_height">100dp</dimen> <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) --> <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item> <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) --> Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +102 −11 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE; Loading @@ -28,11 +30,13 @@ import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.util.DisplayMetrics; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; Loading @@ -41,6 +45,8 @@ import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.view.animation.DecelerateInterpolator; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayController; Loading Loading @@ -93,28 +99,114 @@ public class DesktopModeVisualIndicator { /** * Based on the coordinates of the current drag event, determine which indicator type we should * display, including no visible indicator. * TODO(b/280828642): Update drag zones per starting windowing mode. */ IndicatorType updateIndicatorType(PointF inputCoordinates) { IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) { final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId); // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone. IndicatorType result = mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM ? IndicatorType.NO_INDICATOR : IndicatorType.TO_DESKTOP_INDICATOR; int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( IndicatorType result = IndicatorType.NO_INDICATOR; final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_area_width); if (inputCoordinates.y <= transitionAreaHeight) { // Because drags in freeform use task position for indicator calculation, we need to // account for the possibility of the task going off the top of the screen by captionHeight final int captionHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height); final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode, captionHeight); final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode, transitionAreaWidth, captionHeight); final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode, transitionAreaWidth, captionHeight); final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode, splitLeftRegion, splitRightRegion, fullscreenRegion); if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_FULLSCREEN_INDICATOR; } else if (inputCoordinates.x <= transitionAreaWidth) { } if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_SPLIT_LEFT_INDICATOR; } else if (inputCoordinates.x >= layout.width() - transitionAreaWidth) { } if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR; } if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_DESKTOP_INDICATOR; } transitionIndicator(result); return result; } @VisibleForTesting Region calculateFullscreenRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) { final Region region = new Region(); int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); // A thin, short Rect at the top of the screen. if (windowingMode == WINDOWING_MODE_FREEFORM) { int fromFreeformWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width); int fromFreeformHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height); region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2), -captionHeight, (layout.width() / 2) + (fromFreeformWidth / 2), fromFreeformHeight)); } // A screen-wide, shorter Rect if the task is in fullscreen or split. if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_MULTI_WINDOW) { region.union(new Rect(0, -captionHeight, layout.width(), edgeTransitionHeight)); } return region; } @VisibleForTesting Region calculateToDesktopRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, Region splitLeftRegion, Region splitRightRegion, Region toFullscreenRegion) { final Region region = new Region(); // If in desktop, we need no region. Otherwise it's the same for all windowing modes. if (windowingMode != WINDOWING_MODE_FREEFORM) { region.union(new Rect(0, 0, layout.width(), layout.height())); region.op(splitLeftRegion, Region.Op.DIFFERENCE); region.op(splitRightRegion, Region.Op.DIFFERENCE); region.op(toFullscreenRegion, Region.Op.DIFFERENCE); } return region; } @VisibleForTesting Region calculateSplitLeftRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int transitionEdgeWidth, int captionHeight) { final Region region = new Region(); // In freeform, keep the top corners clear. int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM ? mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) : -captionHeight; region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height())); return region; } @VisibleForTesting Region calculateSplitRightRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int transitionEdgeWidth, int captionHeight) { final Region region = new Region(); // In freeform, keep the top corners clear. int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM ? mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) : -captionHeight; region.union(new Rect(layout.width() - transitionEdgeWidth, transitionHeight, layout.width(), layout.height())); return region; } /** * Create a fullscreen indicator with no animation */ Loading Loading @@ -175,7 +267,6 @@ public class DesktopModeVisualIndicator { mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); mCurrentType = IndicatorType.NO_INDICATOR; } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +1 −1 Original line number Diff line number Diff line Loading @@ -930,7 +930,7 @@ class DesktopTasksController( } // Then, update the indicator type. val indicator = visualIndicator ?: return indicator.updateIndicatorType(PointF(inputX, taskTop)) indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode) } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +3 −3 Original line number Diff line number Diff line Loading @@ -728,7 +728,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTransitionDragActive = false; final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > 2 * statusBarHeight) { if (ev.getRawY() > 2 * statusBarHeight) { if (DesktopModeStatus.isEnabled()) { animateToDesktop(relevantDecor, ev); } Loading @@ -753,10 +753,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.ifPresent( c -> c.updateVisualIndicator( relevantDecor.mTaskInfo, relevantDecor.mTaskSurface, ev.getX(), ev.getY())); relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY())); final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { if (ev.getRawY() > statusBarHeight) { if (mMoveToDesktopAnimator == null) { mMoveToDesktopAnimator = new MoveToDesktopAnimator( mContext, mDragToDesktopAnimationStartBounds, Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.graphics.Rect import android.graphics.Region import android.testing.AndroidTestingRunner import android.view.SurfaceControl import androidx.test.filters.SmallTest import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopModeVisualIndicatorTest : ShellTestCase() { @Mock private lateinit var taskInfo: RunningTaskInfo @Mock private lateinit var syncQueue: SyncTransactionQueue @Mock private lateinit var displayController: DisplayController @Mock private lateinit var taskSurface: SurfaceControl @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var displayLayout: DisplayLayout private lateinit var visualIndicator: DesktopModeVisualIndicator @Before fun setUp() { visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, taskDisplayAreaOrganizer) whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) } @Test fun testFullscreenRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_transition_area_height) val fromFreeformWidth = mContext.resources.getDimensionPixelSize( R.dimen.desktop_mode_fullscreen_from_desktop_width ) val fromFreeformHeight = mContext.resources.getDimensionPixelSize( R.dimen.desktop_mode_fullscreen_from_desktop_height ) var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect( DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2, -50, DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2, fromFreeformHeight)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) } @Test fun testSplitLeftRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_split_from_desktop_height) var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600)) testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) } @Test fun testSplitRightRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_split_from_desktop_height) var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600)) testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) } @Test fun testToDesktopRegionCalculation() { val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT) val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion) var testRegion = Region() testRegion.union(DISPLAY_BOUNDS) testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE) testRegion.op(splitRightRegion, Region.Op.DIFFERENCE) testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE) assertThat(desktopRegion).isEqualTo(testRegion) } companion object { private const val TRANSITION_AREA_WIDTH = 32 private const val CAPTION_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) } } No newline at end of file Loading
libs/WindowManager/Shell/res/values/dimen.xml +11 −0 Original line number Diff line number Diff line Loading @@ -503,6 +503,17 @@ fullscreen if dragged until the top bound of the task is within the area. --> <dimen name="desktop_mode_transition_area_height">16dp</dimen> <!-- The width of the area where a desktop task will transition to fullscreen. --> <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen> <!-- The height of the area where a desktop task will transition to fullscreen. --> <dimen name="desktop_mode_fullscreen_from_desktop_height">40dp</dimen> <!-- The height on the screen where drag to the left or right edge will result in a desktop task snapping to split size. The empty space between this and the top is to allow for corner drags without transition. --> <dimen name="desktop_mode_split_from_desktop_height">100dp</dimen> <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) --> <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item> <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) --> Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +102 −11 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE; Loading @@ -28,11 +30,13 @@ import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.util.DisplayMetrics; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; Loading @@ -41,6 +45,8 @@ import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.view.animation.DecelerateInterpolator; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayController; Loading Loading @@ -93,28 +99,114 @@ public class DesktopModeVisualIndicator { /** * Based on the coordinates of the current drag event, determine which indicator type we should * display, including no visible indicator. * TODO(b/280828642): Update drag zones per starting windowing mode. */ IndicatorType updateIndicatorType(PointF inputCoordinates) { IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) { final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId); // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone. IndicatorType result = mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM ? IndicatorType.NO_INDICATOR : IndicatorType.TO_DESKTOP_INDICATOR; int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( IndicatorType result = IndicatorType.NO_INDICATOR; final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_area_width); if (inputCoordinates.y <= transitionAreaHeight) { // Because drags in freeform use task position for indicator calculation, we need to // account for the possibility of the task going off the top of the screen by captionHeight final int captionHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height); final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode, captionHeight); final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode, transitionAreaWidth, captionHeight); final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode, transitionAreaWidth, captionHeight); final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode, splitLeftRegion, splitRightRegion, fullscreenRegion); if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_FULLSCREEN_INDICATOR; } else if (inputCoordinates.x <= transitionAreaWidth) { } if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_SPLIT_LEFT_INDICATOR; } else if (inputCoordinates.x >= layout.width() - transitionAreaWidth) { } if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR; } if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_DESKTOP_INDICATOR; } transitionIndicator(result); return result; } @VisibleForTesting Region calculateFullscreenRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) { final Region region = new Region(); int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); // A thin, short Rect at the top of the screen. if (windowingMode == WINDOWING_MODE_FREEFORM) { int fromFreeformWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width); int fromFreeformHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height); region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2), -captionHeight, (layout.width() / 2) + (fromFreeformWidth / 2), fromFreeformHeight)); } // A screen-wide, shorter Rect if the task is in fullscreen or split. if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_MULTI_WINDOW) { region.union(new Rect(0, -captionHeight, layout.width(), edgeTransitionHeight)); } return region; } @VisibleForTesting Region calculateToDesktopRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, Region splitLeftRegion, Region splitRightRegion, Region toFullscreenRegion) { final Region region = new Region(); // If in desktop, we need no region. Otherwise it's the same for all windowing modes. if (windowingMode != WINDOWING_MODE_FREEFORM) { region.union(new Rect(0, 0, layout.width(), layout.height())); region.op(splitLeftRegion, Region.Op.DIFFERENCE); region.op(splitRightRegion, Region.Op.DIFFERENCE); region.op(toFullscreenRegion, Region.Op.DIFFERENCE); } return region; } @VisibleForTesting Region calculateSplitLeftRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int transitionEdgeWidth, int captionHeight) { final Region region = new Region(); // In freeform, keep the top corners clear. int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM ? mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) : -captionHeight; region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height())); return region; } @VisibleForTesting Region calculateSplitRightRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int transitionEdgeWidth, int captionHeight) { final Region region = new Region(); // In freeform, keep the top corners clear. int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM ? mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) : -captionHeight; region.union(new Rect(layout.width() - transitionEdgeWidth, transitionHeight, layout.width(), layout.height())); return region; } /** * Create a fullscreen indicator with no animation */ Loading Loading @@ -175,7 +267,6 @@ public class DesktopModeVisualIndicator { mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); mCurrentType = IndicatorType.NO_INDICATOR; } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +1 −1 Original line number Diff line number Diff line Loading @@ -930,7 +930,7 @@ class DesktopTasksController( } // Then, update the indicator type. val indicator = visualIndicator ?: return indicator.updateIndicatorType(PointF(inputX, taskTop)) indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode) } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +3 −3 Original line number Diff line number Diff line Loading @@ -728,7 +728,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTransitionDragActive = false; final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > 2 * statusBarHeight) { if (ev.getRawY() > 2 * statusBarHeight) { if (DesktopModeStatus.isEnabled()) { animateToDesktop(relevantDecor, ev); } Loading @@ -753,10 +753,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.ifPresent( c -> c.updateVisualIndicator( relevantDecor.mTaskInfo, relevantDecor.mTaskSurface, ev.getX(), ev.getY())); relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY())); final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { if (ev.getRawY() > statusBarHeight) { if (mMoveToDesktopAnimator == null) { mMoveToDesktopAnimator = new MoveToDesktopAnimator( mContext, mDragToDesktopAnimationStartBounds, Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.graphics.Rect import android.graphics.Region import android.testing.AndroidTestingRunner import android.view.SurfaceControl import androidx.test.filters.SmallTest import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopModeVisualIndicatorTest : ShellTestCase() { @Mock private lateinit var taskInfo: RunningTaskInfo @Mock private lateinit var syncQueue: SyncTransactionQueue @Mock private lateinit var displayController: DisplayController @Mock private lateinit var taskSurface: SurfaceControl @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var displayLayout: DisplayLayout private lateinit var visualIndicator: DesktopModeVisualIndicator @Before fun setUp() { visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, taskDisplayAreaOrganizer) whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) } @Test fun testFullscreenRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_transition_area_height) val fromFreeformWidth = mContext.resources.getDimensionPixelSize( R.dimen.desktop_mode_fullscreen_from_desktop_width ) val fromFreeformHeight = mContext.resources.getDimensionPixelSize( R.dimen.desktop_mode_fullscreen_from_desktop_height ) var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect( DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2, -50, DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2, fromFreeformHeight)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) } @Test fun testSplitLeftRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_split_from_desktop_height) var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600)) testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) } @Test fun testSplitRightRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_split_from_desktop_height) var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600)) testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) } @Test fun testToDesktopRegionCalculation() { val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT) val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion) var testRegion = Region() testRegion.union(DISPLAY_BOUNDS) testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE) testRegion.op(splitRightRegion, Region.Op.DIFFERENCE) testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE) assertThat(desktopRegion).isEqualTo(testRegion) } companion object { private const val TRANSITION_AREA_WIDTH = 32 private const val CAPTION_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) } } No newline at end of file