Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dba289df authored by Eghosa Ewansiha-Vlachavas's avatar Eghosa Ewansiha-Vlachavas
Browse files

[1/n] Inherit bounds for same task trampoline launches

If a task trampoline is launching from the same package, and the
orignal instance/task will be closed, inherit the original task bounds
to prevent the application from jumping from different cascading
positions.

Flag: com.android.window.flags.inherit_task_bounds_for_trampoline_task_launches
Test: atest WmTests:DesktopModeLaunchParamsModifierTests,
      atest WMShellUnitTests:DesktopTasksControllerTest
Fixes: 392815318
Bug:391669775

Change-Id: Id7f516b9956d938564300f04f834dcc550930369
parent 449beb9f
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -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
+61 −0
Original line number Diff line number Diff line
@@ -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
@@ -263,6 +272,58 @@ fun getAppHeaderHeight(context: Context): Int =
/** Returns the resource id of the app header height in desktop mode. */
@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.
+23 −5
Original line number Diff line number Diff line
@@ -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,10 +2283,18 @@ 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 (
            inheritedTaskBounds == null &&
                DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
                !taskRepository.isVisibleTask(task.taskId)
        ) {
@@ -2521,10 +2531,18 @@ class DesktopTasksController(
    ) {
        val targetDisplayId = taskRepository.getDisplayForDesk(deskId)
        val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return
        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)
        } else {
+49 −0
Original line number Diff line number Diff line
@@ -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
@@ -1128,6 +1129,54 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        assertThat(finalBounds).isEqualTo(Rect())
    }

    @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() {
+53 −1
Original line number Diff line number Diff line
@@ -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(
Loading