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