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

Commit 6be95d2f authored by Winson Chung's avatar Winson Chung
Browse files

Update recent tasks split pairs from split controller

- Add pairs for the top visible children when both roots are populated,
  and update when either side changes.
- Remove pairs if an activity is finished, or if the user dismisses
  split manually
- Remove pairs for associated tasks if they are made visible in fullscreen

Bug: 202740477
Test: atest WMShellUnitTests
Test: adb shell dumpsys activity service SystemUIService WMShell

Change-Id: I87c632dfd1a014d64ad8e25c9e68ea6ca230744e
parent cef0ca0a
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -240,8 +240,12 @@ public abstract class WMShellBaseModule {
    @WMSingleton
    @Provides
    static FullscreenTaskListener provideFullscreenTaskListener(
            SyncTransactionQueue syncQueue, Optional<FullscreenUnfoldController> controller) {
        return new FullscreenTaskListener(syncQueue, controller);
            SyncTransactionQueue syncQueue,
            Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
            Optional<RecentTasksController> recentTasksOptional
    ) {
        return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
                recentTasksOptional);
    }

    //
@@ -490,12 +494,13 @@ public abstract class WMShellBaseModule {
            DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController, Transitions transitions,
            TransactionPool transactionPool, IconProvider iconProvider,
            Optional<RecentTasksController> recentTasks,
            Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
        if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
            return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
                    rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
                    displayInsetsController, transitions, transactionPool, iconProvider,
                    stageTaskUnfoldControllerProvider));
                    recentTasks, stageTaskUnfoldControllerProvider));
        } else {
            return Optional.empty();
        }
+21 −1
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;

import java.io.PrintWriter;
@@ -47,15 +48,23 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
    private static final String TAG = "FullscreenTaskListener";

    private final SyncTransactionQueue mSyncQueue;
    private final FullscreenUnfoldController mFullscreenUnfoldController;
    private final Optional<RecentTasksController> mRecentTasksOptional;

    private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
    private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
    private final FullscreenUnfoldController mFullscreenUnfoldController;

    public FullscreenTaskListener(SyncTransactionQueue syncQueue,
            Optional<FullscreenUnfoldController> unfoldController) {
        this(syncQueue, unfoldController, Optional.empty());
    }

    public FullscreenTaskListener(SyncTransactionQueue syncQueue,
            Optional<FullscreenUnfoldController> unfoldController,
            Optional<RecentTasksController> recentTasks) {
        mSyncQueue = syncQueue;
        mFullscreenUnfoldController = unfoldController.orElse(null);
        mRecentTasksOptional = recentTasks;
    }

    @Override
@@ -79,6 +88,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
        });

        mAnimatableTasksListener.onTaskAppeared(taskInfo);
        updateRecentsForVisibleFullscreenTask(taskInfo);
    }

    @Override
@@ -86,6 +96,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;

        mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
        updateRecentsForVisibleFullscreenTask(taskInfo);

        final TaskData data = mDataByTaskId.get(taskInfo.taskId);
        final Point positionInParent = taskInfo.positionInParent;
@@ -111,6 +122,15 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
                taskInfo.taskId);
    }

    private void updateRecentsForVisibleFullscreenTask(RunningTaskInfo taskInfo) {
        mRecentTasksOptional.ifPresent(recentTasks -> {
            if (taskInfo.isVisible) {
                // Remove any persisted splits if either tasks are now made fullscreen and visible
                recentTasks.removeSplitPair(taskInfo.taskId);
            }
        });
    }

    @Override
    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
        if (!mDataByTaskId.contains(taskId)) {
+18 −1
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;

@@ -124,10 +125,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
    private final TransactionPool mTransactionPool;
    private final SplitscreenEventLogger mLogger;
    private final IconProvider mIconProvider;
    private final Optional<RecentTasksController> mRecentTasksOptional;
    private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;

    private StageCoordinator mStageCoordinator;

    // TODO(b/205019015): Remove after we clean up downstream modules
    public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
            SyncTransactionQueue syncQueue, Context context,
            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
@@ -135,6 +138,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
            DisplayInsetsController displayInsetsController,
            Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        this(shellTaskOrganizer, syncQueue, context, rootTDAOrganizer, mainExecutor,
                displayImeController, displayInsetsController, transitions, transactionPool,
                iconProvider, Optional.empty(), unfoldControllerProvider);
    }

    public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
            SyncTransactionQueue syncQueue, Context context,
            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
            ShellExecutor mainExecutor, DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController,
            Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
            Optional<RecentTasksController> recentTasks,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        mTaskOrganizer = shellTaskOrganizer;
        mSyncQueue = syncQueue;
        mContext = context;
