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 31 import com.android.window.flags.Flags; 32 import com.android.wm.shell.R; 33 import com.android.wm.shell.common.DisplayController; 34 import com.android.wm.shell.shared.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 84 repositionTaskBounds.set(taskBoundsAtDragStart); 85 86 // Make sure the new resizing destination in any direction falls within the stable bounds. 87 // If not, set the bounds back to the old location that was valid to avoid conflicts with 88 // some regions such as the gesture area. 89 if ((ctrlType & CTRL_TYPE_LEFT) != 0) { 90 final int candidateLeft = repositionTaskBounds.left + (int) delta.x; 91 repositionTaskBounds.left = (candidateLeft > stableBounds.left) 92 ? candidateLeft : oldLeft; 93 } 94 if ((ctrlType & CTRL_TYPE_RIGHT) != 0) { 95 final int candidateRight = repositionTaskBounds.right + (int) delta.x; 96 repositionTaskBounds.right = (candidateRight < stableBounds.right) 97 ? candidateRight : oldRight; 98 } 99 if ((ctrlType & CTRL_TYPE_TOP) != 0) { 100 final int candidateTop = repositionTaskBounds.top + (int) delta.y; 101 repositionTaskBounds.top = (candidateTop > stableBounds.top) 102 ? candidateTop : oldTop; 103 } 104 if ((ctrlType & CTRL_TYPE_BOTTOM) != 0) { 105 final int candidateBottom = repositionTaskBounds.bottom + (int) delta.y; 106 repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom) 107 ? candidateBottom : oldBottom; 108 } 109 // If width or height are negative or less than the minimum width or height, revert the 110 // respective bounds to use previous bound dimensions. 111 if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) { 112 repositionTaskBounds.right = oldRight; 113 repositionTaskBounds.left = oldLeft; 114 } 115 if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) { 116 repositionTaskBounds.top = oldTop; 117 repositionTaskBounds.bottom = oldBottom; 118 } 119 // If there are no changes to the bounds after checking new bounds against minimum width 120 // and height, do not set bounds and return false 121 if (oldLeft == repositionTaskBounds.left && oldTop == repositionTaskBounds.top 122 && oldRight == repositionTaskBounds.right 123 && oldBottom == repositionTaskBounds.bottom) { 124 return false; 125 } 126 return true; 127 } 128 129 /** 130 * Set bounds using a {@link SurfaceControl.Transaction}. 131 */ setPositionOnDrag(WindowDecoration decoration, Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t, float x, float y)132 static void setPositionOnDrag(WindowDecoration decoration, Rect repositionTaskBounds, 133 Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t, 134 float x, float y) { 135 updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y); 136 t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top); 137 } 138 updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, float x, float y)139 static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, 140 PointF repositionStartPoint, float x, float y) { 141 final float deltaX = x - repositionStartPoint.x; 142 final float deltaY = y - repositionStartPoint.y; 143 repositionTaskBounds.set(taskBoundsAtDragStart); 144 repositionTaskBounds.offset((int) deltaX, (int) deltaY); 145 } 146 147 /** 148 * If task bounds are outside of provided drag area, snap the bounds to be just inside the 149 * drag area. 150 * 151 * @param repositionTaskBounds bounds determined by task positioner 152 * @param validDragArea the area that task must be positioned inside 153 * @return whether bounds were modified 154 */ snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea)155 public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) { 156 // If we were never supplied a valid drag area, do not restrict movement. 157 // Otherwise, we restrict deltas to keep task position inside the Rect. 158 if (validDragArea.width() == 0) return false; 159 boolean result = false; 160 if (repositionTaskBounds.left < validDragArea.left) { 161 repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0); 162 result = true; 163 } else if (repositionTaskBounds.left > validDragArea.right) { 164 repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0); 165 result = true; 166 } 167 if (repositionTaskBounds.top < validDragArea.top) { 168 repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top); 169 result = true; 170 } else if (repositionTaskBounds.top > validDragArea.bottom) { 171 repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top); 172 result = true; 173 } 174 return result; 175 } 176 getMinWidth(DisplayController displayController, WindowDecoration windowDecoration)177 private static float getMinWidth(DisplayController displayController, 178 WindowDecoration windowDecoration) { 179 return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController, 180 windowDecoration) 181 : windowDecoration.mTaskInfo.minWidth; 182 } 183 getMinHeight(DisplayController displayController, WindowDecoration windowDecoration)184 private static float getMinHeight(DisplayController displayController, 185 WindowDecoration windowDecoration) { 186 return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinHeight(displayController, 187 windowDecoration) 188 : windowDecoration.mTaskInfo.minHeight; 189 } 190 getDefaultMinWidth(DisplayController displayController, WindowDecoration windowDecoration)191 private static float getDefaultMinWidth(DisplayController displayController, 192 WindowDecoration windowDecoration) { 193 if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) { 194 return WindowDecoration.loadDimensionPixelSize( 195 windowDecoration.mDecorWindowContext.getResources(), 196 R.dimen.desktop_mode_minimum_window_width); 197 } 198 return getDefaultMinSize(displayController, windowDecoration); 199 } 200 getDefaultMinHeight(DisplayController displayController, WindowDecoration windowDecoration)201 private static float getDefaultMinHeight(DisplayController displayController, 202 WindowDecoration windowDecoration) { 203 if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) { 204 return WindowDecoration.loadDimensionPixelSize( 205 windowDecoration.mDecorWindowContext.getResources(), 206 R.dimen.desktop_mode_minimum_window_height); 207 } 208 return getDefaultMinSize(displayController, windowDecoration); 209 } 210 getDefaultMinSize(DisplayController displayController, WindowDecoration windowDecoration)211 private static float getDefaultMinSize(DisplayController displayController, 212 WindowDecoration windowDecoration) { 213 float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId) 214 .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE; 215 return windowDecoration.mTaskInfo.defaultMinSize * density; 216 } 217 isSizeConstraintForDesktopModeEnabled(Context context)218 private static boolean isSizeConstraintForDesktopModeEnabled(Context context) { 219 return DesktopModeStatus.canEnterDesktopMode(context) 220 && Flags.enableDesktopWindowingSizeConstraints(); 221 } 222 223 interface DragStartListener { 224 /** 225 * Inform the implementing class that a drag resize has started 226 * 227 * @param taskId id of this positioner's {@link WindowDecoration} 228 */ onDragStart(int taskId)229 void onDragStart(int taskId); 230 } 231 } 232