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

Commit 1e20bf87 authored by Jerry Chang's avatar Jerry Chang Committed by Android (Google) Code Review
Browse files

Merge "Make multi-instances split opt-in based" into tm-qpr-dev

parents 2d8ced93 15f43b53
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -111,4 +111,8 @@

    <!-- Whether to dim a split-screen task when the other is the IME target -->
    <bool name="config_dimNonImeAttachedSide">true</bool>

    <!-- Components support to launch multiple instances into split-screen -->
    <string-array name="config_componentsSupportMultiInstancesSplit">
    </string-array>
</resources>
+1 −1
Original line number Diff line number Diff line
@@ -601,7 +601,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        animator.start();
    }

    /** Swich both surface position with animation. */
    /** Switch both surface position with animation. */
    public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
            SurfaceControl leash2, Consumer<Rect> finishCallback) {
        final boolean isLandscape = isLandscape();
+4 −5
Original line number Diff line number Diff line
@@ -86,8 +86,8 @@ interface ISplitScreen {
    /**
     * Starts a pair of intent and task in one transition.
     */
    oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
            in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
    oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
            in Bundle options2, int sidePosition, float splitRatio,
            in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;

    /**
@@ -108,9 +108,8 @@ interface ISplitScreen {
     * Starts a pair of intent and task using legacy transition system.
     */
    oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
            in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
            int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
            in InstanceId instanceId) = 12;
            in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
            in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;

    /**
     * Starts a pair of shortcut and task using legacy transition system.
+154 −62
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;

import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@ import android.window.WindowContainerTransaction;

import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
    private final IconProvider mIconProvider;
    private final Optional<RecentTasksController> mRecentTasksOptional;
    private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
    private final String[] mMultiInstancesComponents;

    @VisibleForTesting
    StageCoordinator mStageCoordinator;

    private StageCoordinator mStageCoordinator;
    // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
    // outside the bounds of the roots by being reparented into a higher level fullscreen container
    private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
            shellInit.addInitCallback(this::onInit, this);
        }

        // TODO(255224696): Remove the config once having a way for client apps to opt-in
        //                  multi-instances split.
        mMultiInstancesComponents = mContext.getResources()
                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
    }

    @VisibleForTesting
    SplitScreenController(Context context,
            ShellInit shellInit,
            ShellCommandHandler shellCommandHandler,
            ShellController shellController,
            ShellTaskOrganizer shellTaskOrganizer,
            SyncTransactionQueue syncQueue,
            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
            DisplayController displayController,
            DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController,
            DragAndDropController dragAndDropController,
            Transitions transitions,
            TransactionPool transactionPool,
            IconProvider iconProvider,
            RecentTasksController recentTasks,
            ShellExecutor mainExecutor,
            StageCoordinator stageCoordinator) {
        mShellCommandHandler = shellCommandHandler;
        mShellController = shellController;
        mTaskOrganizer = shellTaskOrganizer;
        mSyncQueue = syncQueue;
        mContext = context;
        mRootTDAOrganizer = rootTDAOrganizer;
        mMainExecutor = mainExecutor;
        mDisplayController = displayController;
        mDisplayImeController = displayImeController;
        mDisplayInsetsController = displayInsetsController;
        mDragAndDropController = dragAndDropController;
        mTransitions = transitions;
        mTransactionPool = transactionPool;
        mIconProvider = iconProvider;
        mRecentTasksOptional = Optional.of(recentTasks);
        mStageCoordinator = stageCoordinator;
        mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
        shellInit.addInitCallback(this::onInit, this);
        mMultiInstancesComponents = mContext.getResources()
                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
    }

    public SplitScreen asSplitScreen() {
@@ -471,62 +521,99 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        startIntent(intent, fillInIntent, position, options);
    }

    private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
            @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
            @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
            InstanceId instanceId) {
        Intent fillInIntent = null;
        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
                && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
            fillInIntent = new Intent();
            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
        }
        mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
                options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
    }

    private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
            int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
            float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
        Intent fillInIntent = null;
        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
                && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
            fillInIntent = new Intent();
            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
        }
        mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
                options2, splitPosition, splitRatio, remoteTransition, instanceId);
    }

    @Override
    public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
            @SplitPosition int position, @Nullable Bundle options) {
        if (fillInIntent == null) {
            fillInIntent = new Intent();
        }
        // Flag this as a no-user-action launch to prevent sending user leaving event to the
        // current top activity since it's going to be put into another side of the split. This
        // prevents the current top activity from going into pip mode due to user leaving event.
        // Flag this as a no-user-action launch to prevent sending user leaving event to the current
        // top activity since it's going to be put into another side of the split. This prevents the
        // current top activity from going into pip mode due to user leaving event.
        if (fillInIntent == null) fillInIntent = new Intent();
        fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);

        // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
        // split and there is no reusable background task.
        if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
            final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
                    ? mRecentTasksOptional.get().findTaskInBackground(
                            intent.getIntent().getComponent())
                    : null;
        if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
            final ComponentName launching = intent.getIntent().getComponent();
            if (supportMultiInstancesSplit(launching)) {
                // To prevent accumulating large number of instances in the background, reuse task
                // in the background with priority.
                final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
                        .map(recentTasks -> recentTasks.findTaskInBackground(launching))
                        .orElse(null);
                if (taskInfo != null) {
                    startTask(taskInfo.taskId, position, options);
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                            "Start task in background");
                    return;
                }

                // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
                // the split and there is no reusable background task.
                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
        }

        if (!ENABLE_SHELL_TRANSITIONS) {
            mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
            } else if (isSplitScreenVisible()) {
                mStageCoordinator.switchSplitPosition("startIntent");
                return;
            }
        }

        mStageCoordinator.startIntent(intent, fillInIntent, position, options);
    }

    /** Returns {@code true} if it's launching the same component on both sides of the split. */
    @VisibleForTesting
    boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
        if (startIntent == null) {
            return false;
        }
    private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
            @SplitPosition int position, int taskId) {
        if (pendingIntent == null || pendingIntent.getIntent() == null) return false;

        final ComponentName launchingActivity = startIntent.getComponent();
        if (launchingActivity == null) {
        final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
        if (launchingActivity == null) return false;

        if (taskId != INVALID_TASK_ID) {
            final ActivityManager.RunningTaskInfo taskInfo =
                    mTaskOrganizer.getRunningTaskInfo(taskId);
            if (taskInfo != null) {
                return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
            }
            return false;
        }

        if (isSplitScreenVisible()) {
            // To prevent users from constantly dropping the same app to the same side resulting in
            // a large number of instances in the background.
            final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
            final ComponentName targetActivity = targetTaskInfo != null
                    ? targetTaskInfo.baseIntent.getComponent() : null;
            if (Objects.equals(launchingActivity, targetActivity)) {
        if (!isSplitScreenVisible()) {
            // Split screen is not yet activated, check if the current top running task is valid to
            // split together.
            final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
            if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
                return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
            }
            return false;
        }

            // Allow users to start a new instance the same to adjacent side.
        // Compare to the adjacent side of the split to determine if this is launching the same
        // component adjacently.
        final ActivityManager.RunningTaskInfo pairedTaskInfo =
                getTaskInfo(SplitLayout.reversePosition(position));
        final ComponentName pairedActivity = pairedTaskInfo != null
@@ -534,9 +621,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        return Objects.equals(launchingActivity, pairedActivity);
    }

        final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
        if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
            return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
    @VisibleForTesting
    /** Returns {@code true} if the component supports multi-instances split. */
    boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
        if (launching == null) return false;

        final String componentName = launching.flattenToString();
        for (int i = 0; i < mMultiInstancesComponents.length; i++) {
            if (mMultiInstancesComponents[i].equals(componentName)) {
                return true;
            }
        }

        return false;
@@ -839,14 +933,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,

        @Override
        public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
                Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
                int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
                InstanceId instanceId) {
                Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
                RemoteAnimationAdapter adapter, InstanceId instanceId) {
            executeRemoteCallWithTaskPermission(mController,
                    "startIntentAndTaskWithLegacyTransition", (controller) ->
                            controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
                                    pendingIntent, fillInIntent, options1, taskId, options2,
                                    splitPosition, splitRatio, adapter, instanceId));
                            controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
                                    options1, taskId, options2, splitPosition, splitRatio, adapter,
                                    instanceId));
        }

        @Override
