• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.windowdecor;
18 
19 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
20 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
21 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
22 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
23 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
24 
25 import android.content.Context;
26 import android.graphics.PointF;
27 import android.graphics.Rect;
28 import android.util.DisplayMetrics;
29 import android.view.SurfaceControl;
30 import android.window.DesktopModeFlags;
31 
32 import com.android.wm.shell.R;
33 import com.android.wm.shell.common.DisplayController;
34 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
35 
36 /**
37  * Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
38  * Specifically, this class contains logic for determining changed bounds from a drag input
39  * and applying that change to the task bounds when applicable.
40  */
41 public class DragPositioningCallbackUtility {
42     /**
43      * Determine the delta between input's current point and the input start point.
44      *
45      * @param inputX               current input x coordinate
46      * @param inputY               current input y coordinate
47      * @param repositionStartPoint initial input coordinate
48      * @return delta between these two points
49      */
calculateDelta(float inputX, float inputY, PointF repositionStartPoint)50     static PointF calculateDelta(float inputX, float inputY, PointF repositionStartPoint) {
51         final float deltaX = inputX - repositionStartPoint.x;
52         final float deltaY = inputY - repositionStartPoint.y;
53         return new PointF(deltaX, deltaY);
54     }
55 
56     /**
57      * Based on type of resize and delta provided, calculate the new bounds to display for this
58      * task.
59      *
60      * @param ctrlType              type of drag being performed
61      * @param repositionTaskBounds  the bounds the task is being repositioned to
62      * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
63      * @param stableBounds          bounds that represent the resize limit of this task
64      * @param delta                 difference between start input and current input in x/y
65      *                              coordinates
66      * @param windowDecoration      window decoration of the task being dragged
67      * @return whether this method changed repositionTaskBounds
68      */
changeBounds(int ctrlType, Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds, PointF delta, DisplayController displayController, WindowDecoration windowDecoration)69     static boolean changeBounds(int ctrlType, Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
70             Rect stableBounds, PointF delta, DisplayController displayController,
71             WindowDecoration windowDecoration) {
72         // If task is being dragged rather than resized, return since this method only handles
73         // with resizing
74         if (ctrlType == CTRL_TYPE_UNDEFINED) {
75             return false;
76         }
77 
78         final int oldLeft = repositionTaskBounds.left;
79         final int oldTop = repositionTaskBounds.top;
80         final int oldRight = repositionTaskBounds.right;
81         final int oldBottom = repositionTaskBounds.bottom;
82 
83         repositionTaskBounds.set(taskBoundsAtDragStart);
84 
85         boolean isAspectRatioMaintained = true;
86         // Make sure the new resizing destination in any direction falls within the stable bounds.
87         if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
88             repositionTaskBounds.left = Math.max(repositionTaskBounds.left + (int) delta.x,
89                     stableBounds.left);
90             if (repositionTaskBounds.left == stableBounds.left
91                     && repositionTaskBounds.left + (int) delta.x != stableBounds.left) {
92                 // If the task edge have been set to the stable bounds and not due to the users
93                 // drag, the aspect ratio of the task will not be maintained.
94                 isAspectRatioMaintained = false;
95             }
96         }
97         if ((ctrlType & CTRL_TYPE_RIGHT) != 0) {
98             repositionTaskBounds.right = Math.min(repositionTaskBounds.right + (int) delta.x,
99                     stableBounds.right);
100             if (repositionTaskBounds.right == stableBounds.right
101                     && repositionTaskBounds.right + (int) delta.x != stableBounds.right) {
102                 // If the task edge have been set to the stable bounds and not due to the users
103                 // drag, the aspect ratio of the task will not be maintained.
104                 isAspectRatioMaintained = false;
105             }
106         }
107         if ((ctrlType & CTRL_TYPE_TOP) != 0) {
108             repositionTaskBounds.top = Math.max(repositionTaskBounds.top + (int) delta.y,
109                     stableBounds.top);
110             if (repositionTaskBounds.top == stableBounds.top
111                     && repositionTaskBounds.top + (int) delta.y != stableBounds.top) {
112                 // If the task edge have been set to the stable bounds and not due to the users
113                 // drag, the aspect ratio of the task will not be maintained.
114                 isAspectRatioMaintained = false;
115             }
116         }
117         if ((ctrlType & CTRL_TYPE_BOTTOM) != 0) {
118             repositionTaskBounds.bottom = Math.min(repositionTaskBounds.bottom + (int) delta.y,
119                     stableBounds.bottom);
120             if (repositionTaskBounds.bottom == stableBounds.bottom
121                     && repositionTaskBounds.bottom + (int) delta.y != stableBounds.bottom) {
122                 // If the task edge have been set to the stable bounds and not due to the users
123                 // drag, the aspect ratio of the task will not be maintained.
124                 isAspectRatioMaintained = false;
125             }
126         }
127 
128         // If width or height are negative or exceeding the width or height constraints, revert the
129         // respective bounds to use previous bound dimensions.
130         if (isExceedingWidthConstraint(repositionTaskBounds.width(),
131                 /* startingWidth= */ oldRight - oldLeft, stableBounds, displayController,
132                 windowDecoration)) {
133             repositionTaskBounds.right = oldRight;
134             repositionTaskBounds.left = oldLeft;
135             isAspectRatioMaintained = false;
136         }
137         if (isExceedingHeightConstraint(repositionTaskBounds.height(),
138                 /* startingHeight= */oldBottom - oldTop, stableBounds, displayController,
139                 windowDecoration)) {
140             repositionTaskBounds.top = oldTop;
141             repositionTaskBounds.bottom = oldBottom;
142             isAspectRatioMaintained = false;
143         }
144 
145         // If the application is unresizeable and any bounds have been set back to their old
146         // location or to a stable bound edge, reset all the bounds to maintain the applications
147         // aspect ratio.
148         if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()
149                 && !isAspectRatioMaintained && !windowDecoration.mTaskInfo.isResizeable) {
150             repositionTaskBounds.top = oldTop;
151             repositionTaskBounds.bottom = oldBottom;
152             repositionTaskBounds.right = oldRight;
153             repositionTaskBounds.left = oldLeft;
154         }
155 
156         // If there are no changes to the bounds after checking new bounds against minimum and
157         // maximum width and height, do not set bounds and return false
158         return oldLeft != repositionTaskBounds.left || oldTop != repositionTaskBounds.top
159                 || oldRight != repositionTaskBounds.right
160                 || oldBottom != repositionTaskBounds.bottom;
161     }
162 
163     /**
164      * Set bounds using a {@link SurfaceControl.Transaction}.
165      */
setPositionOnDrag(WindowDecoration decoration, Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t, float x, float y)166     static void setPositionOnDrag(WindowDecoration decoration, Rect repositionTaskBounds,
167             Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t,
168             float x, float y) {
169         updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y);
170         t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
171     }
172 
updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, float x, float y)173     static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
174             PointF repositionStartPoint, float x, float y) {
175         final float deltaX = x - repositionStartPoint.x;
176         final float deltaY = y - repositionStartPoint.y;
177         repositionTaskBounds.set(taskBoundsAtDragStart);
178         repositionTaskBounds.offset((int) deltaX, (int) deltaY);
179     }
180 
181     /**
182      * If task bounds are outside of provided drag area, snap the bounds to be just inside the
183      * drag area.
184      *
185      * @param repositionTaskBounds bounds determined by task positioner
186      * @param validDragArea        the area that task must be positioned inside
187      * @return whether bounds were modified
188      */
snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea)189     public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
190         // If we were never supplied a valid drag area, do not restrict movement.
191         // Otherwise, we restrict deltas to keep task position inside the Rect.
192         if (validDragArea.width() == 0) return false;
193         boolean result = false;
194         if (repositionTaskBounds.left < validDragArea.left) {
195             repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
196             result = true;
197         } else if (repositionTaskBounds.left > validDragArea.right) {
198             repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
199             result = true;
200         }
201         if (repositionTaskBounds.top < validDragArea.top) {
202             repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
203             result = true;
204         } else if (repositionTaskBounds.top > validDragArea.bottom) {
205             repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
206             result = true;
207         }
208         return result;
209     }
210 
211     /**
212      * Checks whether the new task bounds exceed the allowed width.
213      *
214      * @param repositionedWidth task width after repositioning.
215      * @param startingWidth task width before repositioning.
216      * @param maxResizeBounds stable bounds for display.
217      * @param displayController display controller for the task being checked.
218      * @param windowDecoration contains decor info and helpers for the task.
219      * @return whether the task is exceeding any of the width constrains, minimum or maximum.
220      */
isExceedingWidthConstraint(int repositionedWidth, int startingWidth, Rect maxResizeBounds, DisplayController displayController, WindowDecoration windowDecoration)221     public static boolean isExceedingWidthConstraint(int repositionedWidth, int startingWidth,
222             Rect maxResizeBounds, DisplayController displayController,
223             WindowDecoration windowDecoration) {
224         boolean isSizeIncreasing = (repositionedWidth - startingWidth) > 0;
225         // Check if width is less than the minimum width constraint.
226         if (repositionedWidth < getMinWidth(displayController, windowDecoration)) {
227             // Only allow width to be increased if it is already below minimum.
228             return !isSizeIncreasing;
229         }
230         // Check if width is more than the maximum resize bounds on desktop windowing mode.
231         // Only allow width to be decreased if it already exceeds maximum.
232         return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
233                 && repositionedWidth > maxResizeBounds.width() && isSizeIncreasing;
234     }
235 
236     /**
237      * Checks whether the new task bounds exceed the allowed height.
238      *
239      * @param repositionedHeight task's height after repositioning.
240      * @param startingHeight task's height before repositioning.
241      * @param maxResizeBounds stable bounds for display.
242      * @param displayController display controller for the task being checked.
243      * @param windowDecoration contains decor info and helpers for the task.
244      * @return whether the task is exceeding any of the height constrains, minimum or maximum.
245      */
isExceedingHeightConstraint(int repositionedHeight, int startingHeight, Rect maxResizeBounds, DisplayController displayController, WindowDecoration windowDecoration)246     public static boolean isExceedingHeightConstraint(int repositionedHeight, int startingHeight,
247             Rect maxResizeBounds, DisplayController displayController,
248             WindowDecoration windowDecoration) {
249         boolean isSizeIncreasing = (repositionedHeight - startingHeight) > 0;
250         // Check if height is less than the minimum height constraint.
251         if (repositionedHeight < getMinHeight(displayController, windowDecoration)) {
252             // Only allow height to be increased if it is already below minimum.
253             return !isSizeIncreasing;
254         }
255         // Check if height is more than the maximum resize bounds on desktop windowing mode.
256         // Only allow height to be decreased if it already exceeds maximum.
257         return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
258                 && repositionedHeight > maxResizeBounds.height() && isSizeIncreasing;
259     }
260 
getMinWidth(DisplayController displayController, WindowDecoration windowDecoration)261     private static float getMinWidth(DisplayController displayController,
262             WindowDecoration windowDecoration) {
263         return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController,
264                 windowDecoration)
265                 : windowDecoration.mTaskInfo.minWidth;
266     }
267 
getMinHeight(DisplayController displayController, WindowDecoration windowDecoration)268     private static float getMinHeight(DisplayController displayController,
269             WindowDecoration windowDecoration) {
270         return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinHeight(displayController,
271                 windowDecoration)
272                 : windowDecoration.mTaskInfo.minHeight;
273     }
274 
getDefaultMinWidth(DisplayController displayController, WindowDecoration windowDecoration)275     private static float getDefaultMinWidth(DisplayController displayController,
276             WindowDecoration windowDecoration) {
277         if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) {
278             return WindowDecoration.loadDimensionPixelSize(
279                     windowDecoration.mDecorWindowContext.getResources(),
280                     R.dimen.desktop_mode_minimum_window_width);
281         }
282         return getDefaultMinSize(displayController, windowDecoration);
283     }
284 
getDefaultMinHeight(DisplayController displayController, WindowDecoration windowDecoration)285     private static float getDefaultMinHeight(DisplayController displayController,
286             WindowDecoration windowDecoration) {
287         if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) {
288             return WindowDecoration.loadDimensionPixelSize(
289                     windowDecoration.mDecorWindowContext.getResources(),
290                     R.dimen.desktop_mode_minimum_window_height);
291         }
292         return getDefaultMinSize(displayController, windowDecoration);
293     }
294 
getDefaultMinSize(DisplayController displayController, WindowDecoration windowDecoration)295     private static float getDefaultMinSize(DisplayController displayController,
296             WindowDecoration windowDecoration) {
297         float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId)
298                 .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
299         return windowDecoration.mTaskInfo.defaultMinSize * density;
300     }
301 
isSizeConstraintForDesktopModeEnabled(Context context)302     private static boolean isSizeConstraintForDesktopModeEnabled(Context context) {
303         return DesktopModeStatus.canEnterDesktopMode(context)
304                 && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS.isTrue();
305     }
306 
307     public interface DragEventListener {
308         /**
309          * Inform the implementing class that a drag resize has started
310          *
311          * @param taskId id of this positioner's {@link WindowDecoration}
312          */
onDragStart(int taskId)313         void onDragStart(int taskId);
314 
315         /**
316          * Inform the implementing class that a drag move has started.
317          *
318          * @param taskId id of this positioner's {@link WindowDecoration}
319          */
onDragMove(int taskId)320         void onDragMove(int taskId);
321     }
322 }
323