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

Commit 6ee9895a authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Pass back entire split tree for remote animations" into main

parents 4eb855df 5a1ca15d
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -680,6 +680,7 @@ class SplitScreenTransitions {
        final boolean mResizeAnim;
        /** The starting snap position we'll enter into with this transition. */
        final @SplitScreenConstants.PersistentSnapPosition int mEnteringPosition;
        final boolean mRequireRootsInTransition;

        EnterSession(IBinder transition,
                @Nullable RemoteTransition remoteTransition,
@@ -688,6 +689,7 @@ class SplitScreenTransitions {
                    remoteTransition, extraTransitType);
            this.mResizeAnim = resizeAnim;
            this.mEnteringPosition = snapPosition;
            this.mRequireRootsInTransition = remoteTransition != null;
        }
    }

+244 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.splitscreen

import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
import android.util.Log
import android.util.Slog
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.WindowContainerToken
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.split.SplitScreenConstants

/**
 * Utils class that modifies the changes for a given TransitionInfo.
 * Avoiding static classes to help keep testability. Let's try to avoid keeping any
 * state at the class level.
 */
class SplitTransitionModifier {
    private val TAG = "SplitTransitionModifier"

    /** Add the dim layer for the given [stage] to the [info].  */
    fun addDimLayerToTransition(
        info: TransitionInfo, show: Boolean,
        stage: StageTaskListener, bounds: Rect,
        parentToken: WindowContainerToken
    ) {
        val dimLayer = stage.mDimLayer
        if (dimLayer == null || !dimLayer.isValid) {
            Slog.w(TAG, "addDimLayerToTransition but leash was released or not created")
        } else {
            val change =
                TransitionInfo.Change(null,  /* token */dimLayer)
            change.parent = parentToken
            change.setStartAbsBounds(bounds)
            change.setEndAbsBounds(bounds)
            change.mode =
                if (show) TRANSIT_TO_FRONT else TRANSIT_TO_BACK
            change.flags = SplitScreenConstants.FLAG_IS_DIM_LAYER
            info.addChange(change)
        }
    }

    /**
     * Adds [TransitionInfo.Change]s to [info] *IF* they are not already a part of it. This
     * will not modify top level or stage root tasks already part of [info]
     */
    fun addStageRootsToTransition(
        info: TransitionInfo,
        mainStage: StageTaskListener?,
        sideStage: StageTaskListener?,
        mainStageBounds: Rect,
        sideStageBounds: Rect,
        splitRootTask: RunningTaskInfo,
        splitRootLeash: SurfaceControl,
        splitRootBounds: Rect
    ) {
        if (mainStage == null || sideStage == null) {
            return
        }

        // Opening and closing tasks of main stage
        val mainStageChildren: List<TransitionInfo.Change>
        // Opening and closing tasks of side stage
        val sideStageChildren: List<TransitionInfo.Change>
        // No task parent :(
        val remainingChanges: List<TransitionInfo.Change>
        val mainStageChange : TransitionInfo.Change
        val sideStageChange : TransitionInfo.Change
        val splitRootChange : TransitionInfo.Change

        // If any children are opening, set the roots to be opening
        val mode = if (anySplitChangesToFront(info, mainStage, sideStage))
            TRANSIT_TO_FRONT else TRANSIT_TO_BACK

        // Add main stage root, set it as the parent for its children
        if (info.getChange(mainStage.mRootTaskInfo.token) != null) {
            mainStageChange = checkNotNull(info.getChange(mainStage.mRootTaskInfo.token))
            mainStageChildren = getChildrenForParent(info, mainStageChange, false /*setParent*/)
        } else {
            mainStageChange = getChangeForStageRoot(
                mainStage,
                mainStageBounds,
                mode,
                splitRootTask.token
            )
            mainStageChildren = getChildrenForParent(info, mainStageChange, true /*setParent*/)
        }

        // Add side stage root, set it as the parent for its children
        if (info.getChange(sideStage.mRootTaskInfo.token) != null) {
            sideStageChange = checkNotNull(info.getChange(sideStage.mRootTaskInfo.token))
            sideStageChildren = getChildrenForParent(info, sideStageChange, false /*setParent*/)
        } else {
            sideStageChange = getChangeForStageRoot(
                sideStage,
                sideStageBounds,
                mode,
                splitRootTask.token
            )
            sideStageChildren = getChildrenForParent(info, sideStageChange, true /*setParent*/)
        }

        // Add top level split root, set it as the parent for main and side stage roots
        splitRootChange = if (info.getChange(splitRootTask.token) != null) {
            checkNotNull(info.getChange(splitRootTask.token))
        } else {
            getChangeForSplitRoot(
                mode, splitRootTask,
                splitRootLeash, splitRootBounds
            )
        }
        // Explicitly set the parents of the stage roots because if either of the stage roots
        // weren't present or the top level split root wasn't present in the original transition,
        // the parent will be null.
        mainStageChange.parent = splitRootTask.token
        sideStageChange.parent = splitRootTask.token

        remainingChanges = info.changes.stream()
            .filter { change: TransitionInfo.Change ->
                // No parent AND not the top level split root (we'll add that separately)
                (change.taskInfo == null || change.taskInfo?.parentTaskId == -1) &&
                        (change.taskInfo?.taskId != splitRootChange.taskInfo?.taskId)
            }
            .toList()

        val finalList = mutableListOf<TransitionInfo.Change>()
        finalList.addAll(mainStageChildren)
        finalList.add(mainStageChange)
        finalList.addAll(sideStageChildren)
        finalList.add(sideStageChange)
        finalList.add(splitRootChange)
        finalList.addAll(remainingChanges)

        Log.v(TAG, "original change size: ${info.changes.size} finalSize: ${finalList.size}")
        info.changes.clear()
        info.changes.addAll(finalList)
    }

