Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +2 −0 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -688,6 +689,7 @@ class SplitScreenTransitions { remoteTransition, extraTransitType); this.mResizeAnim = resizeAnim; this.mEnteringPosition = snapPosition; this.mRequireRootsInTransition = remoteTransition != null; } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitTransitionModifier.kt 0 → 100644 +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 libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +23 −25 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -452,6 +456,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mFoldLockSettingsObserver.register(); mStatusBarHider = new SplitStatusBarHider(taskOrganizer, splitState, rootDisplayAreaOrganizer); mSplitTransitionModifier = new SplitTransitionModifier(); } @VisibleForTesting Loading Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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( Loading @@ -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)) { Loading Loading @@ -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; } Loading Loading @@ -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); } } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +2 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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; } Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +2 −0 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -688,6 +689,7 @@ class SplitScreenTransitions { remoteTransition, extraTransitType); this.mResizeAnim = resizeAnim; this.mEnteringPosition = snapPosition; this.mRequireRootsInTransition = remoteTransition != null; } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitTransitionModifier.kt 0 → 100644 +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
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +23 −25 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -452,6 +456,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mFoldLockSettingsObserver.register(); mStatusBarHider = new SplitStatusBarHider(taskOrganizer, splitState, rootDisplayAreaOrganizer); mSplitTransitionModifier = new SplitTransitionModifier(); } @VisibleForTesting Loading Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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( Loading @@ -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)) { Loading Loading @@ -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; } Loading Loading @@ -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); } } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +2 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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; } Loading