diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index a0b4e24ca7c2a..3b35b6dfc8899 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -76,6 +76,9 @@ public final class TransitionInfo implements Parcelable { /** The container is the recipient of a transferred starting-window */ public static final int FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT = 1 << 3; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ + public static final int FLAG_FIRST_CUSTOM = 1 << 4; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -252,7 +255,8 @@ public final class TransitionInfo implements Parcelable { * Indication that `change` is independent of parents (ie. it has a different type of * transition vs. "going along for the ride") */ - public static boolean isIndependent(TransitionInfo.Change change, TransitionInfo info) { + public static boolean isIndependent(@NonNull TransitionInfo.Change change, + @NonNull TransitionInfo info) { // If the change has no parent (it is root), then it is independent if (change.getParent() == null) return true; @@ -343,7 +347,7 @@ public final class TransitionInfo implements Parcelable { * Sets the taskinfo of this container if this is a task. WARNING: this takes the * reference, so don't modify it afterwards. */ - public void setTaskInfo(ActivityManager.RunningTaskInfo taskInfo) { + public void setTaskInfo(@Nullable ActivityManager.RunningTaskInfo taskInfo) { mTaskInfo = taskInfo; } @@ -424,8 +428,8 @@ public final class TransitionInfo implements Parcelable { return mEndRotation; } - @Override /** @hide */ + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeTypedObject(mContainer, flags); dest.writeTypedObject(mParent, flags); @@ -454,8 +458,8 @@ public final class TransitionInfo implements Parcelable { } }; - @Override /** @hide */ + @Override public int describeContents() { return 0; } diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index c62ea335de944..26a6f0dcf4703 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -23,6 +23,7 @@ import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -337,6 +338,18 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /** + * Starts a task by id. The task is expected to already exist (eg. as a recent task). + * @param taskId Id of task to start. + * @param options bundle containing ActivityOptions for the task's top activity. + * @hide + */ + @NonNull + public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) { + mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options)); + return this; + } + /** * Merges another WCT into this one. * @param transfer When true, this will transfer everything from other potentially leaving @@ -663,6 +676,11 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT = 2; public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT = 3; public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS = 4; + public static final int HIERARCHY_OP_TYPE_LAUNCH_TASK = 5; + + // The following key(s) are for use with mLaunchOptions: + // When launching a task (eg. from recents), this is the taskId to be launched. + public static final String LAUNCH_KEY_TASK_ID = "android:transaction.hop.taskId"; private final int mType; @@ -678,36 +696,47 @@ public final class WindowContainerTransaction implements Parcelable { final private int[] mWindowingModes; final private int[] mActivityTypes; + private final Bundle mLaunchOptions; + public static HierarchyOp createForReparent( @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) { return new HierarchyOp(HIERARCHY_OP_TYPE_REPARENT, - container, reparent, null, null, toTop); + container, reparent, null, null, toTop, null); } public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) { return new HierarchyOp(HIERARCHY_OP_TYPE_REORDER, - container, container, null, null, toTop); + container, container, null, null, toTop, null); } public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent, IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) { return new HierarchyOp(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT, - currentParent, newParent, windowingModes, activityTypes, onTop); + currentParent, newParent, windowingModes, activityTypes, onTop, null); } public static HierarchyOp createForSetLaunchRoot(IBinder container, int[] windowingModes, int[] activityTypes) { return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT, - container, null, windowingModes, activityTypes, false); + container, null, windowingModes, activityTypes, false, null); } public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) { return new HierarchyOp(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS, - root1, root2, null, null, false); + root1, root2, null, null, false, null); } - private HierarchyOp(int type, @NonNull IBinder container, @Nullable IBinder reparent, - int[] windowingModes, int[] activityTypes, boolean toTop) { + /** Create a hierarchy op for launching a task. */ + public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) { + final Bundle fullOptions = options == null ? new Bundle() : options; + fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId); + return new HierarchyOp(HIERARCHY_OP_TYPE_LAUNCH_TASK, null, null, null, null, true, + fullOptions); + } + + private HierarchyOp(int type, @Nullable IBinder container, @Nullable IBinder reparent, + int[] windowingModes, int[] activityTypes, boolean toTop, + @Nullable Bundle launchOptions) { mType = type; mContainer = container; mReparent = reparent; @@ -716,6 +745,7 @@ public final class WindowContainerTransaction implements Parcelable { mActivityTypes = activityTypes != null ? Arrays.copyOf(activityTypes, activityTypes.length) : null; mToTop = toTop; + mLaunchOptions = launchOptions; } public HierarchyOp(@NonNull HierarchyOp copy) { @@ -725,6 +755,7 @@ public final class WindowContainerTransaction implements Parcelable { mToTop = copy.mToTop; mWindowingModes = copy.mWindowingModes; mActivityTypes = copy.mActivityTypes; + mLaunchOptions = copy.mLaunchOptions; } protected HierarchyOp(Parcel in) { @@ -734,6 +765,7 @@ public final class WindowContainerTransaction implements Parcelable { mToTop = in.readBoolean(); mWindowingModes = in.createIntArray(); mActivityTypes = in.createIntArray(); + mLaunchOptions = in.readBundle(); } public int getType() { @@ -771,6 +803,11 @@ public final class WindowContainerTransaction implements Parcelable { return mActivityTypes; } + @Nullable + public Bundle getLaunchOptions() { + return mLaunchOptions; + } + @Override public String toString() { switch (mType) { @@ -790,6 +827,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "{SetAdjacentRoot: container=" + mContainer + " adjacentRoot=" + mReparent + "}"; + case HIERARCHY_OP_TYPE_LAUNCH_TASK: + return "{LaunchTask: " + mLaunchOptions + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes @@ -805,6 +844,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBoolean(mToTop); dest.writeIntArray(mWindowingModes); dest.writeIntArray(mActivityTypes); + dest.writeBundle(mLaunchOptions); } @Override diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index e3d549bee2e79..2e37fee1fb1ca 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; @@ -38,6 +39,7 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; @@ -191,6 +193,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (type < 0) { throw new IllegalArgumentException("Can't create transition with no type"); } + if (mTransitionController.getTransitionPlayer() == null) { + Slog.w(TAG, "Using shell transitions API for legacy transitions."); + if (t == null) { + throw new IllegalArgumentException("Can't use legacy transitions in" + + " compatibility mode with no WCT."); + } + applyTransaction(t, -1 /* syncId */, null); + return null; + } transition = mTransitionController.createTransition(type); } transition.start(); @@ -346,6 +357,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } effects |= sanitizeAndApplyHierarchyOp(wc, hop); + break; + case HIERARCHY_OP_TYPE_LAUNCH_TASK: + Bundle launchOpts = hop.getLaunchOptions(); + int taskId = launchOpts.getInt( + WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); + launchOpts.remove( + WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); + mService.startActivityFromRecents(taskId, launchOpts); + break; } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index e2b58bcb4bd57..6d552206c9aca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityManager.START_CANCELED; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -55,6 +56,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -68,6 +70,7 @@ import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -1315,6 +1318,26 @@ public class WindowOrganizerTests extends WindowTestsBase { assertFalse(info.topActivityInSizeCompat); } + @Test + public void testStartTasksInTransaction() { + WindowContainerTransaction wct = new WindowContainerTransaction(); + Bundle testOptions = new Bundle(); + testOptions.putInt("test", 20); + wct.startTask(1, null /* options */); + wct.startTask(2, testOptions); + spyOn(mWm.mAtmService); + doReturn(START_CANCELED).when(mWm.mAtmService).startActivityFromRecents(anyInt(), any()); + clearInvocations(mWm.mAtmService); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + + final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(1), bundleCaptor.capture()); + assertTrue(bundleCaptor.getValue().isEmpty()); + + verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(2), bundleCaptor.capture()); + assertEquals(20, bundleCaptor.getValue().getInt("test")); + } + /** * Verifies that task vanished is called for a specific task. */