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 @VisibleForTesting 55 public static final float MIN_ASPECT = 1.2f; 56 57 /** 58 * Given a (x, y) point and its original starting down point and its original bounds, calculate 59 * and return a new resized bound. 60 * @param x the new moved X point. 61 * @param y the new moved Y point. 62 * @param startDragX the original starting X point. 63 * @param startDragY the original starting Y point. 64 * @param originalBounds the original bound before resize. 65 * @param ctrlType The type of resize operation. 66 * @param minVisibleWidth The minimal width required for the new size. 67 * @param minVisibleHeight The minimal height required for the new size. 68 * @param maxVisibleSize The maximum size allowed. 69 * @param preserveOrientation 70 * @param startOrientationWasLandscape 71 * @return 72 */ resizeDrag(float x, float y, float startDragX, float startDragY, Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight, Point maxVisibleSize, boolean preserveOrientation, boolean startOrientationWasLandscape)73 public static Rect resizeDrag(float x, float y, float startDragX, float startDragY, 74 Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight, 75 Point maxVisibleSize, boolean preserveOrientation, 76 boolean startOrientationWasLandscape) { 77 // This is a resizing operation. 78 // We need to keep various constraints: 79 // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] 80 // 2. The orientation is kept - if required. 81 final int deltaX = Math.round(x - startDragX); 82 final int deltaY = Math.round(y - startDragY); 83 int left = originalBounds.left; 84 int top = originalBounds.top; 85 int right = originalBounds.right; 86 int bottom = originalBounds.bottom; 87 88 // Calculate the resulting width and height of the drag operation. 89 int width = right - left; 90 int height = bottom - top; 91 if ((ctrlType & CTRL_LEFT) != 0) { 92 width = Math.max(minVisibleWidth, Math.min(width - deltaX, maxVisibleSize.x)); 93 } else if ((ctrlType & CTRL_RIGHT) != 0) { 94 width = Math.max(minVisibleWidth, Math.min(width + deltaX, maxVisibleSize.x)); 95 } 96 if ((ctrlType & CTRL_TOP) != 0) { 97 height = Math.max(minVisibleHeight, Math.min(height - deltaY, maxVisibleSize.y)); 98 } else if ((ctrlType & CTRL_BOTTOM) != 0) { 99 height = Math.max(minVisibleHeight, Math.min(height + deltaY, maxVisibleSize.y)); 100 } 101 102 // If we have to preserve the orientation - check that we are doing so. 103 final float aspect = (float) width / (float) height; 104 if (preserveOrientation && ((startOrientationWasLandscape && aspect < MIN_ASPECT) 105 || (!startOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { 106 // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major 107 // drag axis. What ever is producing the bigger rectangle will be chosen. 108 int width1; 109 int width2; 110 int height1; 111 int height2; 112 if (startOrientationWasLandscape) { 113 // Assuming that the width is our target we calculate the height. 114 width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); 115 height1 = Math.min(height, Math.round((float) width1 / MIN_ASPECT)); 116 if (height1 < minVisibleHeight) { 117 // If the resulting height is too small we adjust to the minimal size. 118 height1 = minVisibleHeight; 119 width1 = Math.max(minVisibleWidth, 120 Math.min(maxVisibleSize.x, Math.round((float) height1 * MIN_ASPECT))); 121 } 122 // Assuming that the height is our target we calculate the width. 123 height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); 124 width2 = Math.max(width, Math.round((float) height2 * MIN_ASPECT)); 125 if (width2 < minVisibleWidth) { 126 // If the resulting width is too small we adjust to the minimal size. 127 width2 = minVisibleWidth; 128 height2 = Math.max(minVisibleHeight, 129 Math.min(maxVisibleSize.y, Math.round((float) width2 / MIN_ASPECT))); 130 } 131 } else { 132 // Assuming that the width is our target we calculate the height. 133 width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); 134 height1 = Math.max(height, Math.round((float) width1 * MIN_ASPECT)); 135 if (height1 < minVisibleHeight) { 136 // If the resulting height is too small we adjust to the minimal size. 137 height1 = minVisibleHeight; 138 width1 = Math.max(minVisibleWidth, 139 Math.min(maxVisibleSize.x, Math.round((float) height1 / MIN_ASPECT))); 140 } 141 // Assuming that the height is our target we calculate the width. 142 height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); 143 width2 = Math.min(width, Math.round((float) height2 / MIN_ASPECT)); 144 if (width2 < minVisibleWidth) { 145 // If the resulting width is too small we adjust to the minimal size. 146 width2 = minVisibleWidth; 147 height2 = Math.max(minVisibleHeight, 148 Math.min(maxVisibleSize.y, Math.round((float) width2 * MIN_ASPECT))); 149 } 150 } 151 152 // Use the bigger of the two rectangles if the major change was positive, otherwise 153 // do the opposite. 154 final boolean grows = width > (right - left) || height > (bottom - top); 155 if (grows == (width1 * height1 > width2 * height2)) { 156 width = width1; 157 height = height1; 158 } else { 159 width = width2; 160 height = height2; 161 } 162 } 163 164 // Generate the final bounds by keeping the opposite drag edge constant. 165 if ((ctrlType & CTRL_LEFT) != 0) { 166 left = right - width; 167 } else { // Note: The right might have changed - if we pulled at the right or not. 168 right = left + width; 169 } 170 if ((ctrlType & CTRL_TOP) != 0) { 171 top = bottom - height; 172 } else { // Note: The height might have changed - if we pulled at the bottom or not. 173 bottom = top + height; 174 } 175 return new Rect(left, top, right, bottom); 176 } 177 } 178