From 9b27cdf8bf474cc45c4910dacf272da2dc228951 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Fri, 19 May 2023 09:35:41 +0000 Subject: [PATCH] Improve drag divider dismiss transition. Before this CL, the split root task will not include in tansition when drag to dismiss. It leads to users could see flicker when the top task bounds changed due to no animation on it. On this CL, it add update bounds too on dismiss transition then split root could be included in transition. And we could use decor to do fade-out animation. Fix: 245472831 Fix: 283045645 Test: manual Test: pass existing tests Change-Id: Ie6be85173d9a5b71603aa89858df117be08ea484 --- .../shell/common/split/SplitDecorManager.java | 6 ++ .../splitscreen/SplitScreenTransitions.java | 55 +++++++++++++++++-- .../shell/splitscreen/StageCoordinator.java | 25 +++++++-- .../shell/splitscreen/StageTaskListener.java | 4 +- .../wm/shell/splitscreen/SplitTestUtils.java | 4 ++ .../splitscreen/SplitTransitionTests.java | 12 ++-- .../splitscreen/StageCoordinatorTests.java | 3 + 7 files changed, 94 insertions(+), 15 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 753dfa7396f84..a9ccdf6a156f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -293,6 +293,9 @@ public class SplitDecorManager extends WindowlessWindowManager { } if (mResizingIconView == null) { + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(false); + } return; } @@ -311,6 +314,9 @@ public class SplitDecorManager extends WindowlessWindowManager { releaseDecor(finishT); finishT.apply(); finishT.close(); + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(true); + } } }); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 14ea86a8c0e93..d2b0e2800dc93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -87,6 +87,14 @@ class SplitScreenTransitions { mStageCoordinator = stageCoordinator; } + private void initTransition(@NonNull IBinder transition, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + mAnimatingTransition = transition; + mFinishTransaction = finishTransaction; + mFinishCallback = finishCallback; + } + /** Play animation for enter transition or dismiss transition. */ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -94,9 +102,7 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { - mFinishCallback = finishCallback; - mAnimatingTransition = transition; - mFinishTransaction = finishTransaction; + initTransition(transition, finishTransaction, finishCallback); final TransitSession pendingTransition = getPendingTransition(transition); if (pendingTransition != null) { @@ -220,6 +226,45 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); } + /** Play animation for drag divider dismiss transition. */ + void playDragDismissAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor, + @NonNull WindowContainerToken topRoot) { + initTransition(transition, finishTransaction, finishCallback); + + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = change.getLeash(); + + if (toTopRoot.equals(change.getContainer())) { + startTransaction.setAlpha(leash, 1.f); + startTransaction.show(leash); + + ValueAnimator va = new ValueAnimator(); + mAnimations.add(va); + + toTopDecor.onResized(startTransaction, animated -> { + mAnimations.remove(va); + if (animated) { + mTransitions.getMainExecutor().execute(() -> { + onFinish(null /* wct */, null /* wctCB */); + }); + } + }); + } else if (topRoot.equals(change.getContainer())) { + // Ensure it on top of all changes in transition. + startTransaction.setLayer(leash, Integer.MAX_VALUE); + startTransaction.setAlpha(leash, 1.f); + startTransaction.show(leash); + } + } + startTransaction.apply(); + onFinish(null /* wct */, null /* wctCB */); + } + /** Play animation for resize transition. */ void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -227,9 +272,7 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { - mFinishCallback = finishCallback; - mAnimatingTransition = transition; - mFinishTransaction = finishTransaction; + initTransition(transition, finishTransaction, finishCallback); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index bf20567834e05..e0ffffffa7273 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1328,8 +1328,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mIsExiting = true; childrenToTop.resetBounds(wct); wct.reorder(childrenToTop.mRootTaskInfo.token, true); - wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token, - SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); } wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false /* reparentLeafTaskIfRelaunch */); @@ -1517,6 +1515,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction t) { mSplitLayout.update(t); + mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash, + getMainStageBounds()); + mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash, + getSideStageBounds()); setDividerVisibility(true, t); // Ensure divider surface are re-parented back into the hierarchy at the end of the // transition. See Transition#buildFinishTransaction for more detail. @@ -1989,13 +1991,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final boolean mainStageToTop = bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; + final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage; if (!ENABLE_SHELL_TRANSITIONS) { - exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, reason); + exitSplitScreen(toTopStage, reason); return; } final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); + toTopStage.resetBounds(wct); prepareExitSplitScreen(dismissTop, wct); if (mRootTaskInfo != null) { wct.setDoNotPip(mRootTaskInfo.token); @@ -2531,8 +2535,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, shouldAnimate = startPendingEnterAnimation( mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { + final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; shouldAnimate = startPendingDismissAnimation( - mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); + dismiss, info, startTransaction, finishTransaction); + if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) { + final StageTaskListener toTopStage = + dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage; + mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction, + finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token, + toTopStage.getSplitDecorManager(), mRootTaskInfo.token); + return true; + } } else if (mSplitTransitions.isPendingResize(transition)) { mSplitTransitions.playResizeAnimation(transition, info, startTransaction, finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token, @@ -2787,6 +2800,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.mPendingDismiss = null; return false; } + dismissTransition.setFinishedCallback((callbackWct, callbackT) -> { + mMainStage.getSplitDecorManager().release(callbackT); + mSideStage.getSplitDecorManager().release(callbackT); + }); addDividerBarToTransition(info, false /* show */); return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index e2e9270cd6cc8..da7d18641a974 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; @@ -201,7 +202,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. - if (mRootTaskInfo.isVisible != taskInfo.isVisible) { + if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { if (taskInfo.isVisible) { mSplitDecorManager.inflate(mContext, mRootLeash, taskInfo.configuration.windowConfiguration.getBounds()); @@ -385,6 +386,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void resetBounds(WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, null); wct.setAppBounds(mRootTaskInfo.token, null); + wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); } void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index ae69b3ddd0428..4e446c684d862 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -44,11 +44,15 @@ public class SplitTestUtils { static SplitLayout createMockSplitLayout() { 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 SurfaceControl leash = createMockSurface(); SplitLayout out = mock(SplitLayout.class); doReturn(dividerBounds).when(out).getDividerBounds(); doReturn(dividerBounds).when(out).getRefDividerBounds(); doReturn(leash).when(out).getDividerLeash(); + doReturn(bounds1).when(out).getBounds1(); + doReturn(bounds2).when(out).getBounds2(); return out; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 80384531e9ae9..60c0e55355680 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -42,6 +42,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import android.annotation.NonNull; import android.app.ActivityManager; @@ -72,6 +73,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.Transitions; @@ -117,13 +119,13 @@ public class SplitTransitionTests extends ShellTestCase { doReturn(mockExecutor).when(mTransitions).getAnimExecutor(); doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire(); mSplitLayout = SplitTestUtils.createMockSplitLayout(); - mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider); + mIconProvider)); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); - mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( + mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider); + mIconProvider)); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, @@ -137,6 +139,8 @@ public class SplitTransitionTests extends ShellTestCase { .setParentTaskId(mMainStage.mRootTaskInfo.taskId).build(); mSideChild = new TestRunningTaskInfoBuilder() .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build(); + doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); + doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 6621ab8ce0d83..66b6c62f1dd6f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -68,6 +68,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.sysui.ShellController; @@ -145,6 +146,8 @@ public class StageCoordinatorTests extends ShellTestCase { mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); + doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); + doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); } @Test