diff options
7 files changed, 240 insertions, 26 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d7a696f47b7e..e6d81324efa1 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -139,6 +139,7 @@ import com.android.server.wm.BackgroundActivityStartController.BalCode; import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParams; import com.android.server.wm.TaskFragment.EmbeddingCheckResult; +import com.android.wm.shell.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -1723,7 +1724,14 @@ class ActivityStarter { // Get top task at beginning because the order may be changed when reusing existing task. final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask(); final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null; - final Task reusedTask = resolveReusableTask(); + final boolean sourceActivityLaunchedFromBubble = + sourceRecord != null && sourceRecord.getLaunchedFromBubble(); + // if the flag is enabled, allow reusing bubbled tasks only if the source activity is + // bubbled. + final boolean includeLaunchedFromBubble = + Flags.onlyReuseBubbledTaskWhenLaunchedFromBubble() + ? sourceActivityLaunchedFromBubble : true; + final Task reusedTask = resolveReusableTask(includeLaunchedFromBubble); // If requested, freeze the task list if (mOptions != null && mOptions.freezeRecentTasksReordering() @@ -2722,8 +2730,11 @@ class ActivityStarter { /** * Decide whether the new activity should be inserted into an existing task. Returns null * if not or an ActivityRecord with the task into which the new activity should be added. + * + * @param includeLaunchedFromBubble whether a task whose top activity was launched from a bubble + * should be allowed to be reused for the new activity. */ - private Task resolveReusableTask() { + private Task resolveReusableTask(boolean includeLaunchedFromBubble) { // If a target task is specified, try to reuse that one if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) { Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId()); @@ -2767,7 +2778,8 @@ class ActivityStarter { } else { // Otherwise find the best task to put the activity in. intentActivity = - mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea); + mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea, + includeLaunchedFromBubble); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 9c7c41c8525c..f5ab38f72b54 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -314,13 +314,19 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private boolean isDocument; private Uri documentData; - void init(int activityType, String taskAffinity, Intent intent, ActivityInfo info) { + // determines whether to include bubbled tasks. defaults to true to preserve previous + // behavior. + private boolean mIncludeLaunchedFromBubble = true; + + void init(int activityType, String taskAffinity, Intent intent, ActivityInfo info, + boolean includeLaunchedFromBubble) { mActivityType = activityType; mTaskAffinity = taskAffinity; mIntent = intent; mInfo = info; mIdealRecord = null; mCandidateRecord = null; + mIncludeLaunchedFromBubble = includeLaunchedFromBubble; } /** @@ -362,7 +368,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } // Overlays should not be considered as the task's logical top activity. - final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */); + final ActivityRecord r = task.getTopNonFinishingActivity( + false /* includeOverlays */, mIncludeLaunchedFromBubble); if (r == null || r.finishing || r.mUserId != userId || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { @@ -2370,18 +2377,20 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } @Nullable - ActivityRecord findTask(ActivityRecord r, TaskDisplayArea preferredTaskDisplayArea) { + ActivityRecord findTask(ActivityRecord r, TaskDisplayArea preferredTaskDisplayArea, + boolean includeLaunchedFromBubble) { return findTask(r.getActivityType(), r.taskAffinity, r.intent, r.info, - preferredTaskDisplayArea); + preferredTaskDisplayArea, includeLaunchedFromBubble); } @Nullable ActivityRecord findTask(int activityType, String taskAffinity, Intent intent, ActivityInfo info, - TaskDisplayArea preferredTaskDisplayArea) { + TaskDisplayArea preferredTaskDisplayArea, boolean includeLaunchedFromBubble) { ProtoLog.d(WM_DEBUG_TASKS, "Looking for task of type=%s, taskAffinity=%s, intent=%s" - + ", info=%s, preferredTDA=%s", activityType, taskAffinity, intent, info, - preferredTaskDisplayArea); - mTmpFindTaskResult.init(activityType, taskAffinity, intent, info); + + ", info=%s, preferredTDA=%s, includeLaunchedFromBubble=%b", activityType, + taskAffinity, intent, info, preferredTaskDisplayArea, includeLaunchedFromBubble); + mTmpFindTaskResult.init(activityType, taskAffinity, intent, info, + includeLaunchedFromBubble); // Looking up task on preferred display area first ActivityRecord candidateActivity = null; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b8b746a3de7f..d6394503be2f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1101,21 +1101,34 @@ class TaskFragment extends WindowContainer<WindowContainer> { } ActivityRecord getTopNonFinishingActivity() { - return getTopNonFinishingActivity(true /* includeOverlays */); + return getTopNonFinishingActivity( + true /* includeOverlays */, true /* includeLaunchedFromBubble */); } /** * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to * the current user. * @param includeOverlays whether the task overlay activity should be included. + * @param includeLaunchedFromBubble whether activities that were launched from a bubble should + * be included. * @see #topRunningActivity(boolean) */ - ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) { - // Split into 2 to avoid object creation due to variable capture. + ActivityRecord getTopNonFinishingActivity(boolean includeOverlays, + boolean includeLaunchedFromBubble) { + // Split to avoid object creation due to variable capture. if (includeOverlays) { - return getActivity((r) -> !r.finishing); + if (includeLaunchedFromBubble) { + return getActivity(r -> !r.finishing); + } else { + return getActivity(r -> !r.finishing && !r.getLaunchedFromBubble()); + } + } + if (includeLaunchedFromBubble) { + return getActivity(r -> !r.finishing && !r.isTaskOverlay()); + } else { + return getActivity( + r -> !r.finishing && !r.isTaskOverlay() && !r.getLaunchedFromBubble()); } - return getActivity((r) -> !r.finishing && !r.isTaskOverlay()); } ActivityRecord topRunningActivity() { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 1fd8f50b9dec..ff1c6c8fc70c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -95,6 +95,8 @@ import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.service.voice.IVoiceInteractionSession; @@ -112,6 +114,7 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; import com.android.server.wm.utils.MockTracker; +import com.android.wm.shell.Flags; import org.junit.After; import org.junit.Before; @@ -492,7 +495,8 @@ public class ActivityStarterTests extends WindowTestsBase { // Start activity and delivered new intent. starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent); - doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), any()); + doReturn(splitSecondReusableActivity) + .when(mRootWindowContainer).findTask(any(), any(), anyBoolean()); final int result = starter.setReason("testSplitScreenDeliverToTop").execute(); // Ensure result is delivering intent to top. @@ -519,7 +523,8 @@ public class ActivityStarterTests extends WindowTestsBase { // Start activity and delivered new intent. starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent); - doReturn(splitSecondReusableActivity).when(mRootWindowContainer).findTask(any(), any()); + doReturn(splitSecondReusableActivity) + .when(mRootWindowContainer).findTask(any(), any(), anyBoolean()); final int result = starter.setReason("testSplitScreenMoveToFront").execute(); // Ensure result is moving task to front. @@ -566,7 +571,7 @@ public class ActivityStarterTests extends WindowTestsBase { // Start activity and delivered new intent. starter.getIntent().setComponent(activities.get(3).mActivityComponent); - doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any()); + doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any(), anyBoolean()); final int result = starter.setReason("testDesktopModeDeliverToTop").execute(); // Ensure result is delivering intent to top. @@ -593,7 +598,8 @@ public class ActivityStarterTests extends WindowTestsBase { // Start activity and delivered new intent. starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent); - doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any()); + doReturn(desktopModeReusableActivity) + .when(mRootWindowContainer).findTask(any(), any(), anyBoolean()); final int result = starter.setReason("testDesktopModeMoveToFront").execute(); // Ensure result is moving task to front. @@ -755,7 +761,7 @@ public class ActivityStarterTests extends WindowTestsBase { final ActivityRecord baseActivity = new ActivityBuilder(mAtm).setCreateTask(true).build(); baseActivity.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED); - doReturn(baseActivity).when(mRootWindowContainer).findTask(any(), any()); + doReturn(baseActivity).when(mRootWindowContainer).findTask(any(), any(), anyBoolean()); ActivityOptions rawOptions = ActivityOptions.makeBasic() .setPendingIntentCreatorBackgroundActivityStartMode( @@ -1648,6 +1654,120 @@ public class ActivityStarterTests extends WindowTestsBase { assertNotEquals(inTask, target.getTask()); } + @EnableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE) + @Test + public void launchActivity_reusesBubbledTask() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord bubbledActivity = createBubbledActivity(); + + // create the target activity to be launched with the same component as the bubbled activity + final ActivityRecord targetRecord = new ActivityBuilder(mAtm) + .setLaunchMode(LAUNCH_SINGLE_TASK) + .setComponent(ActivityBuilder.getDefaultComponent()).build(); + starter.getIntent().setComponent(bubbledActivity.mActivityComponent); + startActivityInner(starter, targetRecord, bubbledActivity, null /* options */, + null /* inTask */, null /* inTaskFragment */); + + assertEquals(bubbledActivity.getTask(), targetRecord.getTask()); + } + + @EnableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE) + @Test + public void launchActivity_nullSourceRecord_doesNotReuseBubbledTask() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord bubbledActivity = createBubbledActivity(); + + // create the target activity to be launched + final ActivityRecord targetRecord = + new ActivityBuilder(mAtm) + .setLaunchMode(LAUNCH_SINGLE_TASK) + .setComponent(ActivityBuilder.getDefaultComponent()).build(); + starter.getIntent().setComponent(bubbledActivity.mActivityComponent); + + // pass null as the source record + startActivityInner(starter, targetRecord, null, null /* options */, + null /* inTask */, null /* inTaskFragment */); + + assertNotEquals(bubbledActivity.getTask(), targetRecord.getTask()); + } + + @EnableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE) + @Test + public void launchActivity_nonBubbledSourceRecord_doesNotReuseBubbledTask() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord bubbledActivity = createBubbledActivity(); + + // create a non bubbled activity + final ActivityRecord nonBubbleSourceRecord = + new ActivityBuilder(mAtm).setCreateTask(true) + .setLaunchMode(LAUNCH_SINGLE_TASK) + .setComponent(ActivityBuilder.getDefaultComponent()) + .build(); + + // create the target activity to be launched + final ActivityRecord targetRecord = + new ActivityBuilder(mAtm) + .setLaunchMode(LAUNCH_SINGLE_TASK) + .setComponent(ActivityBuilder.getDefaultComponent()).build(); + starter.getIntent().setComponent(bubbledActivity.mActivityComponent); + + // use the non bubbled activity as the source + startActivityInner(starter, targetRecord, nonBubbleSourceRecord, null /* options */, + null /* inTask */, null /* inTaskFragment*/); + + assertNotEquals(bubbledActivity.getTask(), targetRecord.getTask()); + } + + @DisableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE) + @Test + public void launchActivity_nullSourceRecord_flagDisabled_reusesBubbledTask() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord bubbledActivity = createBubbledActivity(); + + // create the target activity to be launched + final ActivityRecord targetRecord = + new ActivityBuilder(mAtm) + .setLaunchMode(LAUNCH_SINGLE_TASK) + .setComponent(ActivityBuilder.getDefaultComponent()).build(); + starter.getIntent().setComponent(bubbledActivity.mActivityComponent); + + // pass null as the source record + startActivityInner(starter, targetRecord, null, null /* options */, + null /* inTask */, null /* inTaskFragment */); + + assertEquals(bubbledActivity.getTask(), targetRecord.getTask()); + } + + @DisableFlags(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE) + @Test + public void launchActivity_fromBubble_flagDisabled_reusesBubbledTask() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord bubbledActivity = createBubbledActivity(); + + // create the target activity to be launched with the same component as the bubbled activity + final ActivityRecord targetRecord = + new ActivityBuilder(mAtm) + .setLaunchMode(LAUNCH_SINGLE_TASK) + .setComponent(ActivityBuilder.getDefaultComponent()).build(); + starter.getIntent().setComponent(bubbledActivity.mActivityComponent); + startActivityInner(starter, targetRecord, bubbledActivity, null /* options */, + null /* inTask */, null /* inTaskFragment */); + + assertEquals(bubbledActivity.getTask(), targetRecord.getTask()); + } + + private ActivityRecord createBubbledActivity() { + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setTaskAlwaysOnTop(true); + opts.setLaunchedFromBubble(true); + opts.setLaunchBounds(new Rect(10, 10, 100, 100)); + return new ActivityBuilder(mAtm) + .setCreateTask(true) + .setComponent(ActivityBuilder.getDefaultComponent()) + .setActivityOptions(opts) + .build(); + } + private static void startActivityInner(ActivityStarter starter, ActivityRecord target, ActivityRecord source, ActivityOptions options, Task inTask, TaskFragment inTaskFragment) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index ce9050456681..0f28528c9327 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -405,11 +405,12 @@ public class RootTaskTests extends WindowTestsBase { final RootWindowContainer.FindTaskResult result = new RootWindowContainer.FindTaskResult(); - result.init(r.getActivityType(), r.taskAffinity, r.intent, r.info); + result.init(r.getActivityType(), r.taskAffinity, r.intent, r.info, true); result.process(task); - assertEquals(r, task.getTopNonFinishingActivity(false /* includeOverlays */)); - assertEquals(taskOverlay, task.getTopNonFinishingActivity(true /* includeOverlays */)); + assertEquals(r, task.getTopNonFinishingActivity(false /* includeOverlays */, true)); + assertEquals( + taskOverlay, task.getTopNonFinishingActivity(true /* includeOverlays */, true)); assertNotNull(result.mIdealRecord); } @@ -432,7 +433,7 @@ public class RootTaskTests extends WindowTestsBase { final ActivityRecord r1 = new ActivityBuilder(mAtm).setComponent( target).setTargetActivity(targetActivity).build(); RootWindowContainer.FindTaskResult result = new RootWindowContainer.FindTaskResult(); - result.init(r1.getActivityType(), r1.taskAffinity, r1.intent, r1.info); + result.init(r1.getActivityType(), r1.taskAffinity, r1.intent, r1.info, true); result.process(parentTask); assertThat(result.mIdealRecord).isNotNull(); @@ -440,7 +441,7 @@ public class RootTaskTests extends WindowTestsBase { final ActivityRecord r2 = new ActivityBuilder(mAtm).setComponent( alias).setTargetActivity(targetActivity).build(); result = new RootWindowContainer.FindTaskResult(); - result.init(r2.getActivityType(), r2.taskAffinity, r2.intent, r2.info); + result.init(r2.getActivityType(), r2.taskAffinity, r2.intent, r2.info, true); result.process(parentTask); assertThat(result.mIdealRecord).isNotNull(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index d88871cd4af7..eb79118fe1c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -147,6 +147,40 @@ public class RootWindowContainerTests extends WindowTestsBase { } @Test + public void testFindTask_includeLaunchedFromBubbled() { + final ComponentName component = ComponentName.createRelative( + DEFAULT_COMPONENT_PACKAGE_NAME, ".BubbledActivity"); + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setTaskAlwaysOnTop(true); + opts.setLaunchedFromBubble(true); + final ActivityRecord activity = new ActivityBuilder(mWm.mAtmService) + .setComponent(component) + .setActivityOptions(opts) + .setCreateTask(true) + .build(); + + assertEquals(activity, mWm.mRoot.findTask(activity, activity.getTaskDisplayArea(), + true /* includeLaunchedFromBubble */)); + } + + @Test + public void testFindTask_ignoreLaunchedFromBubbled() { + final ComponentName component = ComponentName.createRelative( + DEFAULT_COMPONENT_PACKAGE_NAME, ".BubbledActivity"); + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setTaskAlwaysOnTop(true); + opts.setLaunchedFromBubble(true); + final ActivityRecord activity = new ActivityBuilder(mWm.mAtmService) + .setComponent(component) + .setActivityOptions(opts) + .setCreateTask(true) + .build(); + + assertNull(mWm.mRoot.findTask(activity, activity.getTaskDisplayArea(), + false /* includeLaunchedFromBubble */)); + } + + @Test public void testAllPausedActivitiesComplete() { DisplayContent displayContent = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY); ActivityRecord activity = createActivityRecord(displayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index d57a7e61ad63..f94e5e3c38ed 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -56,6 +56,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import android.app.ActivityOptions; import android.content.pm.SigningDetails; import android.content.res.Configuration; import android.graphics.Color; @@ -291,6 +292,30 @@ public class TaskFragmentTest extends WindowTestsBase { } @Test + public void testFindTopNonFinishingActivity_ignoresLaunchedFromBubbleActivities() { + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setTaskAlwaysOnTop(true); + opts.setLaunchedFromBubble(true); + ActivityRecord activity = new ActivityBuilder(mAtm) + .setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID).setActivityOptions(opts).build(); + mTaskFragment.addChild(activity); + + assertNull(mTaskFragment.getTopNonFinishingActivity(true, false)); + } + + @Test + public void testFindTopNonFinishingActivity_includesLaunchedFromBubbleActivities() { + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setTaskAlwaysOnTop(true); + opts.setLaunchedFromBubble(true); + ActivityRecord activity = new ActivityBuilder(mAtm) + .setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID).setActivityOptions(opts).build(); + mTaskFragment.addChild(activity); + + assertEquals(mTaskFragment.getTopNonFinishingActivity(true, true), activity); + } + + @Test public void testMoveTaskToFront_supportsEnterPipOnTaskSwitchForAdjacentTaskFragment() { final Task bottomTask = createTask(mDisplayContent); final ActivityRecord bottomActivity = createActivityRecord(bottomTask); |