    /**
    * Returns true if any of the changes in [info] are opening and have a parent that is
    * either the main or side stage root.
    */
    private fun anySplitChangesToFront(info: TransitionInfo,
                                       mainStage: StageTaskListener,
                                       sideStage: StageTaskListener): Boolean {
        return info.changes.stream()
            .anyMatch { change: TransitionInfo.Change ->
                val isOpening = TransitionUtil.isOpeningMode(change.mode)
                val taskInfo = change.taskInfo
                val hasStageRootParent = taskInfo != null &&
                        (taskInfo.parentTaskId == mainStage.mRootTaskInfo.taskId ||
                                taskInfo.parentTaskId == sideStage.mRootTaskInfo.taskId)
                isOpening && hasStageRootParent
            }
    }

    /**
     * Creates and returns a [TransitionInfo.Change] for the top level split root (indicated
     * by [rootTaskInfo]) to the transition. Almost entirely similar to [getChangeForStageRoot] except
     * this does not set a parent for the new Change.
     */
    private fun getChangeForSplitRoot(
        transitMode: Int,
        rootTaskInfo: RunningTaskInfo,
        rootTaskLeash: SurfaceControl,
        rootBounds: Rect
    ): TransitionInfo.Change {
        val change = TransitionInfo.Change(
            rootTaskInfo.token,
            rootTaskLeash
        )
        change.taskInfo = rootTaskInfo
        change.mode = transitMode
        change.setStartAbsBounds(rootBounds)
        change.setEndAbsBounds(rootBounds)
        return change
    }

    /**
     * Creates and returns a [TransitionInfo.Change] for the individual stage roots
     * (indicated by [stage]). The [parentToken] should be that of the top level split root.
     */
    private fun getChangeForStageRoot(
        stage: StageTaskListener, bounds: Rect,
        transitMode: Int,
        parentToken: WindowContainerToken
    ): TransitionInfo.Change {
        val change = TransitionInfo.Change(
            stage.mRootTaskInfo.token,
            stage.mRootLeash
        )
        change.taskInfo = stage.mRootTaskInfo
        change.parent = parentToken
        change.mode = transitMode
        change.setStartAbsBounds(bounds)
        change.setEndAbsBounds(bounds)
        return change
    }

    /**
     * Given a [parentChange], this iterates over all changes in [info] and gets the children
     * for all changes where the change's parentTaskId matches [parentChange]s taskId.
     * If [setParent] is [true] then it will also set the child's parent WCT to that of the
     * [parentChange]
     *
     * @return [List] of all the changes that are children of [parentChange]
     */
    private fun getChildrenForParent(
        info: TransitionInfo,
        parentChange: TransitionInfo.Change,
        setParent: Boolean) : List<TransitionInfo.Change> {
        val childrenOfChange = mutableListOf<TransitionInfo.Change>()
        info.changes.stream()
            .filter { change: TransitionInfo.Change ->
                change.taskInfo != null &&
                        change.taskInfo?.parentTaskId == parentChange.taskInfo?.taskId
            }
            .forEach { change: TransitionInfo.Change ->
                if (setParent) {
                    change.parent = parentChange.taskInfo?.token
                }
                childrenOfChange.add(change)
            }
        return childrenOfChange
    }
}
 No newline at end of file
+23 −25
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_NONE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;

