diff options
12 files changed, 354 insertions, 31 deletions
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e5ef95c603a1..125a0b242df9 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -31,6 +31,13 @@ flag { } flag { + name: "disable_non_resizable_app_snap_resizing" + namespace: "lse_desktop_experience" + description: "Stops non-resizable app desktop windows from being snap resized" + bug: "325240072" +} + +flag { name: "enable_desktop_windowing_task_limit" namespace: "lse_desktop_experience" description: "Enables a limit on the number of Tasks shown in Desktop Mode" diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt index 6d63971b0b3e..434885f1089d 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt @@ -43,6 +43,7 @@ enum class DesktopModeFlags( APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true), TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true), + DISABLE_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true), DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true), ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true), BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 9cfbde4bad41..a18bbadbde69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -67,6 +67,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver; import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; +import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.GlobalDragListener; @@ -538,6 +539,7 @@ public abstract class WMShellModule { DragAndDropController dragAndDropController, Transitions transitions, KeyguardManager keyguardManager, + ReturnToDragStartAnimator returnToDragStartAnimator, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, @@ -553,13 +555,13 @@ public abstract class WMShellModule { InteractionJankMonitor interactionJankMonitor) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, - dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler, + dragAndDropController, transitions, keyguardManager, + returnToDragStartAnimator, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, - recentsTransitionHandler, multiInstanceHelper, - mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null), - interactionJankMonitor); + recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, + recentTasksController.orElse(null), interactionJankMonitor); } @WMSingleton @@ -587,6 +589,13 @@ public abstract class WMShellModule { ); } + @WMSingleton + @Provides + static ReturnToDragStartAnimator provideReturnToDragStartAnimator( + InteractionJankMonitor interactionJankMonitor) { + return new ReturnToDragStartAnimator(interactionJankMonitor); + } + @WMSingleton @Provides diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index d1f557a5175e..e154da58028a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -66,7 +66,6 @@ import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -79,13 +78,14 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.annotations.ExternalThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopModeFlags +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity -import com.android.wm.shell.shared.TransitionUtil -import com.android.wm.shell.shared.annotations.ExternalThread -import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE import com.android.wm.shell.sysui.ShellCommandHandler @@ -96,6 +96,7 @@ import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator +import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow @@ -117,6 +118,7 @@ class DesktopTasksController( private val dragAndDropController: DragAndDropController, private val transitions: Transitions, private val keyguardManager: KeyguardManager, + private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, @@ -214,6 +216,10 @@ class DesktopTasksController( dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener) } + fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { + returnToDragStartAnimator.setTaskRepositionAnimationListener(listener) + } + /** Setter needed to avoid cyclic dependency. */ fun setSplitScreenController(controller: SplitScreenController) { splitScreenController = controller @@ -661,6 +667,28 @@ class DesktopTasksController( } } + @VisibleForTesting + fun handleSnapResizingTask( + taskInfo: RunningTaskInfo, + position: SnapPosition, + taskSurface: SurfaceControl, + currentDragBounds: Rect, + dragStartBounds: Rect + ) { + releaseVisualIndicator() + if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) { + // reposition non-resizable app back to its original position before being dragged + returnToDragStartAnimator.start( + taskInfo.taskId, + taskSurface, + startBounds = currentDragBounds, + endBounds = dragStartBounds + ) + } else { + snapToHalfScreen(taskInfo, currentDragBounds, position) + } + } + private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect { // TODO(b/319819547): Account for app constraints so apps do not become letterboxed val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt() @@ -1293,18 +1321,22 @@ class DesktopTasksController( * that change. Otherwise, ensure bounds are up to date. * * @param taskInfo the task being dragged. + * @param taskSurface the leash of the task being dragged. * @param position position of surface when drag ends. * @param inputCoordinate the coordinates of the motion event * @param currentDragBounds the current bounds of where the visible task is (might be actual * task bounds or just task leash) * @param validDragArea the bounds of where the task can be dragged within the display. + * @param dragStartBounds the bounds of the task before starting dragging. */ fun onDragPositioningEnd( taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, position: Point, inputCoordinate: PointF, currentDragBounds: Rect, - validDragArea: Rect + validDragArea: Rect, + dragStartBounds: Rect, ) { if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) { return @@ -1325,12 +1357,14 @@ class DesktopTasksController( ) } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { - releaseVisualIndicator() - snapToHalfScreen(taskInfo, currentDragBounds, SnapPosition.LEFT) + handleSnapResizingTask( + taskInfo, SnapPosition.LEFT, taskSurface, currentDragBounds, dragStartBounds + ) } IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { - releaseVisualIndicator() - snapToHalfScreen(taskInfo, currentDragBounds, SnapPosition.RIGHT) + handleSnapResizingTask( + taskInfo, SnapPosition.RIGHT, taskSurface, currentDragBounds, dragStartBounds + ) } IndicatorType.NO_INDICATOR -> { // If task bounds are outside valid drag area, snap them inward @@ -1339,6 +1373,8 @@ class DesktopTasksController( validDragArea ) + if (currentDragBounds == dragStartBounds) return + // Update task bounds so that the task position will match the position of its leash val wct = WindowContainerTransaction() wct.setBounds(taskInfo.token, currentDragBounds) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt new file mode 100644 index 000000000000..be67a40560aa --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt @@ -0,0 +1,96 @@ +/* + * 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.animation.Animator +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.graphics.Rect +import android.view.SurfaceControl +import androidx.core.animation.addListener +import com.android.internal.jank.InteractionJankMonitor +import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener +import java.util.function.Supplier + +/** Animates the task surface moving from its current drag position to its pre-drag position. */ +class ReturnToDragStartAnimator( + private val transactionSupplier: Supplier<SurfaceControl.Transaction>, + private val interactionJankMonitor: InteractionJankMonitor +) { + private var boundsAnimator: Animator? = null + private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener + + constructor(interactionJankMonitor: InteractionJankMonitor) : + this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor) + + /** Sets a listener for the start and end of the reposition animation. */ + fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { + taskRepositionAnimationListener = listener + } + + /** Builds new animator and starts animation of task leash reposition. */ + fun start(taskId: Int, taskSurface: SurfaceControl, startBounds: Rect, endBounds: Rect) { + val tx = transactionSupplier.get() + + boundsAnimator?.cancel() + boundsAnimator = + ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds) + .setDuration(RETURN_TO_DRAG_START_ANIMATION_MS) + .apply { + addListener( + onStart = { + val startTransaction = transactionSupplier.get() + startTransaction + .setPosition( + taskSurface, + startBounds.left.toFloat(), + startBounds.top.toFloat() + ) + .show(taskSurface) + .apply() + taskRepositionAnimationListener.onAnimationStart(taskId) + }, + onEnd = { + val finishTransaction = transactionSupplier.get() + finishTransaction + .setPosition( + taskSurface, + endBounds.left.toFloat(), + endBounds.top.toFloat() + ) + .show(taskSurface) + .apply() + taskRepositionAnimationListener.onAnimationEnd(taskId) + boundsAnimator = null + // TODO(b/354658237) - show toast with relevant string + // TODO(b/339582583) - add Jank CUJ using interactionJankMonitor + } + ) + addUpdateListener { anim -> + val rect = anim.animatedValue as Rect + tx.setPosition(taskSurface, rect.left.toFloat(), rect.top.toFloat()) + .show(taskSurface) + .apply() + } + } + .also(ValueAnimator::start) + } + + companion object { + const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index d68c018267a6..2e98836b2ded 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -325,6 +325,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new DesktopModeOnInsetsChangedListener()); mDesktopTasksController.setOnTaskResizeAnimationListener( new DesktopModeOnTaskResizeAnimationListener()); + mDesktopTasksController.setOnTaskRepositionAnimationListener( + new DesktopModeOnTaskRepositionAnimationListener()); mDisplayController.addDisplayChangingController(mOnDisplayChangingListener); try { mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener, @@ -464,9 +466,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) { return; } - mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo, - decoration.mTaskInfo.configuration.windowConfiguration.getBounds(), - left ? SnapPosition.LEFT : SnapPosition.RIGHT); + + if (!decoration.mTaskInfo.isResizeable + && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(mContext)) { + //TODO(b/354658237) - show toast with relevant string + } else { + mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo, + decoration.mTaskInfo.configuration.windowConfiguration.getBounds(), + left ? SnapPosition.LEFT : SnapPosition.RIGHT); + } + decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } @@ -549,6 +558,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DragDetector mDragDetector; private final GestureDetector mGestureDetector; private final int mDisplayId; + private final Rect mOnDragStartInitialBounds = new Rect(); /** * Whether to pilfer the next motion event to send cancellations to the windows below. @@ -752,10 +762,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); - mDragPositioningCallback.onDragPositioningStart( + final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart( 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); updateDragStatus(e.getActionMasked()); + mOnDragStartInitialBounds.set(initialBounds); mHasLongClicked = false; // Do not consume input event if a button is touched, otherwise it would // prevent the button's ripple effect from showing. @@ -799,9 +810,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // Tasks bounds haven't actually been updated (only its leash), so pass to // DesktopTasksController to allow secondary transformations (i.e. snap resizing // or transforming to fullscreen) before setting new task bounds. - mDesktopTasksController.onDragPositioningEnd(taskInfo, position, + mDesktopTasksController.onDragPositioningEnd( + taskInfo, decoration.mTaskSurface, position, new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), - newTaskBounds, decoration.calculateValidDragArea()); + newTaskBounds, decoration.calculateValidDragArea(), + new Rect(mOnDragStartInitialBounds)); if (touchingButton && !mHasLongClicked) { // We need the input event to not be consumed here to end the ripple // effect on the touched button. We will reset drag state in the ensuing @@ -1299,6 +1312,25 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId); } + private class DesktopModeOnTaskRepositionAnimationListener + implements OnTaskRepositionAnimationListener { + @Override + public void onAnimationStart(int taskId) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration != null) { + decoration.setAnimatingTaskResizeOrReposition(true); + } + } + + @Override + public void onAnimationEnd(int taskId) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration != null) { + decoration.setAnimatingTaskResizeOrReposition(false); + } + } + } + private class DesktopModeOnTaskResizeAnimationListener implements OnTaskResizeAnimationListener { @Override @@ -1309,7 +1341,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } decoration.showResizeVeil(t, bounds); - decoration.setAnimatingTaskResize(true); + decoration.setAnimatingTaskResizeOrReposition(true); } @Override @@ -1324,7 +1356,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) return; decoration.hideResizeVeil(); - decoration.setAnimatingTaskResize(false); + decoration.setAnimatingTaskResizeOrReposition(false); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index d70e225b9a9a..538d0fb9cbf6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -1268,10 +1268,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return R.id.desktop_mode_caption; } - void setAnimatingTaskResize(boolean animatingTaskResize) { + void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return; ((AppHeaderViewHolder) mWindowDecorViewHolder) - .setAnimatingTaskResize(animatingTaskResize); + .setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskRepositionAnimationListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskRepositionAnimationListener.kt new file mode 100644 index 000000000000..214a6fa0b200 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskRepositionAnimationListener.kt @@ -0,0 +1,32 @@ +/* + * 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.windowdecor + +/** + * Listener that allows notifies when an animation that is repositioning a task is starting + * and finishing the animation. + */ +interface OnTaskRepositionAnimationListener { + /** + * Notifies that an animation is about to be started. + */ + fun onAnimationStart(taskId: Int) + + /** + * Notifies that an animation is about to be finished. + */ + fun onAnimationEnd(taskId: Int) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 237492eddfcd..7f2c1a81d20c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -163,7 +163,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, } else { DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); - mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW); + mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW); } mCtrlType = CTRL_TYPE_UNDEFINED; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index 17b3dea5db75..d0eb6da36702 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -238,12 +238,12 @@ internal class AppHeaderViewHolder( override fun onHandleMenuClosed() {} - fun setAnimatingTaskResize(animatingTaskResize: Boolean) { - // If animating a task resize, cancel any running hover animations - if (animatingTaskResize) { + fun setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition: Boolean) { + // If animating a task resize or reposition, cancel any running hover animations + if (animatingTaskResizeOrReposition) { maximizeButtonView.cancelHoverAnimation() } - maximizeButtonView.hoverDisabled = animatingTaskResize + maximizeButtonView.hoverDisabled = animatingTaskResizeOrReposition } fun onMaximizeWindowHoverExit() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 0597951c9bbc..76939f61832f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -118,7 +118,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.isA import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock @@ -131,6 +130,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.eq import org.mockito.kotlin.capture import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -157,6 +157,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock lateinit var transitions: Transitions @Mock lateinit var keyguardManager: KeyguardManager + @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler @Mock @@ -250,6 +251,7 @@ class DesktopTasksControllerTest : ShellTestCase() { dragAndDropController, transitions, keyguardManager, + mReturnToDragStartAnimator, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, @@ -2375,10 +2377,12 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.onDragPositioningEnd( task, + mockSurface, Point(100, -100), /* position */ PointF(200f, -200f), /* inputCoordinate */ Rect(100, -100, 500, 1000), /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */) + Rect(0, 50, 2000, 2000), /* validDragArea */ + Rect() /* dragStartBounds */ ) val rectAfterEnd = Rect(100, 50, 500, 1150) verify(transitions) .startTransition( @@ -2408,10 +2412,12 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onDragPositioningEnd( task, + mockSurface, Point(100, 200), /* position */ PointF(200f, 300f), /* inputCoordinate */ currentDragBounds, /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */) + Rect(0, 50, 2000, 2000) /* validDragArea */, + Rect() /* dragStartBounds */) verify(transitions) @@ -2532,6 +2538,41 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun handleSnapResizingTask_nonResizable_snapsToHalfScreen() { + val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { + isResizeable = false + } + val preDragBounds = Rect(100, 100, 400, 500) + val currentDragBounds = Rect(0, 100, 300, 500) + + controller.handleSnapResizingTask( + task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds) + val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) + assertThat(findBoundsChange(wct, task)).isEqualTo( + Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom)) + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun handleSnapResizingTask_nonResizable_startsRepositionAnimation() { + val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { + isResizeable = false + } + val preDragBounds = Rect(100, 100, 400, 500) + val currentDragBounds = Rect(0, 100, 300, 500) + + controller.handleSnapResizingTask( + task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds) + verify(mReturnToDragStartAnimator).start( + eq(task.taskId), + eq(mockSurface), + eq(currentDragBounds), + eq(preDragBounds) + ) + } + + @Test fun toggleBounds_togglesToCalculatedBoundsForNonResizable() { val bounds = Rect(0, 0, 200, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index bbf42b5fb1a1..68975ec3556e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -598,6 +598,40 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun testOnSnapResizeLeft_nonResizable_decorSnappedLeft() { + val onLeftSnapClickListenerCaptor = forClass(Function0::class.java) + as ArgumentCaptor<Function0<Unit>> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor + ).also { it.mTaskInfo.isResizeable = false } + + val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds + onLeftSnapClickListenerCaptor.value.invoke() + + verify(mockDesktopTasksController) + .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT) + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun testOnSnapResizeLeft_nonResizable_decorNotSnapped() { + val onLeftSnapClickListenerCaptor = forClass(Function0::class.java) + as ArgumentCaptor<Function0<Unit>> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor + ).also { it.mTaskInfo.isResizeable = false } + + val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds + onLeftSnapClickListenerCaptor.value.invoke() + + verify(mockDesktopTasksController, never()) + .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT) + } + + @Test fun testOnDecorSnappedRight_snapResizes() { val onRightSnapClickListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> @@ -629,6 +663,40 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun testOnSnapResizeRight_nonResizable_decorSnappedRight() { + val onRightSnapClickListenerCaptor = forClass(Function0::class.java) + as ArgumentCaptor<Function0<Unit>> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor + ).also { it.mTaskInfo.isResizeable = false } + + val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds + onRightSnapClickListenerCaptor.value.invoke() + + verify(mockDesktopTasksController) + .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT) + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun testOnSnapResizeRight_nonResizable_decorNotSnapped() { + val onRightSnapClickListenerCaptor = forClass(Function0::class.java) + as ArgumentCaptor<Function0<Unit>> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor + ).also { it.mTaskInfo.isResizeable = false } + + val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds + onRightSnapClickListenerCaptor.value.invoke() + + verify(mockDesktopTasksController, never()) + .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT) + } + + @Test fun testDecor_onClickToDesktop_movesToDesktopWithSource() { val toDesktopListenerCaptor = forClass(Consumer::class.java) as ArgumentCaptor<Consumer<DesktopModeTransitionSource>> @@ -991,6 +1059,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { .build().apply { topActivityInfo = activityInfo isFocused = focused + isResizeable = true } } |