From 7e3fb8e790ab236af55dc368675d77ae6a234e3c Mon Sep 17 00:00:00 2001 From: Xu Cao Date: Fri, 22 Jul 2022 11:31:06 -0700 Subject: [PATCH] Launch all activities into the same display When multiple activities are launched through a PendingIntent, all activities need to be launched onto the same display. To ensure that, all display-related information from the passed-in SafeActivityOptions object are used to create a new SafeActivityOptions object for bottom activities. Bug: 225102873 Bug: 215308769 Test: atest StartActivitiesTest#testStartActivities_shouldLaunchOnSameDisplay Change-Id: Ie42913194847578972320a135ee753bffaa3f27d --- .../server/wm/ActivityStartController.java | 10 ++++++- .../server/wm/SafeActivityOptions.java | 28 +++++++++++++++++++ .../server/wm/SafeActivityOptionsTest.java | 20 +++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 7c25b53dc4204..0707b818f0930 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -384,6 +384,14 @@ public class ActivityStartController { callingUid, realCallingUid, UserHandle.USER_NULL); final SparseArray startingUidPkgs = new SparseArray<>(); final long origId = Binder.clearCallingIdentity(); + + SafeActivityOptions bottomOptions = null; + if (options != null) { + // To ensure the first N-1 activities (N == total # of activities) are also launched + // into the correct display, use a copy of the passed-in options (keeping only + // display-related info) for these activities. + bottomOptions = options.selectiveCloneDisplayOptions(); + } try { intents = ArrayUtils.filterNotNull(intents, Intent[]::new); final ActivityStarter[] starters = new ActivityStarter[intents.length]; @@ -432,7 +440,7 @@ public class ActivityStartController { final boolean top = i == intents.length - 1; final SafeActivityOptions checkedOptions = top ? options - : null; + : bottomOptions; starters[i] = obtainStarter(intent, reason) .setIntentGrants(intentGrants) .setCaller(caller) diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 2879e33fb71ac..8bacacd1451c4 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -115,6 +115,34 @@ public class SafeActivityOptions { mOriginalOptions = options; } + /** + * To ensure that two activities, one using this object, and the other using the + * SafeActivityOptions returned from this function, are launched into the same display through + * ActivityStartController#startActivities, all display-related information, i.e. + * displayAreaToken, launchDisplayId and callerDisplayId, are cloned. + */ + @Nullable SafeActivityOptions selectiveCloneDisplayOptions() { + final ActivityOptions options = cloneLaunchingDisplayOptions(mOriginalOptions); + final ActivityOptions callerOptions = cloneLaunchingDisplayOptions(mCallerOptions); + if (options == null && callerOptions == null) { + return null; + } + + final SafeActivityOptions safeOptions = new SafeActivityOptions(options, + mOriginalCallingPid, mOriginalCallingUid); + safeOptions.mCallerOptions = callerOptions; + safeOptions.mRealCallingPid = mRealCallingPid; + safeOptions.mRealCallingUid = mRealCallingUid; + return safeOptions; + } + + private ActivityOptions cloneLaunchingDisplayOptions(ActivityOptions options) { + return options == null ? null : ActivityOptions.makeBasic() + .setLaunchTaskDisplayArea(options.getLaunchTaskDisplayArea()) + .setLaunchDisplayId(options.getLaunchDisplayId()) + .setCallerDisplayId((options.getCallerDisplayId())); + } + /** * Overrides options with options from a caller and records {@link Binder#getCallingPid}/ * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when calling this diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index 71118521acf5b..e57ad5d9ff8cc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -17,9 +17,12 @@ package com.android.server.wm; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; import android.app.ActivityOptions; import android.platform.test.annotations.Presubmit; +import android.window.WindowContainerToken; import androidx.test.filters.MediumTest; @@ -43,4 +46,21 @@ public class SafeActivityOptionsTest { final ActivityOptions result = options.mergeActivityOptions(opts1, opts2); assertEquals(6, result.getLaunchDisplayId()); } + + @Test + public void test_selectiveCloneDisplayOptions() { + final WindowContainerToken token = mock(WindowContainerToken.class); + final int launchDisplayId = 5; + final int callerDisplayId = 6; + + final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic() + .setLaunchTaskDisplayArea(token) + .setLaunchDisplayId(launchDisplayId) + .setCallerDisplayId(callerDisplayId)) + .selectiveCloneDisplayOptions(); + + assertSame(clone.getOriginalOptions().getLaunchTaskDisplayArea(), token); + assertEquals(clone.getOriginalOptions().getLaunchDisplayId(), launchDisplayId); + assertEquals(clone.getOriginalOptions().getCallerDisplayId(), callerDisplayId); + } }