blob: 92f1cc34be929daff7a33cb289d6792ff5e06e1e [file] [log] [blame]
Filip Gruszczynskie5390e72015-08-18 16:39:00 -07001/*
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
17package com.android.server.am;
18
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070019import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
20import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
21
Bryce Leedacefc42017-10-10 12:56:02 -070022import android.app.ActivityOptions;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070023import android.content.pm.ActivityInfo;
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070024import android.graphics.Rect;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070025import android.util.Slog;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070026import android.view.Gravity;
Bryce Lee81e30a22017-10-06 13:34:12 -070027import com.android.internal.annotations.VisibleForTesting;
Bryce Leeec55eb02017-12-05 20:51:27 -080028import com.android.server.am.LaunchParamsController.LaunchParams;
29import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070030
31import java.util.ArrayList;
32
33/**
34 * Determines where a launching task should be positioned and sized on the display.
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070035 *
Bryce Leeec55eb02017-12-05 20:51:27 -080036 * The modifier is fairly simple. For the new task it tries default position based on the gravity
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070037 * 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.
Bryce Leedacefc42017-10-10 12:56:02 -070040 *
41 * Note that the only gravities of concern are the corners and the center.
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070042 */
Bryce Leeec55eb02017-12-05 20:51:27 -080043class TaskLaunchParamsModifier implements LaunchParamsModifier {
44 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskLaunchParamsModifier" : TAG_AM;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070045
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070046 // 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
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070062 // 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
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070070 private final Rect mAvailableRect = new Rect();
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070071 private final Rect mTmpProposal = new Rect();
72 private final Rect mTmpOriginal = new Rect();
73
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070074 /**
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.
Filip Gruszczynskie5390e72015-08-18 16:39:00 -070078 */
Bryce Leedacefc42017-10-10 12:56:02 -070079 @Override
Bryce Leeec55eb02017-12-05 20:51:27 -080080 public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout,
81 ActivityRecord activity, ActivityRecord source, ActivityOptions options,
82 LaunchParams currentParams, LaunchParams outParams) {
Bryce Leedacefc42017-10-10 12:56:02 -070083 // 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
Bryce Leef3c6a472017-11-14 14:53:06 -080090 mAvailableRect.set(task.getParent().getBounds());
Bryce Lee9ad3eb32017-10-10 10:10:31 -070091
Bryce Leeec55eb02017-12-05 20:51:27 -080092 final Rect resultBounds = outParams.mBounds;
93
Bryce Leedacefc42017-10-10 12:56:02 -070094 if (layout == null) {
95 positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
Bryce Leeec55eb02017-12-05 20:51:27 -080096 getFreeformHeight(mAvailableRect), resultBounds);
Bryce Leedacefc42017-10-10 12:56:02 -070097 return RESULT_CONTINUE;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -070098 }
Bryce Leedacefc42017-10-10 12:56:02 -070099
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;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700104 if (verticalGravity == Gravity.TOP) {
105 if (horizontalGravity == Gravity.RIGHT) {
Bryce Leeec55eb02017-12-05 20:51:27 -0800106 positionTopRight(tasks, mAvailableRect, width, height, resultBounds);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700107 } else {
Bryce Leeec55eb02017-12-05 20:51:27 -0800108 positionTopLeft(tasks, mAvailableRect, width, height, resultBounds);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700109 }
110 } else if (verticalGravity == Gravity.BOTTOM) {
111 if (horizontalGravity == Gravity.RIGHT) {
Bryce Leeec55eb02017-12-05 20:51:27 -0800112 positionBottomRight(tasks, mAvailableRect, width, height, resultBounds);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700113 } else {
Bryce Leeec55eb02017-12-05 20:51:27 -0800114 positionBottomLeft(tasks, mAvailableRect, width, height, resultBounds);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700115 }
116 } else {
117 // Some fancy gravity setting that we don't support yet. We just put the activity in the
118 // center.
Bryce Leedacefc42017-10-10 12:56:02 -0700119 Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700120 + ", positioning in the center instead.");
Bryce Leeec55eb02017-12-05 20:51:27 -0800121 positionCenter(tasks, mAvailableRect, width, height, resultBounds);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700122 }
Bryce Leedacefc42017-10-10 12:56:02 -0700123
124 return RESULT_CONTINUE;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700125 }
126
Bryce Lee81e30a22017-10-06 13:34:12 -0700127 @VisibleForTesting
128 static int getFreeformStartLeft(Rect bounds) {
129 return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
130 }
131
132 @VisibleForTesting
133 static int getFreeformStartTop(Rect bounds) {
134 return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR;
135 }
136
137 @VisibleForTesting
138 static int getFreeformWidth(Rect bounds) {
139 return bounds.width() / WINDOW_SIZE_DENOMINATOR;
140 }
141
142 @VisibleForTesting
143 static int getFreeformHeight(Rect bounds) {
144 return bounds.height() / WINDOW_SIZE_DENOMINATOR;
145 }
146
147 @VisibleForTesting
148 static int getHorizontalStep(Rect bounds) {
149 return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP);
150 }
151
152 @VisibleForTesting
153 static int getVerticalStep(Rect bounds) {
154 return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP);
155 }
156
157
158
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700159 private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
160 int width = getFreeformWidth(availableRect);
Andrii Kulian2e751b82016-03-16 16:59:32 -0700161 if (windowLayout.width > 0) {
162 width = windowLayout.width;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700163 }
Andrii Kulian2e751b82016-03-16 16:59:32 -0700164 if (windowLayout.widthFraction > 0) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700165 width = (int) (availableRect.width() * windowLayout.widthFraction);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700166 }
167 return width;
168 }
169
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700170 private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
171 int height = getFreeformHeight(availableRect);
Andrii Kulian2e751b82016-03-16 16:59:32 -0700172 if (windowLayout.height > 0) {
173 height = windowLayout.height;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700174 }
Andrii Kulian2e751b82016-03-16 16:59:32 -0700175 if (windowLayout.heightFraction > 0) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700176 height = (int) (availableRect.height() * windowLayout.heightFraction);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700177 }
178 return height;
179 }
180
Bryce Leedacefc42017-10-10 12:56:02 -0700181 private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
182 int height, Rect result) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700183 mTmpProposal.set(availableRect.left, availableRect.bottom - height,
184 availableRect.left + width, availableRect.bottom);
Bryce Leedacefc42017-10-10 12:56:02 -0700185 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
186 result);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700187 }
188
Bryce Leedacefc42017-10-10 12:56:02 -0700189 private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
190 int height, Rect result) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700191 mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
192 availableRect.right, availableRect.bottom);
Bryce Leedacefc42017-10-10 12:56:02 -0700193 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
194 result);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700195 }
196
Bryce Leedacefc42017-10-10 12:56:02 -0700197 private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
198 int height, Rect result) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700199 mTmpProposal.set(availableRect.left, availableRect.top,
200 availableRect.left + width, availableRect.top + height);
Bryce Leedacefc42017-10-10 12:56:02 -0700201 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
202 result);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700203 }
204
Bryce Leedacefc42017-10-10 12:56:02 -0700205 private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
206 int height, Rect result) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700207 mTmpProposal.set(availableRect.right - width, availableRect.top,
208 availableRect.right, availableRect.top + height);
Bryce Leedacefc42017-10-10 12:56:02 -0700209 position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
210 result);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700211 }
212
Bryce Leedacefc42017-10-10 12:56:02 -0700213 private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
214 int height, Rect result) {
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700215 final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
216 final int defaultFreeformTop = getFreeformStartTop(availableRect);
217 mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
218 defaultFreeformLeft + width, defaultFreeformTop + height);
Bryce Leedacefc42017-10-10 12:56:02 -0700219 position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN,
220 result);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700221 }
222
Bryce Leedacefc42017-10-10 12:56:02 -0700223 private void position(ArrayList<TaskRecord> tasks, Rect availableRect,
224 Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) {
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700225 mTmpOriginal.set(proposal);
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700226 boolean restarted = false;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700227 while (boundsConflict(proposal, tasks)) {
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700228 // Unfortunately there is already a task at that spot, so we need to look for some
229 // other place.
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700230 shiftStartingPoint(proposal, availableRect, shiftPolicy);
231 if (shiftedTooFar(proposal, availableRect, shiftPolicy)) {
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700232 // 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.
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700240 proposal.set(availableRect.left, availableRect.top,
241 availableRect.left + proposal.width(),
242 availableRect.top + proposal.height());
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700243 restarted = true;
244 }
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700245 if (restarted && (proposal.left > getFreeformStartLeft(availableRect)
246 || proposal.top > getFreeformStartTop(availableRect))) {
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700247 // 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.
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700250 proposal.set(mTmpOriginal);
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700251 break;
252 }
253 }
Bryce Leedacefc42017-10-10 12:56:02 -0700254 result.set(proposal);
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700255 }
256
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700257 private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700258 switch (shiftPolicy) {
259 case SHIFT_POLICY_HORIZONTAL_LEFT:
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700260 return start.left < availableRect.left;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700261 case SHIFT_POLICY_HORIZONTAL_RIGHT:
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700262 return start.right > availableRect.right;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700263 default: // SHIFT_POLICY_DIAGONAL_DOWN
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700264 return start.right > availableRect.right || start.bottom > availableRect.bottom;
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700265 }
266 }
267
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700268 private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) {
269 final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect);
270 final int defaultFreeformStepVertical = getVerticalStep(availableRect);
271
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700272 switch (shiftPolicy) {
273 case SHIFT_POLICY_HORIZONTAL_LEFT:
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700274 posposal.offset(-defaultFreeformStepHorizontal, 0);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700275 break;
276 case SHIFT_POLICY_HORIZONTAL_RIGHT:
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700277 posposal.offset(defaultFreeformStepHorizontal, 0);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700278 break;
279 default: // SHIFT_POLICY_DIAGONAL_DOWN:
Bryce Lee9ad3eb32017-10-10 10:10:31 -0700280 posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical);
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700281 break;
282 }
283 }
284
285 private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) {
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700286 for (int i = tasks.size() - 1; i >= 0; i--) {
Bryce Leef3c6a472017-11-14 14:53:06 -0800287 final TaskRecord task = tasks.get(i);
288 if (!task.mActivities.isEmpty() && !task.matchParentBounds()) {
289 final Rect bounds = task.getOverrideBounds();
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700290 if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds)
291 || closeLeftBottomCorner(proposal, bounds)
292 || closeRightBottomCorner(proposal, bounds)) {
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700293 return true;
294 }
295 }
296 }
297 return false;
298 }
299
Filip Gruszczynski9b1ce522015-08-20 18:37:19 -0700300 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
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
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
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 }
Filip Gruszczynskie5390e72015-08-18 16:39:00 -0700319}