import static com.android.window.flags.Flags.enableFullScreenWindowOnRemovingSplitScreenStageBugfix;
@@ -277,6 +278,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    @VisibleForTesting
    SplitMultiDisplayHelper mSplitMultiDisplayHelper;
    private final SplitTransitionModifier mSplitTransitionModifier;


    /**
     * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
@@ -315,6 +318,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        Intent mStartIntent;
        Intent mStartIntent2;


        SplitRequest(int taskId, Intent startIntent, int position) {
            mActivateTaskId = taskId;
            mStartIntent = startIntent;
@@ -452,6 +456,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mFoldLockSettingsObserver.register();
        mStatusBarHider = new SplitStatusBarHider(taskOrganizer, splitState,
                rootDisplayAreaOrganizer);
        mSplitTransitionModifier = new SplitTransitionModifier();
    }

    @VisibleForTesting
@@ -504,6 +509,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
        mStatusBarHider = new SplitStatusBarHider(taskOrganizer, splitState,
                rootDisplayAreaOrganizer);
        mSplitTransitionModifier = new SplitTransitionModifier();
    }

    public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -3420,6 +3426,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: transition=%d",
                info.getDebugId());
        boolean shouldAnimate = true;
        ActivityManager.RunningTaskInfo displayRootTaskInfo = mSplitMultiDisplayHelper
                .getDisplayRootTaskInfo(DEFAULT_DISPLAY);
        if (mSplitTransitions.isPendingEnter(transition)) {
            shouldAnimate = startPendingEnterAnimation(transition,
                    mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
@@ -3431,6 +3439,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            mMainHandler.removeCallbacks(mReEnableLaunchAdjacentOnRoot);
            mMainHandler.postDelayed(mReEnableLaunchAdjacentOnRoot,
                    DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS);
            if (shouldAnimate && mSplitTransitions.mPendingEnter.mRequireRootsInTransition) {
                mSplitTransitionModifier.addStageRootsToTransition(info,
                        mMainStage, mSideStage, getMainStageBounds(), getSideStageBounds(),
                        displayRootTaskInfo, mRootTaskLeash, mSplitLayout.getRootBounds());
            }
        } else if (mSplitTransitions.isPendingDismiss(transition)) {
            final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
            shouldAnimate = startPendingDismissAnimation(
@@ -3446,8 +3459,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                }
                mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction,
                        finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token,
                        toTopStage.getSplitDecorManager(), mSplitMultiDisplayHelper
                                .getDisplayRootTaskInfo(DEFAULT_DISPLAY).token);
                        toTopStage.getSplitDecorManager(), displayRootTaskInfo.token);
                return true;
            }
        } else if (mSplitTransitions.isPendingResize(transition)) {
@@ -3479,7 +3491,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
        mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
                finishCallback, mainToken, sideToken,
                mSplitMultiDisplayHelper.getDisplayRootTaskInfo(DEFAULT_DISPLAY).token);
                displayRootTaskInfo.token);
        return true;
    }

@@ -4051,35 +4063,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            return;
        }

        WindowContainerToken rootToken =
                mSplitMultiDisplayHelper.getDisplayRootTaskInfo(DEFAULT_DISPLAY).token;
        if (Flags.enableFlexibleSplit()) {
            List<StageTaskListener> stages = mStageOrderOperator.getActiveStages();
            for (int i = 0; i < stages.size(); i++) {
                final StageTaskListener stage = stages.get(i);
                mSplitState.getCurrentLayout().get(i).roundOut(mTempRect1);
                addDimLayerToTransition(info, show, stage, mTempRect1);
                mSplitTransitionModifier.addDimLayerToTransition(info, show, stage,
                        mTempRect1, rootToken);
            }
        } else if (enableFlexibleTwoAppSplit()) {
            addDimLayerToTransition(info, show, mMainStage, getMainStageBounds());
            addDimLayerToTransition(info, show, mSideStage, getSideStageBounds());
        }
    }

    /** Adds a single dim layer to the given TransitionInfo. */
    private void addDimLayerToTransition(@NonNull TransitionInfo info, boolean show,
            StageTaskListener stage, Rect bounds) {
        final SurfaceControl dimLayer = stage.mDimLayer;
        if (dimLayer == null || !dimLayer.isValid()) {
            Slog.w(TAG, "addDimLayerToTransition but leash was released or not created");
        } else {
            final TransitionInfo.Change change =
                    new TransitionInfo.Change(null /* token */, dimLayer);
            change.setParent(mSplitMultiDisplayHelper.getDisplayRootTaskInfo(DEFAULT_DISPLAY)
                    .token);
            change.setStartAbsBounds(bounds);
            change.setEndAbsBounds(bounds);
            change.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
            change.setFlags(FLAG_IS_DIM_LAYER);
            info.addChange(change);
            mSplitTransitionModifier.addDimLayerToTransition(info, show, mMainStage,
                    getMainStageBounds(), rootToken);
            mSplitTransitionModifier.addDimLayerToTransition(info, show, mSideStage,
                    getSideStageBounds(), rootToken);
        }
    }

+2 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ public class SplitTestUtils {
        final Rect dividerBounds = new Rect(48, 0, 52, 100);
        final Rect bounds1 = new Rect(0, 0, 40, 100);
        final Rect bounds2 = new Rect(60, 0, 100, 100);
        final Rect rootBounds = new Rect(0, 0, 100, 100);
        final SurfaceControl leash = createMockSurface();
        SplitLayout out = mock(SplitLayout.class);
        doReturn(dividerBounds).when(out).getDividerBounds();
@@ -64,6 +65,7 @@ public class SplitTestUtils {
        doReturn(leash).when(out).getDividerLeash();
        doReturn(bounds1).when(out).getTopLeftBounds();
        doReturn(bounds2).when(out).getBottomRightBounds();
        doReturn(rootBounds).when(out).getRootBounds();
        doReturn(SNAP_TO_2_50_50).when(out).calculateCurrentSnapPosition();
        return out;
    }