diff options
6 files changed, 307 insertions, 10 deletions
diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index bfc42ef8848d..5d5956e4dca4 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -51,6 +51,9 @@ interface ITaskOrganizerController { /** 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 33f21f211a1a..cf34b0bad78d 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.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -36,10 +40,14 @@ import java.util.Map; 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 @@ public class WindowContainerTransaction implements Parcelable { 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 @@ public class WindowContainerTransaction implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeMap(mChanges); + dest.writeList(mHierarchyOps); } @Override @@ -249,4 +287,88 @@ public class WindowContainerTransaction implements Parcelable { } }; } + + /** + * 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 87c91ef6b96c..b1db9d7889dd 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.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS 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.provider.Settings; 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 @@ class Task extends WindowContainer<WindowContainer> { 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 096541f57ba5..0a0530c92a16 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_PRIMAR 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 com.android.internal.util.function.pooled.PooledLambda; 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } } + @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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub 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 9acb660967cb..504aa2dc7b4c 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< 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 7172a1b14244..f7aa3cc9e52c 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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 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.app.ActivityManager.StackInfo; 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 @@ public class TaskOrganizerTests extends WindowTestsBase { 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) { |