From 9b1ce52f254b4d9c17ebf437f19f45603d3ad5b2 Mon Sep 17 00:00:00 2001 From: Filip Gruszczynski Date: Thu, 20 Aug 2015 18:37:19 -0700 Subject: [PATCH] Using initial activity layout to position the launching activity. The activity can be positioned in the center or in one of the corners. From there it shifts its position and tries to find a spot where it doesn't collide with other activities. The CL also includes a few fixes necessary to pipe the information about the initial layout through the system. Change-Id: I2aaf5b6d20044aafec713b7bd4193b05cfbd16f3 --- api/current.txt | 2 + api/system-current.txt | 2 + .../java/android/content/pm/ActivityInfo.java | 1 + .../android/content/pm/PackageParser.java | 13 +- core/res/res/values/attrs_manifest.xml | 4 +- core/res/res/values/public.xml | 2 + .../com/android/server/am/ActivityStack.java | 2 +- .../server/am/LaunchingTaskPositioner.java | 211 +++++++++++++++--- .../com/android/server/am/TaskRecord.java | 4 +- 9 files changed, 205 insertions(+), 36 deletions(-) diff --git a/api/current.txt b/api/current.txt index b1e80dd2a6721..4157c6ffb3cf0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -240,8 +240,10 @@ package android { field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb + field public static final int activityHeight = 16844019; // 0x10104f3 field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8 field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9 + field public static final int activityWidth = 16844018; // 0x10104f2 field public static final int addPrintersActivity = 16843750; // 0x10103e6 field public static final int addStatesFromChildren = 16842992; // 0x10100f0 field public static final int adjustViewBounds = 16843038; // 0x101011e diff --git a/api/system-current.txt b/api/system-current.txt index fe7c6dac610ed..33ce543c9ff8f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -333,8 +333,10 @@ package android { field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb + field public static final int activityHeight = 16844019; // 0x10104f3 field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8 field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9 + field public static final int activityWidth = 16844018; // 0x10104f2 field public static final int addPrintersActivity = 16843750; // 0x10103e6 field public static final int addStatesFromChildren = 16842992; // 0x10100f0 field public static final int adjustViewBounds = 16843038; // 0x101011e diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 876fbf55765c9..83092a9ab034f 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -719,6 +719,7 @@ public class ActivityInfo extends ComponentInfo maxRecents = orig.maxRecents; resizeable = orig.resizeable; lockTaskLaunchMode = orig.lockTaskLaunchMode; + initialLayout = orig.initialLayout; } /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 6443667ab1db5..502f73562418d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3307,25 +3307,25 @@ public class PackageParser { int height = -1; float heightFraction = -1f; final int widthType = sw.getType( - com.android.internal.R.styleable.AndroidManifestInitialLayout_activity_width); + com.android.internal.R.styleable.AndroidManifestInitialLayout_activityWidth); if (widthType == TypedValue.TYPE_FRACTION) { widthFraction = sw.getFraction( - com.android.internal.R.styleable.AndroidManifestInitialLayout_activity_width, + com.android.internal.R.styleable.AndroidManifestInitialLayout_activityWidth, 1, 1, -1); } else if (widthType == TypedValue.TYPE_DIMENSION) { width = sw.getDimensionPixelSize( - com.android.internal.R.styleable.AndroidManifestInitialLayout_activity_width, + com.android.internal.R.styleable.AndroidManifestInitialLayout_activityWidth, -1); } final int heightType = sw.getType( - com.android.internal.R.styleable.AndroidManifestInitialLayout_activity_height); + com.android.internal.R.styleable.AndroidManifestInitialLayout_activityHeight); if (heightType == TypedValue.TYPE_FRACTION) { heightFraction = sw.getFraction( - com.android.internal.R.styleable.AndroidManifestInitialLayout_activity_height, + com.android.internal.R.styleable.AndroidManifestInitialLayout_activityHeight, 1, 1, -1); } else if (heightType == TypedValue.TYPE_DIMENSION) { height = sw.getDimensionPixelSize( - com.android.internal.R.styleable.AndroidManifestInitialLayout_activity_height, + com.android.internal.R.styleable.AndroidManifestInitialLayout_activityHeight, -1); } int gravity = sw.getInt( @@ -3415,6 +3415,7 @@ public class PackageParser { info.uiOptions = target.info.uiOptions; info.parentActivityName = target.info.parentActivityName; info.maxRecents = target.info.maxRecents; + info.initialLayout = target.info.initialLayout; Activity a = new Activity(mParseActivityAliasArgs, info); if (outError[0] != null) { diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 74fd1eccd4db6..1a2600d478204 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2200,10 +2200,10 @@ - + - + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index cabb56cb006d6..d2089cdad73b3 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2679,5 +2679,7 @@ + + diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index fcd596f28c5a7..965f5b626508b 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -4543,7 +4543,7 @@ final class ActivityStack { TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, voiceInteractor); if (mTaskPositioner != null) { - mTaskPositioner.updateDefaultBounds(task, mTaskHistory); + mTaskPositioner.updateDefaultBounds(task, mTaskHistory, info.initialLayout); } addTask(task, toTop, false); return task; diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java index 3005c869ddcf5..5c4fd138c19c2 100644 --- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java +++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java @@ -16,16 +16,30 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.annotation.Nullable; +import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; +import android.util.Slog; import android.view.Display; +import android.view.Gravity; import java.util.ArrayList; /** * Determines where a launching task should be positioned and sized on the display. + * + * The positioner is fairly simple. For the new task it tries default position based on the gravity + * and compares corners of the task with corners of existing tasks. If some two pairs of corners are + * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts + * all possible shifts, it gives up and puts the task in the original position. */ class LaunchingTaskPositioner { + private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM; + // Determines how close window frames/corners have to be to call them colliding. private static final int BOUNDS_CONFLICT_MIN_DISTANCE = 4; @@ -42,8 +56,19 @@ class LaunchingTaskPositioner { // We always want to step by at least this. private static final int MINIMAL_STEP = 1; + // Used to indicate if positioning algorithm is allowed to restart from the beginning, when it + // reaches the end of stack bounds. + private static final boolean ALLOW_RESTART = true; + + private static final int SHIFT_POLICY_DIAGONAL_DOWN = 1; + private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2; + private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3; + private boolean mDefaultStartBoundsConfigurationSet = false; private final Rect mAvailableRect = new Rect(); + private final Rect mTmpProposal = new Rect(); + private final Rect mTmpOriginal = new Rect(); + private int mDefaultFreeformStartX; private int mDefaultFreeformStartY; private int mDefaultFreeformWidth; @@ -84,51 +109,167 @@ class LaunchingTaskPositioner { * * @param task Task for which we want to find bounds that won't collide with other. * @param tasks Existing tasks with which we don't want to collide. + * @param initialLayout Optional information from the client about how it would like to be sized + * and positioned. */ - void updateDefaultBounds(TaskRecord task, ArrayList tasks) { + void updateDefaultBounds(TaskRecord task, ArrayList tasks, + @Nullable ActivityInfo.InitialLayout initialLayout) { if (!mDefaultStartBoundsConfigurationSet) { return; } - int startX = mDefaultFreeformStartX; - int startY = mDefaultFreeformStartY; - final int right = mAvailableRect.right; - final int bottom = mAvailableRect.bottom; + if (initialLayout == null) { + positionCenter(task, tasks, mDefaultFreeformWidth, mDefaultFreeformHeight); + return; + } + int width = getFinalWidth(initialLayout); + int height = getFinalHeight(initialLayout); + int verticalGravity = initialLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; + int horizontalGravity = initialLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + if (verticalGravity == Gravity.TOP) { + if (horizontalGravity == Gravity.RIGHT) { + positionTopRight(task, tasks, width, height); + } else { + positionTopLeft(task, tasks, width, height); + } + } else if (verticalGravity == Gravity.BOTTOM) { + if (horizontalGravity == Gravity.RIGHT) { + positionBottomRight(task, tasks, width, height); + } else { + positionBottomLeft(task, tasks, width, height); + } + } else { + // Some fancy gravity setting that we don't support yet. We just put the activity in the + // center. + Slog.w(TAG, "Received unsupported gravity: " + initialLayout.gravity + + ", positioning in the center instead."); + positionCenter(task, tasks, width, height); + } + } + + private int getFinalWidth(ActivityInfo.InitialLayout initialLayout) { + int width = mDefaultFreeformWidth; + if (initialLayout.width > 0) { + width = initialLayout.width; + } + if (initialLayout.widthFraction > 0) { + width = (int) (mAvailableRect.width() * initialLayout.widthFraction); + } + return width; + } + + private int getFinalHeight(ActivityInfo.InitialLayout initialLayout) { + int height = mDefaultFreeformHeight; + if (initialLayout.height > 0) { + height = initialLayout.height; + } + if (initialLayout.heightFraction > 0) { + height = (int) (mAvailableRect.height() * initialLayout.heightFraction); + } + return height; + } + + private void positionBottomLeft(TaskRecord task, ArrayList tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.left, mAvailableRect.bottom - height, + mAvailableRect.left + width, mAvailableRect.bottom); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT); + } + + private void positionBottomRight(TaskRecord task, ArrayList tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.bottom - height, + mAvailableRect.right, mAvailableRect.bottom); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT); + } + + private void positionTopLeft(TaskRecord task, ArrayList tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.left, mAvailableRect.top, + mAvailableRect.left + width, mAvailableRect.top + height); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT); + } + + private void positionTopRight(TaskRecord task, ArrayList tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.top, + mAvailableRect.right, mAvailableRect.top + height); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT); + } + + private void positionCenter(TaskRecord task, ArrayList tasks, int width, + int height) { + mTmpProposal.set(mDefaultFreeformStartX, mDefaultFreeformStartY, + mDefaultFreeformStartX + width, mDefaultFreeformStartY + height); + position(task, tasks, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN); + } + + private void position(TaskRecord task, ArrayList tasks, Rect proposal, + boolean allowRestart, int shiftPolicy) { + mTmpOriginal.set(proposal); boolean restarted = false; - while (boundsConflict(startX, startY, tasks)) { + while (boundsConflict(proposal, tasks)) { // Unfortunately there is already a task at that spot, so we need to look for some // other place. - startX += mDefaultFreeformStepHorizontal; - startY += mDefaultFreeformStepVertical; - if (startX + mDefaultFreeformWidth > right - || startY + mDefaultFreeformHeight > bottom) { - // We don't want the task to go outside of the display, because it won't look - // nice. Let's restart from the top instead, because there should be some space - // there. - startX = mAvailableRect.left; - startY = mAvailableRect.top; + shiftStartingPoint(proposal, shiftPolicy); + if (shiftedToFar(proposal, shiftPolicy)) { + // We don't want the task to go outside of the stack, because it won't look + // nice. Depending on the starting point we either restart, or immediately give up. + if (!allowRestart) { + proposal.set(mTmpOriginal); + break; + } + // We must have started not from the top. Let's restart from there because there + // might be some space there. + proposal.set(mAvailableRect.left, mAvailableRect.top, + mAvailableRect.left + proposal.width(), + mAvailableRect.top + proposal.height()); restarted = true; } - if (restarted - && (startX > mDefaultFreeformStartX || startY > mDefaultFreeformStartY)) { + if (restarted && (proposal.left > mDefaultFreeformStartX + || proposal.top > mDefaultFreeformStartY)) { // If we restarted and crossed the initial position, let's not struggle anymore. // The user already must have ton of tasks visible, we can just smack the new // one in the center. - startX = mDefaultFreeformStartX; - startY = mDefaultFreeformStartY; + proposal.set(mTmpOriginal); break; } } - task.setInitialBounds(startX, startY, startX + mDefaultFreeformWidth, - startY + mDefaultFreeformHeight); + task.setInitialBounds(proposal); } - private boolean boundsConflict(int startX, int startY, ArrayList tasks) { + private boolean shiftedToFar(Rect start, int shiftPolicy) { + switch (shiftPolicy) { + case SHIFT_POLICY_HORIZONTAL_LEFT: + return start.left < mAvailableRect.left; + case SHIFT_POLICY_HORIZONTAL_RIGHT: + return start.right > mAvailableRect.right; + default: // SHIFT_POLICY_DIAGONAL_DOWN + return start.right > mAvailableRect.right || start.bottom > mAvailableRect.bottom; + } + } + + private void shiftStartingPoint(Rect posposal, int shiftPolicy) { + switch (shiftPolicy) { + case SHIFT_POLICY_HORIZONTAL_LEFT: + posposal.offset(-mDefaultFreeformStepHorizontal, 0); + break; + case SHIFT_POLICY_HORIZONTAL_RIGHT: + posposal.offset(mDefaultFreeformStepHorizontal, 0); + break; + default: // SHIFT_POLICY_DIAGONAL_DOWN: + posposal.offset(mDefaultFreeformStepHorizontal, mDefaultFreeformStepVertical); + break; + } + } + + private static boolean boundsConflict(Rect proposal, ArrayList tasks) { for (int i = tasks.size() - 1; i >= 0; i--) { TaskRecord task = tasks.get(i); - if (!task.mActivities.isEmpty()) { + if (!task.mActivities.isEmpty() && task.mBounds != null) { Rect bounds = task.mBounds; - if (bounds != null && (Math.abs(bounds.left - startX) < BOUNDS_CONFLICT_MIN_DISTANCE - || Math.abs(bounds.top - startY) < BOUNDS_CONFLICT_MIN_DISTANCE)) { + if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds) + || closeLeftBottomCorner(proposal, bounds) + || closeRightBottomCorner(proposal, bounds)) { return true; } } @@ -136,6 +277,26 @@ class LaunchingTaskPositioner { return false; } + private static final boolean closeLeftTopCorner(Rect first, Rect second) { + return Math.abs(first.left - second.left) < BOUNDS_CONFLICT_MIN_DISTANCE + && Math.abs(first.top - second.top) < BOUNDS_CONFLICT_MIN_DISTANCE; + } + + private static final boolean closeRightTopCorner(Rect first, Rect second) { + return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE + && Math.abs(first.top - second.top) < BOUNDS_CONFLICT_MIN_DISTANCE; + } + + private static final boolean closeLeftBottomCorner(Rect first, Rect second) { + return Math.abs(first.left - second.left) < BOUNDS_CONFLICT_MIN_DISTANCE + && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE; + } + + private static final boolean closeRightBottomCorner(Rect first, Rect second) { + return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE + && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE; + } + void reset() { mDefaultStartBoundsConfigurationSet = false; } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index ff412d1167b9a..712b8cc9af6b5 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -1217,11 +1217,11 @@ final class TaskRecord { return mLastNonFullscreenBounds; } - void setInitialBounds(int left, int top, int right, int bottom) { + void setInitialBounds(Rect rect) { if (mBounds == null) { mBounds = new Rect(); } - mBounds.set(left, top, right, bottom); + mBounds.set(rect); mLastNonFullscreenBounds = mBounds; }