@@ -147,6 +163,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        mUnfoldControllerProvider = unfoldControllerProvider;
        mLogger = new SplitscreenEventLogger();
        mIconProvider = iconProvider;
        mRecentTasksOptional = recentTasks;
    }

    public SplitScreen asSplitScreen() {
@@ -169,7 +186,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
            mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                    mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
                    mIconProvider, mUnfoldControllerProvider);
                    mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);
        }
    }

+64 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;

import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -92,6 +93,7 @@ import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.Transitions;

@@ -147,6 +149,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    private final DisplayInsetsController mDisplayInsetsController;
    private final SplitScreenTransitions mSplitTransitions;
    private final SplitscreenEventLogger mLogger;
    private final Optional<RecentTasksController> mRecentTasks;
    // Tracks whether we should update the recent tasks.  Only allow this to happen in between enter
    // and exit, since exit itself can trigger a number of changes that update the stages.
    private boolean mShouldUpdateRecents;
    private boolean mExitSplitScreenOnHide;
    private boolean mKeyguardOccluded;

@@ -191,6 +197,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            DisplayInsetsController displayInsetsController, Transitions transitions,
            TransactionPool transactionPool, SplitscreenEventLogger logger,
            IconProvider iconProvider,
            Optional<RecentTasksController> recentTasks,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        mContext = context;
        mDisplayId = displayId;
@@ -198,6 +205,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mRootTDAOrganizer = rootTDAOrganizer;
        mTaskOrganizer = taskOrganizer;
        mLogger = logger;
        mRecentTasks = recentTasks;
        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);

@@ -238,6 +246,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
            Transitions transitions, TransactionPool transactionPool,
            SplitscreenEventLogger logger,
            Optional<RecentTasksController> recentTasks,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        mContext = context;
        mDisplayId = displayId;
@@ -255,6 +264,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
        mLogger = logger;
        mRecentTasks = recentTasks;
        transitions.addHandler(this);
    }

@@ -560,12 +570,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    private void applyExitSplitScreen(StageTaskListener childrenToTop,
            WindowContainerTransaction wct, @ExitReason int exitReason) {
        mRecentTasks.ifPresent(recentTasks -> {
            // Notify recents if we are exiting in a way that breaks the pair, and disable further
            // updates to splits in the recents until we enter split again
            if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
                recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
                recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
            }
        });
        mShouldUpdateRecents = false;

        mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
        mMainStage.deactivate(wct, childrenToTop == mMainStage);
        mTaskOrganizer.applyTransaction(wct);
        mSyncQueue.runInSync(t -> t
                .setWindowCrop(mMainStage.mRootLeash, null)
                .setWindowCrop(mSideStage.mRootLeash, null));

        // Hide divider and reset its position.
        setDividerVisibility(false);
        mSplitLayout.resetDividerPosition();
@@ -579,6 +600,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
    }

    /**
     * Returns whether the split pair in the recent tasks list should be broken.
     */
    private boolean shouldBreakPairedTaskInRecents(@ExitReason int exitReason) {
        switch (exitReason) {
            // One of the apps doesn't support MW
            case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
            // User has explicitly dragged the divider to dismiss split
            case EXIT_REASON_DRAG_DIVIDER:
            // Either of the split apps have finished
            case EXIT_REASON_APP_FINISHED:
                return true;
            default:
                return false;
        }
    }

    /**
     * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
     * an existing WindowContainerTransaction (rather than applying immediately). This is intended
@@ -645,12 +683,28 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                    mSplitLayout.isLandscape());
        }
        updateRecentTasksSplitPair();

        for (int i = mListeners.size() - 1; i >= 0; --i) {
            mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
        }
    }

    private void updateRecentTasksSplitPair() {
        if (!mShouldUpdateRecents) {
            return;
        }

        mRecentTasks.ifPresent(recentTasks -> {
            int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId();
            int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId();
            if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
                // Update the pair for the top tasks
                recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId);
            }
        });
    }

    private void sendSplitVisibilityChanged() {
        for (int i = mListeners.size() - 1; i >= 0; --i) {
            final SplitScreen.SplitScreenListener l = mListeners.get(i);
@@ -784,14 +838,18 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            mSyncQueue.queue(wct);
            mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
        }
        if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
                && mSideStageListener.mHasChildren) {
        if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
            mShouldUpdateRecents = true;
            updateRecentTasksSplitPair();

            if (!mLogger.hasStartedSession()) {
                mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
                        getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                        getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                        mSplitLayout.isLandscape());
            }
        }
    }

    @VisibleForTesting
    IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell.splitscreen;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -112,6 +113,19 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        return mChildrenTaskInfo.contains(taskId);
    }

    /**
     * Returns the top visible child task's id.
     */
    int getTopVisibleChildTaskId() {
        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
            final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
            if (info.isVisible) {
                return info.taskId;
            }
        }
        return INVALID_TASK_ID;
    }

    /**
     * Returns the top activity uid for the top child task.
     */
Loading