@@ -872,14 +965,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        }

        @Override
        public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
                @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
                @SplitPosition int splitPosition, float splitRatio,
                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
        public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
                int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
                float splitRatio, @Nullable RemoteTransition remoteTransition,
                InstanceId instanceId) {
            executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
                    (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
                            fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
                            remoteTransition, instanceId));
                    (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
                            options2, splitPosition, splitRatio, remoteTransition, instanceId));
        }

        @Override
+17 −25
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -428,6 +427,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    /** Launches an activity into split. */
    void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
            @Nullable Bundle options) {
        if (!ENABLE_SHELL_TRANSITIONS) {
            startIntentLegacy(intent, fillInIntent, position, options);
            return;
        }

        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
        prepareEvictChildTasks(position, evictWct);
@@ -441,13 +445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        prepareEnterSplitScreen(wct, null /* taskInfo */, position);

        mSplitTransitions.startEnterTransition(transitType, wct, null, this,
                aborted -> {
                    // Switch the split position if launching as MULTIPLE_TASK failed.
                    if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
                        setSideStagePositionAnimated(
                                SplitLayout.reversePosition(mSideStagePosition));
                    }
                } /* consumedCallback */,
                null /* consumedCallback */,
                (finishWct, finishT) -> {
                    if (!evictWct.isEmpty()) {
                        finishWct.merge(evictWct, true);
@@ -457,7 +455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    /** Launches an activity into split by legacy transition. */
    void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
            @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
            @SplitPosition int position, @Nullable Bundle options) {
        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
        prepareEvictChildTasks(position, evictWct);

@@ -473,12 +471,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                                exitSplitScreen(mMainStage.getChildCount() == 0
                                        ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
                        mSplitUnsupportedToast.show();
                    } else {
                        // Switch the split position if launching as MULTIPLE_TASK failed.
                        if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
                            setSideStagePosition(SplitLayout.reversePosition(
                                    getSideStagePosition()), null);
                        }
                    }

                    // Do nothing when the animation was cancelled.
@@ -771,9 +763,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mSideStage.evictInvisibleChildren(wct);
    }

    Bundle resolveStartStage(@StageType int stage,
            @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
            @androidx.annotation.Nullable WindowContainerTransaction wct) {
    Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
            @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
        switch (stage) {
            case STAGE_TYPE_UNDEFINED: {
                if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                : mMainStage.getTopVisibleChildTaskId();
    }

    void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
        if (mSideStagePosition == sideStagePosition) return;
        SurfaceControl.Transaction t = mTransactionPool.acquire();
    void switchSplitPosition(String reason) {
        final SurfaceControl.Transaction t = mTransactionPool.acquire();
        mTempRect1.setEmpty();
        final StageTaskListener topLeftStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +876,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                        va.start();
                    });
                });

        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                mSplitLayout.isLandscape());
    }

    void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1617,10 +1612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    @Override
    public void onDoubleTappedDivider() {
        setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                mSplitLayout.isLandscape());
        switchSplitPosition("double tap");
    }

    @Override
Loading