Add hierarchy operations to container transaction
Also add a way to query for child containers of a container.
Combined, these provide enough flexibility to re-arrange
containers to system-ui. For now, it is locked-down a bit so
it only provides children of TaskTiles and can only reparent
ActivityStacks. Eventually, this can be expanded for nested
tasks or display areas.
Bug: 133381284
Test: Added TaskTileTest#testHierarchyTransaction
Change-Id: Id0e89a4e4b9352d9392349c4e1fe7d69b4f9f212
diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl
index bfc42ef..5d5956e 100644
--- a/core/java/android/app/ITaskOrganizerController.aidl
+++ b/core/java/android/app/ITaskOrganizerController.aidl
@@ -51,6 +51,9 @@
/** Deletes a persistent root task in WM */
boolean deleteRootTask(IWindowContainer task);
+ /** Gets direct child tasks (ordered from top-to-bottom) */
+ List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent);
+
/** Get the root task which contains the current ime target */
IWindowContainer getImeTarget(int display);
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 33f21f2..cf34b0b 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -25,6 +27,8 @@
import android.os.Parcelable;
import android.util.ArrayMap;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -36,10 +40,14 @@
public class WindowContainerTransaction implements Parcelable {
private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
+ // Flat list because re-order operations are order-dependent
+ private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();
+
public WindowContainerTransaction() {}
protected WindowContainerTransaction(Parcel in) {
in.readMap(mChanges, null /* loader */);
+ in.readList(mHierarchyOps, null /* loader */);
}
private Change getOrCreateChange(IBinder token) {
@@ -97,10 +105,39 @@
return this;
}
+ /**
+ * Reparents a container into another one. The effect of a {@code null} parent can vary. For
+ * example, reparenting a stack to {@code null} will reparent it to its display.
+ *
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ */
+ public WindowContainerTransaction reparent(@NonNull IWindowContainer child,
+ @Nullable IWindowContainer parent, boolean onTop) {
+ mHierarchyOps.add(new HierarchyOp(child.asBinder(),
+ parent == null ? null : parent.asBinder(), onTop));
+ return this;
+ }
+
+ /**
+ * Reorders a container within its parent.
+ *
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ */
+ public WindowContainerTransaction reorder(@NonNull IWindowContainer child, boolean onTop) {
+ mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop));
+ return this;
+ }
+
public Map<IBinder, Change> getChanges() {
return mChanges;
}
+ public List<HierarchyOp> getHierarchyOps() {
+ return mHierarchyOps;
+ }
+
@Override
public String toString() {
return "WindowContainerTransaction { changes = " + mChanges + " }";
@@ -109,6 +146,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeMap(mChanges);
+ dest.writeList(mHierarchyOps);
}
@Override
@@ -249,4 +287,88 @@
}
};
}
+
+ /**
+ * Holds information about a reparent/reorder operation in the hierarchy. This is separate from
+ * Changes because they must be executed in the same order that they are added.
+ */
+ public static class HierarchyOp implements Parcelable {
+ private final IBinder mContainer;
+
+ // If this is same as mContainer, then only change position, don't reparent.
+ private final IBinder mReparent;
+
+ // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
+ private final boolean mToTop;
+
+ public HierarchyOp(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
+ mContainer = container;
+ mReparent = reparent;
+ mToTop = toTop;
+ }
+
+ public HierarchyOp(@NonNull IBinder container, boolean toTop) {
+ mContainer = container;
+ mReparent = container;
+ mToTop = toTop;
+ }
+
+ protected HierarchyOp(Parcel in) {
+ mContainer = in.readStrongBinder();
+ mReparent = in.readStrongBinder();
+ mToTop = in.readBoolean();
+ }
+
+ public boolean isReparent() {
+ return mContainer != mReparent;
+ }
+
+ @Nullable
+ public IBinder getNewParent() {
+ return mReparent;
+ }
+
+ @NonNull
+ public IBinder getContainer() {
+ return mContainer;
+ }
+
+ public boolean getToTop() {
+ return mToTop;
+ }
+
+ @Override
+ public String toString() {
+ if (isReparent()) {
+ return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+ + mReparent + "}";
+ } else {
+ return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mContainer);
+ dest.writeStrongBinder(mReparent);
+ dest.writeBoolean(mToTop);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<HierarchyOp> CREATOR = new Creator<HierarchyOp>() {
+ @Override
+ public HierarchyOp createFromParcel(Parcel in) {
+ return new HierarchyOp(in);
+ }
+
+ @Override
+ public HierarchyOp[] newArray(int size) {
+ return new HierarchyOp[size];
+ }
+ };
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 87c91ef..b1db9d7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -79,11 +79,6 @@
import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS;
-import static com.android.server.wm.TaskProto.FILLS_PARENT;
-import static com.android.server.wm.TaskProto.SURFACE_HEIGHT;
-import static com.android.server.wm.TaskProto.SURFACE_WIDTH;
-import static com.android.server.wm.TaskProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
@@ -125,7 +120,6 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
@@ -3195,12 +3189,16 @@
info.lastActiveTime = lastActiveTime;
info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription());
info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode();
- info.resizeMode = mResizeMode;
info.configuration.setTo(getConfiguration());
info.token = mRemoteToken;
// Get's the first non-undefined activity type among this and children. Can't use
// configuration.windowConfiguration because that would only be this level.
info.topActivityType = getActivityType();
+
+ //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
+ // order changes.
+ final Task top = getTopMostTask();
+ info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 096541f..0a0530c9 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -23,6 +23,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
@@ -47,6 +49,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -375,6 +378,45 @@
}
}
+ @Override
+ public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) {
+ enforceStackPermission("getChildTasks()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ if (parent == null) {
+ throw new IllegalArgumentException("Can't get children of null parent");
+ }
+ final WindowContainer container = WindowContainer.fromBinder(parent.asBinder());
+ if (container == null) {
+ Slog.e(TAG, "Can't get children of " + parent + " because it is not valid.");
+ return null;
+ }
+ // For now, only support returning children of persistent root tasks (of which the
+ // only current implementation is TaskTile).
+ if (!(container instanceof TaskTile)) {
+ Slog.w(TAG, "Can only get children of root tasks created via createRootTask");
+ return null;
+ }
+ ArrayList<RunningTaskInfo> out = new ArrayList<>();
+ // Tiles aren't real parents, so we need to go through stacks on the display to
+ // ensure correct ordering.
+ final DisplayContent dc = container.getDisplayContent();
+ for (int i = dc.getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack as = dc.getStackAt(i);
+ if (as.getTile() == container) {
+ final RunningTaskInfo info = new RunningTaskInfo();
+ as.fillTaskInfo(info);
+ out.add(info);
+ }
+ }
+ return out;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int sanitizeAndApplyChange(WindowContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task)) {
@@ -405,6 +447,54 @@
return effects;
}
+ private int sanitizeAndApplyHierarchyOp(WindowContainer container,
+ WindowContainerTransaction.HierarchyOp hop) {
+ if (!(container instanceof Task)) {
+ throw new IllegalArgumentException("Invalid container in hierarchy op");
+ }
+ if (hop.isReparent()) {
+ // special case for tiles since they are "virtual" parents
+ if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
+ ActivityStack as = (ActivityStack) container;
+ TaskTile newParent = hop.getNewParent() == null ? null
+ : (TaskTile) WindowContainer.fromBinder(hop.getNewParent());
+ if (as.getTile() != newParent) {
+ if (as.getTile() != null) {
+ as.getTile().removeChild(as);
+ }
+ if (newParent != null) {
+ if (!as.affectedBySplitScreenResize()) {
+ return 0;
+ }
+ newParent.addChild(as, POSITION_TOP);
+ }
+ }
+ if (hop.getToTop()) {
+ as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+ } else {
+ as.getDisplay().positionStackAtBottom(as);
+ }
+ } else if (container instanceof Task) {
+ throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
+ }
+ } else {
+ // Ugh, of course ActivityStack has its own special reorder logic...
+ if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
+ ActivityStack as = (ActivityStack) container;
+ if (hop.getToTop()) {
+ as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+ } else {
+ as.getDisplay().positionStackAtBottom(as);
+ }
+ } else {
+ container.getParent().positionChildAt(
+ hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+ container, false /* includingParents */);
+ }
+ }
+ return TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
int windowMask, Configuration config) {
if ((container instanceof ActivityStack)
@@ -470,8 +560,7 @@
while (entries.hasNext()) {
final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
entries.next();
- final WindowContainer wc = WindowContainer.RemoteToken.fromBinder(
- entry.getKey()).getContainer();
+ final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
int containerEffect = applyWindowContainerChange(wc, entry.getValue());
effects |= containerEffect;
@@ -484,6 +573,13 @@
mBLASTSyncEngine.addToSyncSet(syncId, wc);
}
}
+ // Hierarchy changes
+ final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
+ for (int i = 0, n = hops.size(); i < n; ++i) {
+ final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+ }
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
// Already calls ensureActivityConfig
mService.mRootWindowContainer.ensureActivitiesVisible(
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9acb660..504aa2d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2296,6 +2296,10 @@
return mRemoteToken;
}
+ static WindowContainer fromBinder(IBinder binder) {
+ return RemoteToken.fromBinder(binder).getContainer();
+ }
+
static class RemoteToken extends IWindowContainer.Stub {
final WeakReference<WindowContainer> mWeakRef;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 7172a1b..f7aa3cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -29,10 +29,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -45,8 +45,10 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
import android.view.Display;
import android.view.ITaskOrganizer;
import android.view.IWindowContainer;
@@ -357,6 +359,78 @@
assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
}
+ @Test
+ public void testHierarchyTransaction() {
+ final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
+ ITaskOrganizer listener = new ITaskOrganizer.Stub() {
+ @Override
+ public void taskAppeared(RunningTaskInfo taskInfo) { }
+
+ @Override
+ public void taskVanished(IWindowContainer container) { }
+
+ @Override
+ public void transactionReady(int id, SurfaceControl.Transaction t) { }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo info) {
+ lastReportedTiles.put(info.token.asBinder(), info);
+ }
+ };
+ mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
+ listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+ final ActivityStack stack = createTaskStackOnDisplay(
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final ActivityStack stack2 = createTaskStackOnDisplay(
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
+
+ lastReportedTiles.clear();
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
+ wct.reparent(stack2.mRemoteToken, info2.token, true /* onTop */);
+ mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
+ null /* organizer */);
+ assertFalse(lastReportedTiles.isEmpty());
+ assertEquals(ACTIVITY_TYPE_STANDARD,
+ lastReportedTiles.get(info1.token.asBinder()).topActivityType);
+ assertEquals(ACTIVITY_TYPE_HOME,
+ lastReportedTiles.get(info2.token.asBinder()).topActivityType);
+
+ lastReportedTiles.clear();
+ wct = new WindowContainerTransaction();
+ wct.reparent(stack2.mRemoteToken, info1.token, false /* onTop */);
+ mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
+ null /* organizer */);
+ assertFalse(lastReportedTiles.isEmpty());
+ // Standard should still be on top of tile 1, so no change there
+ assertFalse(lastReportedTiles.containsKey(info1.token.asBinder()));
+ // But tile 2 has no children, so should become undefined
+ assertEquals(ACTIVITY_TYPE_UNDEFINED,
+ lastReportedTiles.get(info2.token.asBinder()).topActivityType);
+
+ // Check the getChildren call
+ List<RunningTaskInfo> children =
+ mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token);
+ assertEquals(2, children.size());
+ children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token);
+ assertEquals(0, children.size());
+
+ lastReportedTiles.clear();
+ wct = new WindowContainerTransaction();
+ wct.reorder(stack2.mRemoteToken, true /* onTop */);
+ mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
+ null /* organizer */);
+ // Home should now be on top. No change occurs in second tile, so not reported
+ assertEquals(1, lastReportedTiles.size());
+ assertEquals(ACTIVITY_TYPE_HOME,
+ lastReportedTiles.get(info1.token.asBinder()).topActivityType);
+ }
+
private List<TaskTile> getTaskTiles(DisplayContent dc) {
ArrayList<TaskTile> out = new ArrayList<>();
for (int i = dc.getStackCount() - 1; i >= 0; --i) {