diff options
7 files changed, 231 insertions, 11 deletions
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 6936ddc5eeac..59247934ba46 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -655,8 +655,10 @@ public class TaskInfo { + " effectiveUid=" + effectiveUid + " displayId=" + displayId + " isRunning=" + isRunning - + " baseIntent=" + baseIntent + " baseActivity=" + baseActivity - + " topActivity=" + topActivity + " origActivity=" + origActivity + + " baseIntent=" + baseIntent + + " baseActivity=" + baseActivity + + " topActivity=" + topActivity + + " origActivity=" + origActivity + " realActivity=" + realActivity + " numActivities=" + numActivities + " lastActiveTime=" + lastActiveTime diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index c5ee3137e5ba..fa98d0339a65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -22,6 +22,13 @@ import android.annotation.DimenRes import android.app.ActivityManager.RunningTaskInfo import android.app.TaskInfo import android.content.Context +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.pm.ActivityInfo.LAUNCH_MULTIPLE +import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE +import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK +import android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED import android.content.pm.ActivityInfo.isFixedOrientationLandscape import android.content.pm.ActivityInfo.isFixedOrientationPortrait @@ -30,7 +37,9 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Rect import android.os.SystemProperties import android.util.Size +import android.window.DesktopModeFlags import com.android.wm.shell.R +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import kotlin.math.ceil @@ -264,6 +273,58 @@ fun getAppHeaderHeight(context: Context): Int = @DimenRes fun getAppHeaderHeightId(): Int = R.dimen.desktop_mode_freeform_decor_caption_height /** + * Returns the task bounds a launching task should inherit from an existing running instance. + * Returns null if there are no bounds to inherit. + */ +fun getInheritedExistingTaskBounds( + taskRepository: DesktopRepository, + shellTaskOrganizer: ShellTaskOrganizer, + task: RunningTaskInfo, + deskId: Int, +): Rect? { + if (!DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue) return null + val activeTask = taskRepository.getExpandedTasksIdsInDeskOrdered(deskId).firstOrNull() + if (activeTask == null) return null + val lastTask = shellTaskOrganizer.getRunningTaskInfo(activeTask) + val lastTaskTopActivity = lastTask?.topActivity + val currentTaskTopActivity = task.topActivity + val intentFlags = task.baseIntent.flags + val launchMode = task.topActivityInfo?.launchMode ?: LAUNCH_MULTIPLE + return when { + // No running task activity to inherit bounds from. + lastTaskTopActivity == null -> null + // No current top activity to set bounds for. + currentTaskTopActivity == null -> null + // Top task is not an instance of the launching activity, do not inherit its bounds. + lastTaskTopActivity.packageName != currentTaskTopActivity.packageName -> null + // Top task is an instance of launching activity. Activity will be launching in a new + // task with the existing task also being closed. Inherit existing task bounds to + // prevent new task jumping. + (isLaunchingNewTask(launchMode, intentFlags) && isClosingExitingInstance(intentFlags)) -> + lastTask.configuration.windowConfiguration.bounds + else -> null + } +} + +/** + * Returns true if the launch mode or intent will result in a new task being created for the + * activity. + */ +private fun isLaunchingNewTask(launchMode: Int, intentFlags: Int) = + launchMode == LAUNCH_SINGLE_TASK || + launchMode == LAUNCH_SINGLE_INSTANCE || + launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK || + (intentFlags and FLAG_ACTIVITY_NEW_TASK) != 0 + +/** + * Returns true if the intent will result in an existing task instance being closed if a new one + * appears. + */ +private fun isClosingExitingInstance(intentFlags: Int) = + (intentFlags and FLAG_ACTIVITY_CLEAR_TASK) != 0 || + (intentFlags and FLAG_ACTIVITY_MULTIPLE_TASK) == 0 + +/** * Calculates the desired initial bounds for applications in desktop windowing. This is done as a * scale of the screen bounds. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index f67323bb7eb1..b7ae082205cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -32,6 +32,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -2281,11 +2283,19 @@ class DesktopTasksController( wct.reorder(task.token, true) return wct } + val inheritedTaskBounds = + getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId) + if (!taskRepository.isActiveTask(task.taskId) && inheritedTaskBounds != null) { + // Inherit bounds from closing task instance to prevent application jumping different + // cascading positions. + wct.setBounds(task.token, inheritedTaskBounds) + } // TODO(b/365723620): Handle non running tasks that were launched after reboot. // If task is already visible, it must have been handled already and added to desktop mode. - // Cascade task only if it's not visible yet. + // Cascade task only if it's not visible yet and has no inherited bounds. if ( - DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() && + inheritedTaskBounds == null && + DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() && !taskRepository.isVisibleTask(task.taskId) ) { val displayLayout = displayController.getDisplayLayout(task.displayId) @@ -2521,9 +2531,17 @@ class DesktopTasksController( ) { val targetDisplayId = taskRepository.getDisplayForDesk(deskId) val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return - val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId) - if (canChangeTaskPosition(task)) { - wct.setBounds(task.token, initialBounds) + val inheritedTaskBounds = + getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId) + if (inheritedTaskBounds != null) { + // Inherit bounds from closing task instance to prevent application jumping different + // cascading positions. + wct.setBounds(task.token, inheritedTaskBounds) + } else { + val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId) + if (canChangeTaskPosition(task)) { + wct.setBounds(task.token, initialBounds) + } } if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 87b7d344a3ec..d09a7bf05361 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -31,6 +31,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo.CONFIG_DENSITY import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE @@ -1129,6 +1130,54 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES) + fun addMoveToDeskTaskChanges_newTaskInstance_inheritsClosingInstanceBounds() { + // Setup existing task. + val existingTask = setUpFreeformTask(active = true) + val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class") + existingTask.topActivity = testComponent + existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500)) + // Set up new instance of already existing task. + val launchingTask = setUpFullscreenTask() + launchingTask.topActivity = testComponent + launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) + + // Move new instance to desktop. By default multi instance is not supported so first + // instance will close. + val wct = WindowContainerTransaction() + controller.addMoveToDeskTaskChanges(wct, launchingTask, deskId = 0) + + // New instance should inherit task bounds of old instance. + assertThat(findBoundsChange(wct, launchingTask)) + .isEqualTo(existingTask.configuration.windowConfiguration.bounds) + } + + @Test + @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES) + fun handleRequest_newTaskInstance_inheritsClosingInstanceBounds() { + setUpLandscapeDisplay() + // Setup existing task. + val existingTask = setUpFreeformTask(active = true) + val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class") + existingTask.topActivity = testComponent + existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500)) + // Set up new instance of already existing task. + val launchingTask = setUpFreeformTask(active = false) + taskRepository.removeTask(launchingTask.displayId, launchingTask.taskId) + launchingTask.topActivity = testComponent + launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) + + // Move new instance to desktop. By default multi instance is not supported so first + // instance will close. + val wct = controller.handleRequest(Binder(), createTransition(launchingTask)) + + assertNotNull(wct, "should handle request") + val finalBounds = findBoundsChange(wct, launchingTask) + // New instance should inherit task bounds of old instance. + assertThat(finalBounds).isEqualTo(existingTask.configuration.windowConfiguration.bounds) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun handleRequest_newFreeformTaskLaunch_cascadeApplied() { setUpLandscapeDisplay() diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index d466a646c7dd..ddcb5eccb1d8 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -19,6 +19,12 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -131,6 +137,18 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { return RESULT_SKIP; } + if (DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue()) { + ActivityRecord topVisibleFreeformActivity = + task.getDisplayContent().getTopMostVisibleFreeformActivity(); + if (shouldInheritExistingTaskBounds(topVisibleFreeformActivity, activity, task)) { + appendLog("inheriting bounds from existing closing instance"); + outParams.mBounds.set(topVisibleFreeformActivity.getBounds()); + appendLog("final desktop mode task bounds set to %s", outParams.mBounds); + // Return result done to prevent other modifiers from changing or cascading bounds. + return RESULT_DONE; + } + } + DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options, outParams.mBounds, this::appendLog); appendLog("final desktop mode task bounds set to %s", outParams.mBounds); @@ -159,7 +177,7 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { // activity will also enter desktop mode. On this same relationship, we can also assume // if there are not visible freeform tasks but a freeform activity is now launching, it // will force the device into desktop mode. - return (task.getDisplayContent().getTopMostVisibleFreeformActivity() != null + return (task.getDisplayContent().getTopMostFreeformActivity() != null && checkSourceWindowModesCompatible(task, options, currentParams)) || isRequestingFreeformWindowMode(task, options, currentParams); } @@ -201,6 +219,40 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { }; } + /** + * Whether the launching task should inherit the task bounds of an existing closing instance. + */ + private boolean shouldInheritExistingTaskBounds( + @Nullable ActivityRecord existingTaskActivity, + @Nullable ActivityRecord launchingActivity, + @NonNull Task launchingTask) { + if (existingTaskActivity == null || launchingActivity == null) return false; + return (existingTaskActivity.packageName == launchingActivity.packageName) + && isLaunchingNewTask(launchingActivity.launchMode, + launchingTask.getBaseIntent().getFlags()) + && isClosingExitingInstance(launchingTask.getBaseIntent().getFlags()); + } + + /** + * Returns true if the launch mode or intent will result in a new task being created for the + * activity. + */ + private boolean isLaunchingNewTask(int launchMode, int intentFlags) { + return launchMode == LAUNCH_SINGLE_TASK + || launchMode == LAUNCH_SINGLE_INSTANCE + || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK + || (intentFlags & FLAG_ACTIVITY_NEW_TASK) != 0; + } + + /** + * Returns true if the intent will result in an existing task instance being closed if a new + * one appears. + */ + private boolean isClosingExitingInstance(int intentFlags) { + return (intentFlags & FLAG_ACTIVITY_CLEAR_TASK) != 0 + || (intentFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0; + } + private void initLogBuilder(Task task, ActivityRecord activity) { if (DEBUG) { mLogBuilder = new StringBuilder( diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 466ed7863c84..772a7fdfc684 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2006,11 +2006,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return getActivity(r -> !r.finishing, true /* traverseTopToBottom */); } - ActivityRecord getTopMostVisibleFreeformActivity() { + ActivityRecord getTopMostFreeformActivity() { return getActivity(r -> r.isVisibleRequested() && r.inFreeformWindowingMode(), true /* traverseTopToBottom */); } + ActivityRecord getTopMostVisibleFreeformActivity() { + return getActivity(r -> r.isVisible() && r.inFreeformWindowingMode(), + true /* traverseTopToBottom */); + } + ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) { // Break down into 4 calls to avoid object creation due to capturing input params. if (includeFinishing) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index bc37496d14a7..e87e107cd793 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE; @@ -157,7 +158,7 @@ public class DesktopModeLaunchParamsModifierTests extends @Test @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX}) - public void testReturnsContinueIfVisibleFreeformTaskExists() { + public void testReturnsContinueIfFreeformTaskExists() { setupDesktopModeLaunchParamsModifier(); when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod(); @@ -165,7 +166,7 @@ public class DesktopModeLaunchParamsModifierTests extends final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); doReturn(existingFreeformTask.getRootActivity()).when(dc) - .getTopMostVisibleFreeformActivity(); + .getTopMostFreeformActivity(); final Task launchingTask = new TaskBuilder(mSupervisor).build(); launchingTask.onDisplayChanged(dc); @@ -269,6 +270,38 @@ public class DesktopModeLaunchParamsModifierTests extends } @Test + @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES}) + public void testInheritTaskBoundsFromExistingInstanceIfClosing() { + setupDesktopModeLaunchParamsModifier(); + + final String packageName = "com.same.package"; + // Setup existing task. + final DisplayContent dc = spy(createNewDisplay()); + final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM).setPackage(packageName).build(); + existingFreeformTask.setBounds( + /* left */ 0, + /* top */ 0, + /* right */ 500, + /* bottom */ 500); + doReturn(existingFreeformTask.getRootActivity()).when(dc) + .getTopMostVisibleFreeformActivity(); + // Set up new instance of already existing task. By default multi instance is not supported + // so first instance will close. + final Task launchingTask = new TaskBuilder(mSupervisor).setPackage(packageName) + .setCreateActivity(true).build(); + launchingTask.onDisplayChanged(dc); + launchingTask.intent.addFlags(FLAG_ACTIVITY_NEW_TASK); + + // New instance should inherit task bounds of old instance. + assertEquals(RESULT_DONE, + new CalculateRequestBuilder().setTask(launchingTask) + .setActivity(launchingTask.getRootActivity()).calculate()); + assertEquals(existingFreeformTask.getBounds(), mResult.mBounds); + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() { |