Loading core/java/android/view/DragEvent.java +2 −0 Original line number Diff line number Diff line Loading @@ -596,6 +596,7 @@ public class DragEvent implements Parcelable { dest.writeFloat(mY); dest.writeFloat(mOffsetX); dest.writeFloat(mOffsetY); dest.writeInt(mDisplayId); dest.writeInt(mFlags); dest.writeInt(mDragResult ? 1 : 0); if (mClipData == null) { Loading Loading @@ -636,6 +637,7 @@ public class DragEvent implements Parcelable { event.mY = in.readFloat(); event.mOffsetX = in.readFloat(); event.mOffsetY = in.readFloat(); event.mDisplayId = in.readInt(); event.mFlags = in.readInt(); event.mDragResult = (in.readInt() != 0); if (in.readInt() != 0) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt +4 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.graphics.Rect import android.os.IBinder import android.view.DragEvent Loading Loading @@ -108,7 +109,9 @@ class DesktopModeDragAndDropTransitionHandler( } private fun isValidTaskChange(change: TransitionInfo.Change): Boolean = change.taskInfo != null && change.taskInfo?.taskId != -1 change.taskInfo != null && change.taskInfo?.taskId != -1 && change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM private fun extractBounds(dragEvent: DragEvent): Rect { return Rect( Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +30 −20 Original line number Diff line number Diff line Loading @@ -405,7 +405,7 @@ class DesktopTasksController( * TODO(b/405381458): use focusTransitionObserver to get the focused task on a certain display */ fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) { val allFocusedTasks = getAllFocusedTasks(displayId) val allFocusedTasks = getFocusedNonDesktopTasks(displayId) when (allFocusedTasks.size) { 0 -> return // Full screen case Loading Loading @@ -447,7 +447,7 @@ class DesktopTasksController( * Returns all focused tasks in full screen or split screen mode in [displayId] when it is not * the home activity. */ private fun getAllFocusedTasks(displayId: Int): List<RunningTaskInfo> = private fun getFocusedNonDesktopTasks(displayId: Int): List<RunningTaskInfo> = shellTaskOrganizer.getRunningTasks(displayId).filter { it.isFocused && (it.windowingMode == WINDOWING_MODE_FULLSCREEN || Loading Loading @@ -2008,8 +2008,8 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds, ): Boolean = getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds || getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds getSnapBounds(taskInfo.displayId, SnapPosition.LEFT) == taskBounds || getSnapBounds(taskInfo.displayId, SnapPosition.RIGHT) == taskBounds @VisibleForTesting fun doesAnyTaskRequireTaskbarRounding(displayId: Int, excludeTaskId: Int? = null): Boolean { Loading Loading @@ -2065,7 +2065,7 @@ class DesktopTasksController( displayController, ) val destinationBounds = getSnapBounds(taskInfo, position) val destinationBounds = getSnapBounds(taskInfo.displayId, position) desktopModeEventLogger.logTaskResizingEnded( resizeTrigger, inputMethod, Loading Loading @@ -2196,8 +2196,8 @@ class DesktopTasksController( private fun isSnapResizingAllowed(taskInfo: RunningTaskInfo) = taskInfo.isResizeable || !DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue() private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect() private fun getSnapBounds(displayId: Int, position: SnapPosition): Rect { val displayLayout = displayController.getDisplayLayout(displayId) ?: return Rect() val stableBounds = Rect().also { displayLayout.getStableBounds(it) } Loading Loading @@ -4475,13 +4475,16 @@ class DesktopTasksController( dragEvent: DragEvent, onFinishCallback: Consumer<Boolean>, ): Boolean { // TODO(b/320797628): Pass through which display we are dropping onto if (!isAnyDeskActive(DEFAULT_DISPLAY)) { // Not currently in desktop mode, ignore the drop val destinationDisplay = dragEvent.displayId if ( !desktopState.isDesktopModeSupportedOnDisplay(destinationDisplay) || getFocusedNonDesktopTasks(destinationDisplay).isNotEmpty() ) { // Destination display does not support desktop or has focused // non-freeform task; ignore the drop. return false } // TODO: val launchComponent = getComponent(launchIntent) if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) { // TODO(b/320797628): Should only return early if there is an existing running task, and Loading @@ -4489,7 +4492,9 @@ class DesktopTasksController( logV("Dropped intent does not support multi-instance") return false } val taskInfo = getFocusedFreeformTask(DEFAULT_DISPLAY) ?: return false val taskInfo = shellTaskOrganizer.getRunningTaskInfo(focusTransitionObserver.globallyFocusedTaskId) ?: return false // TODO(b/358114479): Update drag and drop handling to give us visibility into when another // window will accept a drag event. This way, we can hide the indicator when we won't // be handling the transition here, allowing us to display the indicator accurately. Loading @@ -4498,7 +4503,7 @@ class DesktopTasksController( updateVisualIndicator( taskInfo, dragEvent.dragSurface, dragEvent.displayId, destinationDisplay, dragEvent.x, dragEvent.y, DragStartState.DRAGGED_INTENT, Loading @@ -4509,17 +4514,21 @@ class DesktopTasksController( IndicatorType.TO_FULLSCREEN_INDICATOR -> { WINDOWING_MODE_FULLSCREEN } // NO_INDICATOR can result from a cross-display drag. IndicatorType.TO_SPLIT_LEFT_INDICATOR, IndicatorType.TO_SPLIT_RIGHT_INDICATOR, IndicatorType.TO_DESKTOP_INDICATOR -> { IndicatorType.TO_DESKTOP_INDICATOR, IndicatorType.NO_INDICATOR -> { WINDOWING_MODE_FREEFORM } else -> error("Invalid indicator type: $indicatorType") } val displayLayout = displayController.getDisplayLayout(DEFAULT_DISPLAY) ?: return false val displayLayout = displayController.getDisplayLayout(destinationDisplay) ?: return false val newWindowBounds = Rect() when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { IndicatorType.TO_DESKTOP_INDICATOR, IndicatorType.NO_INDICATOR -> { // Use default bounds, but with the top-center at the drop point. newWindowBounds.set(calculateDefaultDesktopTaskBounds(displayLayout)) newWindowBounds.offsetTo( Loading @@ -4528,10 +4537,10 @@ class DesktopTasksController( ) } IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { newWindowBounds.set(getSnapBounds(taskInfo, SnapPosition.RIGHT)) newWindowBounds.set(getSnapBounds(destinationDisplay, SnapPosition.RIGHT)) } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { newWindowBounds.set(getSnapBounds(taskInfo, SnapPosition.LEFT)) newWindowBounds.set(getSnapBounds(destinationDisplay, SnapPosition.LEFT)) } else -> { // Use empty bounds for the fullscreen case. Loading @@ -4547,6 +4556,7 @@ class DesktopTasksController( pendingIntentLaunchFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK splashScreenStyle = SPLASH_SCREEN_STYLE_ICON launchDisplayId = destinationDisplay } if (windowingMode == WINDOWING_MODE_FULLSCREEN) { dragAndDropFullscreenCookie = Binder() Loading @@ -4559,13 +4569,13 @@ class DesktopTasksController( DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue || DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue ) { val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false val deskId = getOrCreateDefaultDeskId(destinationDisplay) ?: return false startLaunchTransition( TRANSIT_OPEN, wct, launchingTaskId = null, deskId = deskId, displayId = DEFAULT_DISPLAY, displayId = destinationDisplay, dragEvent = dragEvent, ) } else { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +24 −1 Original line number Diff line number Diff line Loading @@ -9199,6 +9199,25 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION, ) fun onUnhandledDrag_crossDisplayDrag() { taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2) testOnUnhandledDrag( DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR, PointF(1200f, 700f), Rect(240, 700, 2160, 1900), tabTearingMinimizeAnimationFlagEnabled = true, tabTearingLaunchAnimationFlagEnabled = true, destinationDisplayId = SECOND_DISPLAY, ) } @Test fun shellController_registersUserChangeListener() { verify(shellController, times(2)).addUserChangeListener(any()) Loading Loading @@ -10165,7 +10184,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() expectedBounds: Rect, tabTearingMinimizeAnimationFlagEnabled: Boolean, tabTearingLaunchAnimationFlagEnabled: Boolean, destinationDisplayId: Int = DEFAULT_DISPLAY, ) { desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true desktopState.overrideDesktopModeSupportPerDisplay[destinationDisplayId] = true setUpLandscapeDisplay() val task = setUpFreeformTask() markTaskVisible(task) Loading @@ -10179,10 +10201,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val b = SurfaceControl.Builder() b.setName("test surface") val dragSurface = b.build() whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks) whenever(focusTransitionObserver.globallyFocusedTaskId).thenReturn(task.taskId) whenever(mockDragEvent.dragSurface).thenReturn(dragSurface) whenever(mockDragEvent.x).thenReturn(inputCoordinate.x) whenever(mockDragEvent.y).thenReturn(inputCoordinate.y) whenever(mockDragEvent.displayId).thenReturn(destinationDisplayId) whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull(), anyInt())) .thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) Loading Loading
core/java/android/view/DragEvent.java +2 −0 Original line number Diff line number Diff line Loading @@ -596,6 +596,7 @@ public class DragEvent implements Parcelable { dest.writeFloat(mY); dest.writeFloat(mOffsetX); dest.writeFloat(mOffsetY); dest.writeInt(mDisplayId); dest.writeInt(mFlags); dest.writeInt(mDragResult ? 1 : 0); if (mClipData == null) { Loading Loading @@ -636,6 +637,7 @@ public class DragEvent implements Parcelable { event.mY = in.readFloat(); event.mOffsetX = in.readFloat(); event.mOffsetY = in.readFloat(); event.mDisplayId = in.readInt(); event.mFlags = in.readInt(); event.mDragResult = (in.readInt() != 0); if (in.readInt() != 0) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt +4 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.graphics.Rect import android.os.IBinder import android.view.DragEvent Loading Loading @@ -108,7 +109,9 @@ class DesktopModeDragAndDropTransitionHandler( } private fun isValidTaskChange(change: TransitionInfo.Change): Boolean = change.taskInfo != null && change.taskInfo?.taskId != -1 change.taskInfo != null && change.taskInfo?.taskId != -1 && change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM private fun extractBounds(dragEvent: DragEvent): Rect { return Rect( Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +30 −20 Original line number Diff line number Diff line Loading @@ -405,7 +405,7 @@ class DesktopTasksController( * TODO(b/405381458): use focusTransitionObserver to get the focused task on a certain display */ fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) { val allFocusedTasks = getAllFocusedTasks(displayId) val allFocusedTasks = getFocusedNonDesktopTasks(displayId) when (allFocusedTasks.size) { 0 -> return // Full screen case Loading Loading @@ -447,7 +447,7 @@ class DesktopTasksController( * Returns all focused tasks in full screen or split screen mode in [displayId] when it is not * the home activity. */ private fun getAllFocusedTasks(displayId: Int): List<RunningTaskInfo> = private fun getFocusedNonDesktopTasks(displayId: Int): List<RunningTaskInfo> = shellTaskOrganizer.getRunningTasks(displayId).filter { it.isFocused && (it.windowingMode == WINDOWING_MODE_FULLSCREEN || Loading Loading @@ -2008,8 +2008,8 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds, ): Boolean = getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds || getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds getSnapBounds(taskInfo.displayId, SnapPosition.LEFT) == taskBounds || getSnapBounds(taskInfo.displayId, SnapPosition.RIGHT) == taskBounds @VisibleForTesting fun doesAnyTaskRequireTaskbarRounding(displayId: Int, excludeTaskId: Int? = null): Boolean { Loading Loading @@ -2065,7 +2065,7 @@ class DesktopTasksController( displayController, ) val destinationBounds = getSnapBounds(taskInfo, position) val destinationBounds = getSnapBounds(taskInfo.displayId, position) desktopModeEventLogger.logTaskResizingEnded( resizeTrigger, inputMethod, Loading Loading @@ -2196,8 +2196,8 @@ class DesktopTasksController( private fun isSnapResizingAllowed(taskInfo: RunningTaskInfo) = taskInfo.isResizeable || !DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue() private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect() private fun getSnapBounds(displayId: Int, position: SnapPosition): Rect { val displayLayout = displayController.getDisplayLayout(displayId) ?: return Rect() val stableBounds = Rect().also { displayLayout.getStableBounds(it) } Loading Loading @@ -4475,13 +4475,16 @@ class DesktopTasksController( dragEvent: DragEvent, onFinishCallback: Consumer<Boolean>, ): Boolean { // TODO(b/320797628): Pass through which display we are dropping onto if (!isAnyDeskActive(DEFAULT_DISPLAY)) { // Not currently in desktop mode, ignore the drop val destinationDisplay = dragEvent.displayId if ( !desktopState.isDesktopModeSupportedOnDisplay(destinationDisplay) || getFocusedNonDesktopTasks(destinationDisplay).isNotEmpty() ) { // Destination display does not support desktop or has focused // non-freeform task; ignore the drop. return false } // TODO: val launchComponent = getComponent(launchIntent) if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) { // TODO(b/320797628): Should only return early if there is an existing running task, and Loading @@ -4489,7 +4492,9 @@ class DesktopTasksController( logV("Dropped intent does not support multi-instance") return false } val taskInfo = getFocusedFreeformTask(DEFAULT_DISPLAY) ?: return false val taskInfo = shellTaskOrganizer.getRunningTaskInfo(focusTransitionObserver.globallyFocusedTaskId) ?: return false // TODO(b/358114479): Update drag and drop handling to give us visibility into when another // window will accept a drag event. This way, we can hide the indicator when we won't // be handling the transition here, allowing us to display the indicator accurately. Loading @@ -4498,7 +4503,7 @@ class DesktopTasksController( updateVisualIndicator( taskInfo, dragEvent.dragSurface, dragEvent.displayId, destinationDisplay, dragEvent.x, dragEvent.y, DragStartState.DRAGGED_INTENT, Loading @@ -4509,17 +4514,21 @@ class DesktopTasksController( IndicatorType.TO_FULLSCREEN_INDICATOR -> { WINDOWING_MODE_FULLSCREEN } // NO_INDICATOR can result from a cross-display drag. IndicatorType.TO_SPLIT_LEFT_INDICATOR, IndicatorType.TO_SPLIT_RIGHT_INDICATOR, IndicatorType.TO_DESKTOP_INDICATOR -> { IndicatorType.TO_DESKTOP_INDICATOR, IndicatorType.NO_INDICATOR -> { WINDOWING_MODE_FREEFORM } else -> error("Invalid indicator type: $indicatorType") } val displayLayout = displayController.getDisplayLayout(DEFAULT_DISPLAY) ?: return false val displayLayout = displayController.getDisplayLayout(destinationDisplay) ?: return false val newWindowBounds = Rect() when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { IndicatorType.TO_DESKTOP_INDICATOR, IndicatorType.NO_INDICATOR -> { // Use default bounds, but with the top-center at the drop point. newWindowBounds.set(calculateDefaultDesktopTaskBounds(displayLayout)) newWindowBounds.offsetTo( Loading @@ -4528,10 +4537,10 @@ class DesktopTasksController( ) } IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { newWindowBounds.set(getSnapBounds(taskInfo, SnapPosition.RIGHT)) newWindowBounds.set(getSnapBounds(destinationDisplay, SnapPosition.RIGHT)) } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { newWindowBounds.set(getSnapBounds(taskInfo, SnapPosition.LEFT)) newWindowBounds.set(getSnapBounds(destinationDisplay, SnapPosition.LEFT)) } else -> { // Use empty bounds for the fullscreen case. Loading @@ -4547,6 +4556,7 @@ class DesktopTasksController( pendingIntentLaunchFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK splashScreenStyle = SPLASH_SCREEN_STYLE_ICON launchDisplayId = destinationDisplay } if (windowingMode == WINDOWING_MODE_FULLSCREEN) { dragAndDropFullscreenCookie = Binder() Loading @@ -4559,13 +4569,13 @@ class DesktopTasksController( DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue || DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue ) { val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false val deskId = getOrCreateDefaultDeskId(destinationDisplay) ?: return false startLaunchTransition( TRANSIT_OPEN, wct, launchingTaskId = null, deskId = deskId, displayId = DEFAULT_DISPLAY, displayId = destinationDisplay, dragEvent = dragEvent, ) } else { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +24 −1 Original line number Diff line number Diff line Loading @@ -9199,6 +9199,25 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION, ) fun onUnhandledDrag_crossDisplayDrag() { taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2) testOnUnhandledDrag( DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR, PointF(1200f, 700f), Rect(240, 700, 2160, 1900), tabTearingMinimizeAnimationFlagEnabled = true, tabTearingLaunchAnimationFlagEnabled = true, destinationDisplayId = SECOND_DISPLAY, ) } @Test fun shellController_registersUserChangeListener() { verify(shellController, times(2)).addUserChangeListener(any()) Loading Loading @@ -10165,7 +10184,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() expectedBounds: Rect, tabTearingMinimizeAnimationFlagEnabled: Boolean, tabTearingLaunchAnimationFlagEnabled: Boolean, destinationDisplayId: Int = DEFAULT_DISPLAY, ) { desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true desktopState.overrideDesktopModeSupportPerDisplay[destinationDisplayId] = true setUpLandscapeDisplay() val task = setUpFreeformTask() markTaskVisible(task) Loading @@ -10179,10 +10201,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val b = SurfaceControl.Builder() b.setName("test surface") val dragSurface = b.build() whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks) whenever(focusTransitionObserver.globallyFocusedTaskId).thenReturn(task.taskId) whenever(mockDragEvent.dragSurface).thenReturn(dragSurface) whenever(mockDragEvent.x).thenReturn(inputCoordinate.x) whenever(mockDragEvent.y).thenReturn(inputCoordinate.y) whenever(mockDragEvent.displayId).thenReturn(destinationDisplayId) whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull(), anyInt())) .thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) Loading