diff options
13 files changed, 144 insertions, 11 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index c0cd6386aec2..cae2d6d50d2e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3733,6 +3733,7 @@ package android.window { public final class TaskFragmentInfo implements android.os.Parcelable { method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo); method @NonNull public java.util.List<android.os.IBinder> getActivities(); + method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment(); method @NonNull public android.content.res.Configuration getConfiguration(); method @NonNull public android.os.IBinder getFragmentToken(); method @NonNull public android.graphics.Point getPositionInParent(); diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 44327af928cb..b35e87b541d3 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -598,6 +598,23 @@ public class ActivityClient { } /** + * Returns {@code true} if the activity was explicitly requested to be launched in the + * TaskFragment. + * + * @param activityToken The token of the Activity. + * @param taskFragmentToken The token of the TaskFragment. + */ + public boolean isRequestedToLaunchInTaskFragment(IBinder activityToken, + IBinder taskFragmentToken) { + try { + return getActivityClientController().isRequestedToLaunchInTaskFragment(activityToken, + taskFragmentToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Shows or hides a Camera app compat toggle for stretched issues with the requested state. * * @param token The token for the window that needs a control. diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 5136b2033d1c..a3c5e1c67e1b 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -181,4 +181,14 @@ interface IActivityClientController { * that started the task. */ void enableTaskLocaleOverride(in IBinder token); + + /** + * Return {@code true} if the activity was explicitly requested to be launched in the + * TaskFragment. + * + * @param activityToken The token of the Activity. + * @param taskFragmentToken The token of the TaskFragment. + */ + boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken, + in IBinder taskFragmentToken); } diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index a881a05a1054..fa5195727afe 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -66,6 +66,13 @@ public final class TaskFragmentInfo implements Parcelable { @NonNull private final List<IBinder> mActivities = new ArrayList<>(); + /** + * List of Activity tokens that were explicitly requested to be launched in this TaskFragment. + * It only contains Activities that belong to the organizer process for security. + */ + @NonNull + private final List<IBinder> mInRequestedTaskFragmentActivities = new ArrayList<>(); + /** Relative position of the fragment's top left corner in the parent container. */ private final Point mPositionInParent = new Point(); @@ -99,15 +106,18 @@ public final class TaskFragmentInfo implements Parcelable { public TaskFragmentInfo( @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token, @NonNull Configuration configuration, int runningActivityCount, - boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent, - boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip, - boolean isClearedForReorderActivityToFront, @NonNull Point minimumDimensions) { + boolean isVisible, @NonNull List<IBinder> activities, + @NonNull List<IBinder> inRequestedTaskFragmentActivities, + @NonNull Point positionInParent, boolean isTaskClearedForReuse, + boolean isTaskFragmentClearedForPip, boolean isClearedForReorderActivityToFront, + @NonNull Point minimumDimensions) { mFragmentToken = requireNonNull(fragmentToken); mToken = requireNonNull(token); mConfiguration.setTo(configuration); mRunningActivityCount = runningActivityCount; mIsVisible = isVisible; mActivities.addAll(activities); + mInRequestedTaskFragmentActivities.addAll(inRequestedTaskFragmentActivities); mPositionInParent.set(positionInParent); mIsTaskClearedForReuse = isTaskClearedForReuse; mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip; @@ -151,6 +161,11 @@ public final class TaskFragmentInfo implements Parcelable { return mActivities; } + @NonNull + public List<IBinder> getActivitiesRequestedInTaskFragment() { + return mInRequestedTaskFragmentActivities; + } + /** Returns the relative position of the fragment's top left corner in the parent container. */ @NonNull public Point getPositionInParent() { @@ -215,6 +230,8 @@ public final class TaskFragmentInfo implements Parcelable { && mIsVisible == that.mIsVisible && getWindowingMode() == that.getWindowingMode() && mActivities.equals(that.mActivities) + && mInRequestedTaskFragmentActivities.equals( + that.mInRequestedTaskFragmentActivities) && mPositionInParent.equals(that.mPositionInParent) && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse && mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip @@ -229,6 +246,7 @@ public final class TaskFragmentInfo implements Parcelable { mRunningActivityCount = in.readInt(); mIsVisible = in.readBoolean(); in.readBinderList(mActivities); + in.readBinderList(mInRequestedTaskFragmentActivities); mPositionInParent.readFromParcel(in); mIsTaskClearedForReuse = in.readBoolean(); mIsTaskFragmentClearedForPip = in.readBoolean(); @@ -245,6 +263,7 @@ public final class TaskFragmentInfo implements Parcelable { dest.writeInt(mRunningActivityCount); dest.writeBoolean(mIsVisible); dest.writeBinderList(mActivities); + dest.writeBinderList(mInRequestedTaskFragmentActivities); mPositionInParent.writeToParcel(dest, flags); dest.writeBoolean(mIsTaskClearedForReuse); dest.writeBoolean(mIsTaskFragmentClearedForPip); @@ -274,6 +293,7 @@ public final class TaskFragmentInfo implements Parcelable { + " runningActivityCount=" + mRunningActivityCount + " isVisible=" + mIsVisible + " activities=" + mActivities + + " inRequestedTaskFragmentActivities" + mInRequestedTaskFragmentActivities + " positionInParent=" + mPositionInParent + " isTaskClearedForReuse=" + mIsTaskClearedForReuse + " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 2c1ddf7cfcbe..95823b803522 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -748,8 +748,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void onActivityCreated(@NonNull WindowContainerTransaction wct, @NonNull Activity launchedActivity) { - // TODO(b/229680885): we don't support launching into primary yet because we want to always - // launch the new activity on top. resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */); updateCallbackIfNecessary(); } @@ -785,6 +783,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } + final TaskFragmentContainer container = getContainerWithActivity(activity); + if (!isOnReparent && container != null + && container.getTaskContainer().getTopTaskFragmentContainer() != container) { + // Do not resolve if the launched activity is not the top-most container in the Task. + return true; + } + /* * We will check the following to see if there is any embedding rule matched: * 1. Whether the new launched activity should always expand. @@ -807,6 +812,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } + // Skip resolving the following split-rules if the launched activity has been requested + // to be launched into its current container. + if (container != null && container.isActivityInRequestedTaskFragment( + activity.getActivityToken())) { + return true; + } + // 3. Whether the new launched activity has already been in a split with a rule matched. if (isNewActivityInSplitWithRuleMatched(activity)) { return true; @@ -2060,6 +2072,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!container.hasActivity(activityToken) && container.getTaskFragmentToken() .equals(initialTaskFragmentToken)) { + if (ActivityClient.getInstance().isRequestedToLaunchInTaskFragment( + activityToken, initialTaskFragmentToken)) { + container.addPendingAppearedInRequestedTaskFragmentActivity( + activity); + } + // The onTaskFragmentInfoChanged callback containing this activity has // not reached the client yet, so add the activity to the pending // appeared activities. @@ -2151,6 +2169,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/232042367): Consolidate the activity create handling so that we can handle // cross-process the same as normal. + // Early return if the launching taskfragment is already been set. + if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { + synchronized (mLock) { + mCurrentIntent = intent; + } + return super.onStartActivity(who, intent, options); + } + final Activity launchingActivity; if (who instanceof Activity) { // We will check if the new activity should be split with the activity that launched diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index b38f8245a2aa..6c553a836dbd 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -86,6 +86,12 @@ class TaskFragmentContainer { @Nullable private Intent mPendingAppearedIntent; + /** + * The activities that were explicitly requested to be launched in its current TaskFragment, + * but haven't been added to {@link #mInfo} yet. + */ + final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>(); + /** Containers that are dependent on this one and should be completely destroyed on exit. */ private final List<TaskFragmentContainer> mContainersToFinishOnExit = new ArrayList<>(); @@ -296,6 +302,8 @@ class TaskFragmentContainer { void removePendingAppearedActivity(@NonNull IBinder activityToken) { mPendingAppearedActivities.remove(activityToken); + // Also remove the activity from the mPendingInRequestedTaskFragmentActivities. + mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken); } @GuardedBy("mController.mLock") @@ -424,7 +432,7 @@ class TaskFragmentContainer { for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) { final IBinder activityToken = mPendingAppearedActivities.get(i); if (infoActivities.contains(activityToken)) { - mPendingAppearedActivities.remove(i); + removePendingAppearedActivity(activityToken); } } } @@ -720,6 +728,29 @@ class TaskFragmentContainer { mLastCompanionTaskFragment = fragmentToken; } + /** + * Adds the pending appeared activity that has requested to be launched in this task fragment. + * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment + */ + void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) { + final IBinder activityToken = activity.getActivityToken(); + if (hasActivity(activityToken)) { + return; + } + mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken()); + } + + /** + * Checks if the given activity has requested to be launched in this task fragment. + * @see #addPendingAppearedInRequestedTaskFragmentActivity + */ + boolean isActivityInRequestedTaskFragment(IBinder activityToken) { + if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) { + return true; + } + return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken); + } + /** Gets the parent leaf Task id. */ int getTaskId() { return mTaskContainer.getTaskId(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index 459ec9f89c4a..a069ac7256d6 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -177,6 +177,7 @@ public class EmbeddingTestUtils { 1, isVisible, Collections.singletonList(activity.getActivityToken()), + new ArrayList<>(), new Point(), false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index bbb454d31c38..dd087e8eb7c9 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -124,7 +124,7 @@ public class JetpackTaskFragmentOrganizerTest { private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, - false /* isVisible */, new ArrayList<>(), new Point(), + false /* isVisible */, new ArrayList<>(), new ArrayList<>(), new Point(), false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */, false /* isClearedForReorderActivityToFront */, new Point()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 17909d4a0763..88dbcd7bd631 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -696,7 +696,7 @@ public class SplitControllerTest { final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); - assertFalse(result); + assertTrue(result); verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), any(), anyBoolean()); } @@ -730,7 +730,7 @@ public class SplitControllerTest { final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); - assertFalse(result); + assertTrue(result); verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), any(), anyBoolean()); } @@ -804,7 +804,7 @@ public class SplitControllerTest { final Activity launchedActivity = createMockActivity(); primaryContainer.addPendingAppearedActivity(launchedActivity); - assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity, + assertTrue(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity, false /* isOnReparent */)); } @@ -940,7 +940,7 @@ public class SplitControllerTest { boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); - assertFalse(result); + assertTrue(result); assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity)); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 01e9522044a1..d108f0de5d15 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1714,4 +1714,20 @@ class ActivityClientController extends IActivityClientController.Stub { } } } + + /** + * Returns {@code true} if the activity was explicitly requested to be launched in its + * current TaskFragment. + * + * @see ActivityRecord#mRequestedLaunchingTaskFragmentToken + */ + public boolean isRequestedToLaunchInTaskFragment(IBinder activityToken, + IBinder taskFragmentToken) { + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.isInRootTaskLocked(activityToken); + if (r == null) return false; + + return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken; + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3545747a67e3..59cd0c2242bd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -560,6 +560,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean inHistory; // are we in the history task? final ActivityTaskSupervisor mTaskSupervisor; final RootWindowContainer mRootWindowContainer; + // The token of the TaskFragment that this activity was requested to be launched into. + IBinder mRequestedLaunchingTaskFragmentToken; // Tracking splash screen status from previous activity boolean mSplashScreenStyleSolidColor = false; @@ -1600,6 +1602,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (oldParent != null) { oldParent.cleanUpActivityReferences(this); + // Clear the state as this activity is removed from its old parent. + mRequestedLaunchingTaskFragmentToken = null; } if (newParent != null) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 32dac49102bd..fbcf0fa3aeb8 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2957,6 +2957,8 @@ class ActivityStarter { int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task); if (embeddingCheckResult == EMBEDDING_ALLOWED) { newParent = mInTaskFragment; + mStartActivity.mRequestedLaunchingTaskFragmentToken = + mInTaskFragment.getFragmentToken(); } else { // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment. sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index c2afeaf8990e..8ad6012db201 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2574,6 +2574,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ TaskFragmentInfo getTaskFragmentInfo() { List<IBinder> childActivities = new ArrayList<>(); + List<IBinder> inRequestedTaskFragmentActivities = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { final WindowContainer<?> wc = getChildAt(i); final ActivityRecord ar = wc.asActivityRecord(); @@ -2582,6 +2583,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) { // Only includes Activities that belong to the organizer process for security. childActivities.add(ar.token); + if (ar.mRequestedLaunchingTaskFragmentToken == mFragmentToken) { + inRequestedTaskFragmentActivities.add(ar.token); + } } } final Point positionInParent = new Point(); @@ -2593,6 +2597,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { getNonFinishingActivityCount(), shouldBeVisible(null /* starting */), childActivities, + inRequestedTaskFragmentActivities, positionInParent, mClearedTaskForReuse, mClearedTaskFragmentForPip, |