API for moving top activity in a stack to pinned stack.
* AMS.moveTopStackActivityToPinnedStack can be used to move the top
activity in a stack to the pinned stack and also specify the bounds
the pinned stack should be sized to.
* 'am stack move-top-activity-to-pinned-stack' command for testing
AMS.moveTopStackActivityToPinnedStack API
Bug: 25006507
Change-Id: I8392b4c39d8542153e691be7a627b7f35fd44884
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 1e2e33d..12780a8 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -154,7 +154,7 @@
" am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
" am stack resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
" am stack size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]\n" +
- " am stack split <STACK_ID> <v|h> [INTENT]\n" +
+ " am stack move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
" am stack positiontask <TASK_ID> <STACK_ID> <POSITION>\n" +
" am stack list\n" +
" am stack info <STACK_ID>\n" +
@@ -297,11 +297,9 @@
" <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom\n" +
" applying the optional [DELAY_MS] between each step.\n" +
"\n" +
- "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally\n" +
- " starting the new stack with [INTENT] if specified. If [INTENT] isn't\n" +
- " specified and the current stack has more than one task, then the top task\n" +
- " of the current task will be moved to the new stack. Command will also force\n" +
- " all current tasks in both stacks to be resizeable.\n" +
+ "am stack move-top-activity-to-pinned-stack: moves the top activity from\n" +
+ " <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the\n" +
+ " bounds of the pinned stack.\n" +
"\n" +
"am stack positiontask: place <TASK_ID> in <STACK_ID> at <POSITION>" +
"\n" +
@@ -1954,25 +1952,34 @@
private void runStack() throws Exception {
String op = nextArgRequired();
- if (op.equals("start")) {
- runStackStart();
- } else if (op.equals("movetask")) {
- runStackMoveTask();
- } else if (op.equals("resize")) {
- runStackResize();
- } else if (op.equals("positiontask")) {
- runStackPositionTask();
- } else if (op.equals("list")) {
- runStackList();
- } else if (op.equals("info")) {
- runStackInfo();
- } else if (op.equals("split")) {
- runStackSplit();
- } else if (op.equals("size-docked-stack-test")) {
- runStackSizeDockedStackTest();
- } else {
- showError("Error: unknown command '" + op + "'");
- return;
+ switch (op) {
+ case "start":
+ runStackStart();
+ break;
+ case "movetask":
+ runStackMoveTask();
+ break;
+ case "resize":
+ runStackResize();
+ break;
+ case "positiontask":
+ runStackPositionTask();
+ break;
+ case "list":
+ runStackList();
+ break;
+ case "info":
+ runStackInfo();
+ break;
+ case "move-top-activity-to-pinned-stack":
+ runMoveTopActivityToPinnedStack();
+ break;
+ case "size-docked-stack-test":
+ runStackSizeDockedStackTest();
+ break;
+ default:
+ showError("Error: unknown command '" + op + "'");
+ break;
}
}
@@ -2072,61 +2079,21 @@
}
}
- private void runStackSplit() throws Exception {
- final int stackId = Integer.valueOf(nextArgRequired());
- final String splitDirection = nextArgRequired();
- Intent intent = null;
- try {
- intent = makeIntent(UserHandle.USER_CURRENT);
- } catch (IllegalArgumentException e) {
- // no intent supplied.
+ private void runMoveTopActivityToPinnedStack() throws Exception {
+ int stackId = Integer.valueOf(nextArgRequired());
+ final Rect bounds = getBounds();
+ if (bounds == null) {
+ System.err.println("Error: invalid input bounds");
+ return;
}
try {
- final StackInfo currentStackInfo = mAm.getStackInfo(stackId);
- // Calculate bounds for new and current stack.
- final Rect currentStackBounds = new Rect(currentStackInfo.bounds);
- final Rect newStackBounds = new Rect(currentStackInfo.bounds);
- if ("v".equals(splitDirection)) {
- currentStackBounds.right = newStackBounds.left = currentStackInfo.bounds.centerX();
- } else if ("h".equals(splitDirection)) {
- currentStackBounds.bottom = newStackBounds.top = currentStackInfo.bounds.centerY();
- } else {
- showError("Error: unknown split direction '" + splitDirection + "'");
- return;
+ if (!mAm.moveTopActivityToPinnedStack(stackId, bounds)) {
+ showError("Didn't move top activity to pinned stack.");
}
-
- // Create new stack
- IActivityContainer container = mAm.createStackOnDisplay(currentStackInfo.displayId);
- if (container == null) {
- showError("Error: Unable to create new stack...");
- }
-
- final int newStackId = container.getStackId();
-
- if (intent != null) {
- container.startActivity(intent);
- } else if (currentStackInfo.taskIds != null && currentStackInfo.taskIds.length > 1) {
- // Move top task over to new stack
- mAm.moveTaskToStack(currentStackInfo.taskIds[currentStackInfo.taskIds.length - 1],
- newStackId, true);
- }
-
- final StackInfo newStackInfo = mAm.getStackInfo(newStackId);
-
- // Make all tasks in the stacks resizeable.
- for (int taskId : currentStackInfo.taskIds) {
- mAm.setTaskResizeable(taskId, true);
- }
-
- for (int taskId : newStackInfo.taskIds) {
- mAm.setTaskResizeable(taskId, true);
- }
-
- // Resize stacks
- mAm.resizeStack(currentStackInfo.stackId, currentStackBounds, false);
- mAm.resizeStack(newStackInfo.stackId, newStackBounds, false);
} catch (RemoteException e) {
+ showError("Unable to move top activity: " + e);
+ return;
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b97f947..70f1bcc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -755,6 +755,16 @@
return true;
}
+ case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int stackId = data.readInt();
+ final Rect r = Rect.CREATOR.createFromParcel(data);
+ final boolean res = moveTopActivityToPinnedStack(stackId, r);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
+
case RESIZE_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final int stackId = data.readInt();
@@ -3555,6 +3565,22 @@
reply.recycle();
}
@Override
+ public boolean moveTopActivityToPinnedStack(int stackId, Rect r)
+ throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(stackId);
+ r.writeToParcel(data, 0);
+ mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0);
+ reply.readException();
+ final boolean res = reply.readInt() != 0;
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+ @Override
public void resizeStack(int stackId, Rect r, boolean allowResizeInDockedMode)
throws RemoteException
{
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c26a44c..ebdd302 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -143,6 +143,7 @@
public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop)
throws RemoteException;
+ public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException;
public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) throws RemoteException;
public void positionTaskInStack(int taskId, int stackId, int position) throws RemoteException;
public List<StackInfo> getAllStackInfos() throws RemoteException;
@@ -898,4 +899,5 @@
int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
int REMOVE_STACK = IBinder.FIRST_CALL_TRANSACTION + 348;
+ int MOVE_TOP_ACTIVITY_TO_PINNED_STACK = IBinder.FIRST_CALL_TRANSACTION + 349;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 43e45e1..9834757 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9083,6 +9083,29 @@
}
}
+ /**
+ * Moves the top activity in the input stackId to the pinned stack.
+ *
+ * @param stackId Id of stack to move the top activity to pinned stack.
+ * @param bounds Bounds to use for pinned stack.
+ *
+ * @return True if the top activity of the input stack was successfully moved to the pinned
+ * stack.
+ */
+ @Override
+ public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "moveTopActivityToPinnedStack()");
+ synchronized (this) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return mStackSupervisor.moveTopStackActivityToPinnedStackLocked(stackId, bounds);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
@Override
public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) {
enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ed3a1c2..99539e4 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4642,6 +4642,51 @@
r.taskConfigOverride = task.mOverrideConfig;
}
+ void setFocusAndResumeStateIfNeeded(
+ ActivityRecord r, boolean setFocus, boolean setResume, String reason) {
+ // If the activity had focus before move focus to this stack.
+ if (setFocus) {
+ // If the activity owns the last resumed activity, transfer that together,
+ // so that we don't resume the same activity again in the new stack.
+ // Apps may depend on onResume()/onPause() being called in pairs.
+ if (setResume) {
+ mResumedActivity = r;
+ // Move the stack in which we are placing the activity to the front. We don't use
+ // ActivityManagerService.setFocusedActivityLocked, because if the activity is
+ // already focused, the call will short-circuit and do nothing.
+ moveToFront(reason);
+ } else {
+ // We need to not only move the stack to the front, but also have the activity
+ // focused. This will achieve both goals.
+ mService.setFocusedActivityLocked(r, reason);
+ }
+ }
+ }
+
+ /**
+ * Moves the input activity from its current stack to this one.
+ * NOTE: The current task of the activity isn't moved to this stack. Instead a new task is
+ * created on this stack which the activity is added to.
+ * */
+ void moveActivityToStack(ActivityRecord r) {
+ final ActivityStack prevStack = r.task.stack;
+ if (prevStack.mStackId == mStackId) {
+ // You are already in the right stack silly...
+ return;
+ }
+
+ final boolean wasFocused = mStackSupervisor.isFrontStack(prevStack)
+ && (mStackSupervisor.topRunningActivityLocked() == r);
+ final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
+
+ final TaskRecord task = createTaskRecord(
+ mStackSupervisor.getNextTaskId(), r.info, r.intent, null, null, true);
+ r.setTask(task, null);
+ task.addActivityToTop(r);
+ setAppTask(r, task);
+ setFocusAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
+ }
+
private void setAppTask(ActivityRecord r, TaskRecord task) {
final Rect bounds = task.getLaunchBounds();
task.updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 33f8da7..7160885 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3265,23 +3265,8 @@
// If the task had focus before (or we're requested to move focus),
// move focus to the new stack.
- if (forceFocus || wasFocused) {
- // If the task owns the last resumed activity, transfer that together,
- // so that we don't resume the same activity again in the new stack.
- // Apps may depend on onResume()/onPause() being called in pairs.
- if (wasResumed) {
- stack.mResumedActivity = r;
- // Move the stack in which we are placing the task to the front. We don't use
- // ActivityManagerService.setFocusedActivityLocked, because if the activity is
- // already focused, the call will short-circuit and do nothing.
- stack.moveToFront(reason);
- } else {
- // We need to not only move the stack to the front, but also have the activity
- // focused. This will achieve both goals.
- mService.setFocusedActivityLocked(r, reason);
- }
-
- }
+ stack.setFocusAndResumeStateIfNeeded(
+ r, forceFocus || wasFocused, wasResumed, reason);
return stack;
}
@@ -3326,6 +3311,45 @@
resumeTopActivitiesLocked();
}
+ boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
+ final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+ if (stack == null) {
+ throw new IllegalArgumentException(
+ "moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId);
+ }
+
+ final ActivityRecord r = stack.topRunningActivityLocked();
+ if (r == null) {
+ Slog.w(TAG, "moveTopStackActivityToPinnedStackLocked: No top running activity"
+ + " in stack=" + stack);
+ return false;
+ }
+
+ final TaskRecord task = r.task;
+ if (!task.mResizeable) {
+ Slog.w(TAG, "moveTopStackActivityToPinnedStackLocked: Activity not resizeable"
+ + " r=" + r);
+ return false;
+ }
+
+ if (task.mActivities.size() == 1) {
+ // There is only one activity in the task. So, we can just move the task over to the
+ // pinned stack without re-parenting the activity in a different task.
+ moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS);
+ } else {
+ final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+ pinnedStack.moveActivityToStack(r);
+ }
+
+ resizeStackLocked(PINNED_STACK_ID, bounds, PRESERVE_WINDOWS, true);
+
+ // The task might have already been running and its visibility needs to be synchronized with
+ // the visibility of the stack / windows.
+ ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ resumeTopActivitiesLocked();
+ return true;
+ }
+
void positionTaskInStackLocked(int taskId, int stackId, int position) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {