1 /* 2 * Copyright (C) 2020 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.internal.policy; 18 19 import android.annotation.IntDef; 20 import android.graphics.Point; 21 import android.graphics.Rect; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 28 /** 29 * Given a move coordinate (x, y), the original taks bounds and relevant details, calculate the new 30 * bounds. 31 * 32 * @hide 33 */ 34 public class TaskResizingAlgorithm { 35 36 @IntDef(flag = true, 37 value = { 38 CTRL_NONE, 39 CTRL_LEFT, 40 CTRL_RIGHT, 41 CTRL_TOP, 42 CTRL_BOTTOM 43 }) 44 @Retention(RetentionPolicy.SOURCE) 45 public @interface CtrlType {} 46 47 public static final int CTRL_NONE = 0x0; 48 public static final int CTRL_LEFT = 0x1; 49 public static final int CTRL_RIGHT = 0x2; 50 public static final int CTRL_TOP = 0x4; 51 public static final int CTRL_BOTTOM = 0x8; 52 53 // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait). 54 // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever 55 // aspect he desires. 56 @VisibleForTesting 57 public static final float MIN_ASPECT = 1.2f; 58 59 /** 60 * Given a (x, y) point and its original starting down point and its original bounds, calculate 61 * and return a new resized bound. 62 * @param x the new moved X point. 63 * @param y the new moved Y point. 64 * @param startDragX the original starting X point. 65 * @param startDragY the original starting Y point. 66 * @param originalBounds the original bound before resize. 67 * @param ctrlType The type of resize operation. 68 * @param minVisibleWidth The minimal width required for the new size. 69 * @param minVisibleHeight The minimal height required for the new size. 70 * @param maxVisibleSize The maximum size allowed. 71 * @param preserveOrientation 72 * @param startOrientationWasLandscape 73 * @return 74 */ resizeDrag(float x, float y, float startDragX, float startDragY, Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight, Point maxVisibleSize, boolean preserveOrientation, boolean startOrientationWasLandscape)75 public static Rect resizeDrag(float x, float y, float startDragX, float startDragY, 76 Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight, 77 Point maxVisibleSize, boolean preserveOrientation, 78 boolean startOrientationWasLandscape) { 79 // This is a resizing operation. 80 // We need to keep various constraints: 81 // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] 82 // 2. The orientation is kept - if required. 83 final int deltaX = Math.round(x - startDragX); 84 final int deltaY = Math.round(y - startDragY); 85 int left = originalBounds.left; 86 int top = originalBounds.top; 87 int right = originalBounds.right; 88 int bottom = originalBounds.bottom; 89 90 // Calculate the resulting width and height of the drag operation. 91 int width = right - left; 92 int height = bottom - top; 93 if ((ctrlType & CTRL_LEFT) != 0) { 94 width = Math.max(minVisibleWidth, Math.min(width - deltaX, maxVisibleSize.x)); 95 } else if ((ctrlType & CTRL_RIGHT) != 0) { 96 width = Math.max(minVisibleWidth, Math.min(width + deltaX, maxVisibleSize.x)); 97 } 98 if ((ctrlType & CTRL_TOP) != 0) { 99 height = Math.max(minVisibleHeight, Math.min(height - deltaY, maxVisibleSize.y)); 100 } else if ((ctrlType & CTRL_BOTTOM) != 0) { 101 height = Math.max(minVisibleHeight, Math.min(height + deltaY, maxVisibleSize.y)); 102 } 103 104 // If we have to preserve the orientation - check that we are doing so. 105 final float aspect = (float) width / (float) height; 106 if (preserveOrientation && ((startOrientationWasLandscape && aspect < MIN_ASPECT) 107 || (!startOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { 108 // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major 109 // drag axis. What ever is producing the bigger rectangle will be chosen. 110 int width1; 111 int width2; 112 int height1; 113 int height2; 114 if (startOrientationWasLandscape) { 115 // Assuming that the width is our target we calculate the height. 116 width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); 117 height1 = Math.min(height, Math.round((float) width1 / MIN_ASPECT)); 118 if (height1 < minVisibleHeight) { 119 // If the resulting height is too small we adjust to the minimal size. 120 height1 = minVisibleHeight; 121 width1 = Math.max(minVisibleWidth, 122 Math.min(maxVisibleSize.x, Math.round((float) height1 * MIN_ASPECT))); 123 } 124 // Assuming that the height is our target we calculate the width. 125 height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); 126 width2 = Math.max(width, Math.round((float) height2 * MIN_ASPECT)); 127 if (width2 < minVisibleWidth) { 128 // If the resulting width is too small we adjust to the minimal size. 129 width2 = minVisibleWidth; 130 height2 = Math.max(minVisibleHeight, 131 Math.min(maxVisibleSize.y, Math.round((float) width2 / MIN_ASPECT))); 132 } 133 } else { 134 // Assuming that the width is our target we calculate the height. 135 width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); 136 height1 = Math.max(height, Math.round((float) width1 * MIN_ASPECT)); 137 if (height1 < minVisibleHeight) { 138 // If the resulting height is too small we adjust to the minimal size. 139 height1 = minVisibleHeight; 140 width1 = Math.max(minVisibleWidth, 141 Math.min(maxVisibleSize.x, Math.round((float) height1 / MIN_ASPECT))); 142 } 143 // Assuming that the height is our target we calculate the width. 144 height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); 145 width2 = Math.min(width, Math.round((float) height2 / MIN_ASPECT)); 146 if (width2 < minVisibleWidth) { 147 // If the resulting width is too small we adjust to the minimal size. 148 width2 = minVisibleWidth; 149 height2 = Math.max(minVisibleHeight, 150 Math.min(maxVisibleSize.y, Math.round((float) width2 * MIN_ASPECT))); 151 } 152 } 153 154 // Use the bigger of the two rectangles if the major change was positive, otherwise 155 // do the opposite. 156 final boolean grows = width > (right - left) || height > (bottom - top); 157 if (grows == (width1 * height1 > width2 * height2)) { 158 width = width1; 159 height = height1; 160 } else { 161 width = width2; 162 height = height2; 163 } 164 } 165 166 // Generate the final bounds by keeping the opposite drag edge constant. 167 if ((ctrlType & CTRL_LEFT) != 0) { 168 left = right - width; 169 } else { // Note: The right might have changed - if we pulled at the right or not. 170 right = left + width; 171 } 172 if ((ctrlType & CTRL_TOP) != 0) { 173 top = bottom - height; 174 } else { // Note: The height might have changed - if we pulled at the bottom or not. 175 bottom = top + height; 176 } 177 return new Rect(left, top, right, bottom); 178 } 179 } 180