1 /* 2 * Copyright (C) 2015 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.server.am; 18 19 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 20 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 21 22 import android.app.ActivityOptions; 23 import android.content.pm.ActivityInfo; 24 import android.graphics.Rect; 25 import android.util.Slog; 26 import android.view.Gravity; 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.server.am.LaunchParamsController.LaunchParams; 29 import com.android.server.am.LaunchParamsController.LaunchParamsModifier; 30 31 import java.util.ArrayList; 32 33 /** 34 * Determines where a launching task should be positioned and sized on the display. 35 * 36 * The modifier is fairly simple. For the new task it tries default position based on the gravity 37 * and compares corners of the task with corners of existing tasks. If some two pairs of corners are 38 * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts 39 * all possible shifts, it gives up and puts the task in the original position. 40 * 41 * Note that the only gravities of concern are the corners and the center. 42 */ 43 class TaskLaunchParamsModifier implements LaunchParamsModifier { 44 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskLaunchParamsModifier" : TAG_AM; 45 46 // Determines how close window frames/corners have to be to call them colliding. 47 private static final int BOUNDS_CONFLICT_MIN_DISTANCE = 4; 48 49 // Task will receive dimensions based on available dimensions divided by this. 50 private static final int WINDOW_SIZE_DENOMINATOR = 2; 51 52 // Task will receive margins based on available dimensions divided by this. 53 private static final int MARGIN_SIZE_DENOMINATOR = 4; 54 55 // If task bounds collide with some other, we will step and try again until we find a good 56 // position. The step will be determined by using dimensions and dividing it by this. 57 private static final int STEP_DENOMINATOR = 16; 58 59 // We always want to step by at least this. 60 private static final int MINIMAL_STEP = 1; 61 62 // Used to indicate if positioning algorithm is allowed to restart from the beginning, when it 63 // reaches the end of stack bounds. 64 private static final boolean ALLOW_RESTART = true; 65 66 private static final int SHIFT_POLICY_DIAGONAL_DOWN = 1; 67 private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2; 68 private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3; 69 70 private final Rect mAvailableRect = new Rect(); 71 private final Rect mTmpProposal = new Rect(); 72 private final Rect mTmpOriginal = new Rect(); 73 74 /** 75 * Tries to set task's bound in a way that it won't collide with any other task. By colliding 76 * we mean that two tasks have left-top corner very close to each other, so one might get 77 * obfuscated by the other one. 78 */ 79 @Override onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options, LaunchParams currentParams, LaunchParams outParams)80 public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout, 81 ActivityRecord activity, ActivityRecord source, ActivityOptions options, 82 LaunchParams currentParams, LaunchParams outParams) { 83 // We can only apply positioning if we're in a freeform stack. 84 if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) { 85 return RESULT_SKIP; 86 } 87 88 final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks(); 89 90 mAvailableRect.set(task.getParent().getBounds()); 91 92 final Rect resultBounds = outParams.mBounds; 93 94 if (layout == null) { 95 positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect), 96 getFreeformHeight(mAvailableRect), resultBounds); 97 return RESULT_CONTINUE; 98 } 99 100 int width = getFinalWidth(layout, mAvailableRect); 101 int height = getFinalHeight(layout, mAvailableRect); 102 int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK; 103 int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 104 if (verticalGravity == Gravity.TOP) { 105 if (horizontalGravity == Gravity.RIGHT) { 106 positionTopRight(tasks, mAvailableRect, width, height, resultBounds); 107 } else { 108 positionTopLeft(tasks, mAvailableRect, width, height, resultBounds); 109 } 110 } else if (verticalGravity == Gravity.BOTTOM) { 111 if (horizontalGravity == Gravity.RIGHT) { 112 positionBottomRight(tasks, mAvailableRect, width, height, resultBounds); 113 } else { 114 positionBottomLeft(tasks, mAvailableRect, width, height, resultBounds); 115 } 116 } else { 117 // Some fancy gravity setting that we don't support yet. We just put the activity in the 118 // center. 119 Slog.w(TAG, "Received unsupported gravity: " + layout.gravity 120 + ", positioning in the center instead."); 121 positionCenter(tasks, mAvailableRect, width, height, resultBounds); 122 } 123 124 return RESULT_CONTINUE; 125 } 126 127 @VisibleForTesting getFreeformStartLeft(Rect bounds)128 static int getFreeformStartLeft(Rect bounds) { 129 return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR; 130 } 131 132 @VisibleForTesting getFreeformStartTop(Rect bounds)133 static int getFreeformStartTop(Rect bounds) { 134 return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR; 135 } 136 137 @VisibleForTesting getFreeformWidth(Rect bounds)138 static int getFreeformWidth(Rect bounds) { 139 return bounds.width() / WINDOW_SIZE_DENOMINATOR; 140 } 141 142 @VisibleForTesting getFreeformHeight(Rect bounds)143 static int getFreeformHeight(Rect bounds) { 144 return bounds.height() / WINDOW_SIZE_DENOMINATOR; 145 } 146 147 @VisibleForTesting getHorizontalStep(Rect bounds)148 static int getHorizontalStep(Rect bounds) { 149 return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP); 150 } 151 152 @VisibleForTesting getVerticalStep(Rect bounds)153 static int getVerticalStep(Rect bounds) { 154 return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP); 155 } 156 157 158 getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect)159 private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) { 160 int width = getFreeformWidth(availableRect); 161 if (windowLayout.width > 0) { 162 width = windowLayout.width; 163 } 164 if (windowLayout.widthFraction > 0) { 165 width = (int) (availableRect.width() * windowLayout.widthFraction); 166 } 167 return width; 168 } 169 getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect)170 private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) { 171 int height = getFreeformHeight(availableRect); 172 if (windowLayout.height > 0) { 173 height = windowLayout.height; 174 } 175 if (windowLayout.heightFraction > 0) { 176 height = (int) (availableRect.height() * windowLayout.heightFraction); 177 } 178 return height; 179 } 180 positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, int height, Rect result)181 private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, 182 int height, Rect result) { 183 mTmpProposal.set(availableRect.left, availableRect.bottom - height, 184 availableRect.left + width, availableRect.bottom); 185 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, 186 result); 187 } 188 positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, int height, Rect result)189 private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, 190 int height, Rect result) { 191 mTmpProposal.set(availableRect.right - width, availableRect.bottom - height, 192 availableRect.right, availableRect.bottom); 193 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, 194 result); 195 } 196 positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, int height, Rect result)197 private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, 198 int height, Rect result) { 199 mTmpProposal.set(availableRect.left, availableRect.top, 200 availableRect.left + width, availableRect.top + height); 201 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, 202 result); 203 } 204 positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, int height, Rect result)205 private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, 206 int height, Rect result) { 207 mTmpProposal.set(availableRect.right - width, availableRect.top, 208 availableRect.right, availableRect.top + height); 209 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, 210 result); 211 } 212 positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width, int height, Rect result)213 private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width, 214 int height, Rect result) { 215 final int defaultFreeformLeft = getFreeformStartLeft(availableRect); 216 final int defaultFreeformTop = getFreeformStartTop(availableRect); 217 mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop, 218 defaultFreeformLeft + width, defaultFreeformTop + height); 219 position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN, 220 result); 221 } 222 position(ArrayList<TaskRecord> tasks, Rect availableRect, Rect proposal, boolean allowRestart, int shiftPolicy, Rect result)223 private void position(ArrayList<TaskRecord> tasks, Rect availableRect, 224 Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) { 225 mTmpOriginal.set(proposal); 226 boolean restarted = false; 227 while (boundsConflict(proposal, tasks)) { 228 // Unfortunately there is already a task at that spot, so we need to look for some 229 // other place. 230 shiftStartingPoint(proposal, availableRect, shiftPolicy); 231 if (shiftedTooFar(proposal, availableRect, shiftPolicy)) { 232 // We don't want the task to go outside of the stack, because it won't look 233 // nice. Depending on the starting point we either restart, or immediately give up. 234 if (!allowRestart) { 235 proposal.set(mTmpOriginal); 236 break; 237 } 238 // We must have started not from the top. Let's restart from there because there 239 // might be some space there. 240 proposal.set(availableRect.left, availableRect.top, 241 availableRect.left + proposal.width(), 242 availableRect.top + proposal.height()); 243 restarted = true; 244 } 245 if (restarted && (proposal.left > getFreeformStartLeft(availableRect) 246 || proposal.top > getFreeformStartTop(availableRect))) { 247 // If we restarted and crossed the initial position, let's not struggle anymore. 248 // The user already must have ton of tasks visible, we can just smack the new 249 // one in the center. 250 proposal.set(mTmpOriginal); 251 break; 252 } 253 } 254 result.set(proposal); 255 } 256 shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy)257 private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) { 258 switch (shiftPolicy) { 259 case SHIFT_POLICY_HORIZONTAL_LEFT: 260 return start.left < availableRect.left; 261 case SHIFT_POLICY_HORIZONTAL_RIGHT: 262 return start.right > availableRect.right; 263 default: // SHIFT_POLICY_DIAGONAL_DOWN 264 return start.right > availableRect.right || start.bottom > availableRect.bottom; 265 } 266 } 267 shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy)268 private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) { 269 final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect); 270 final int defaultFreeformStepVertical = getVerticalStep(availableRect); 271 272 switch (shiftPolicy) { 273 case SHIFT_POLICY_HORIZONTAL_LEFT: 274 posposal.offset(-defaultFreeformStepHorizontal, 0); 275 break; 276 case SHIFT_POLICY_HORIZONTAL_RIGHT: 277 posposal.offset(defaultFreeformStepHorizontal, 0); 278 break; 279 default: // SHIFT_POLICY_DIAGONAL_DOWN: 280 posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical); 281 break; 282 } 283 } 284 boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks)285 private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) { 286 for (int i = tasks.size() - 1; i >= 0; i--) { 287 final TaskRecord task = tasks.get(i); 288 if (!task.mActivities.isEmpty() && !task.matchParentBounds()) { 289 final Rect bounds = task.getOverrideBounds(); 290 if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds) 291 || closeLeftBottomCorner(proposal, bounds) 292 || closeRightBottomCorner(proposal, bounds)) { 293 return true; 294 } 295 } 296 } 297 return false; 298 } 299 closeLeftTopCorner(Rect first, Rect second)300 private static final boolean closeLeftTopCorner(Rect first, Rect second) { 301 return Math.abs(first.left - second.left) < BOUNDS_CONFLICT_MIN_DISTANCE 302 && Math.abs(first.top - second.top) < BOUNDS_CONFLICT_MIN_DISTANCE; 303 } 304 closeRightTopCorner(Rect first, Rect second)305 private static final boolean closeRightTopCorner(Rect first, Rect second) { 306 return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE 307 && Math.abs(first.top - second.top) < BOUNDS_CONFLICT_MIN_DISTANCE; 308 } 309 closeLeftBottomCorner(Rect first, Rect second)310 private static final boolean closeLeftBottomCorner(Rect first, Rect second) { 311 return Math.abs(first.left - second.left) < BOUNDS_CONFLICT_MIN_DISTANCE 312 && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE; 313 } 314 closeRightBottomCorner(Rect first, Rect second)315 private static final boolean closeRightBottomCorner(Rect first, Rect second) { 316 return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE 317 && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE; 318 } 319 } 320