From 3c1ea525ed6666a687e60144f1d4b584c1ff3c4a Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Thu, 13 Jan 2022 07:36:00 +0000 Subject: [PATCH] Consolidate recent animation in split screen with shell-transition Make sure to delegate recent transition to launcher after split screen activated. Bug: 206487881 Test: manual check swipe split-pair to overview panel and swipe to home behavior Change-Id: I1177b1efdd7a45e8f9ec162b2155dbea1660e1af --- .../splitscreen/SplitScreenTransitions.java | 53 ++++++++++++++----- .../shell/splitscreen/StageCoordinator.java | 42 +++++++++++---- .../splitscreen/SplitTransitionTests.java | 6 +-- .../system/RemoteAnimationTargetCompat.java | 35 +++++------- 4 files changed, 88 insertions(+), 48 deletions(-) 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 0aa8d7e2f1a35..8664d9be3340e 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 @@ -44,6 +44,7 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.TransactionPool; @@ -66,28 +67,26 @@ class SplitScreenTransitions { DismissTransition mPendingDismiss = null; IBinder mPendingEnter = null; + IBinder mPendingRecent = null; private IBinder mAnimatingTransition = null; private OneShotRemoteHandler mRemoteHandler = null; - private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> { - if (wct != null || wctCB != null) { - throw new UnsupportedOperationException("finish transactions not supported yet."); - } - onFinish(); - }; + private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; /** Keeps track of currently running animations */ private final ArrayList mAnimations = new ArrayList<>(); + private final StageCoordinator mStageCoordinator; private Transitions.TransitionFinishCallback mFinishCallback = null; private SurfaceControl.Transaction mFinishTransaction; SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, - @NonNull Runnable onFinishCallback) { + @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) { mTransactionPool = pool; mTransitions = transitions; mOnFinish = onFinishCallback; + mStageCoordinator = stageCoordinator; } void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -166,7 +165,7 @@ class SplitScreenTransitions { } } t.apply(); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); } /** Starts a transition to enter split with a remote transition animator. */ @@ -203,7 +202,26 @@ class SplitScreenTransitions { return transition; } - void onFinish() { + IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct, + Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) { + if (transition == null) { + transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler); + } + mPendingRecent = transition; + + if (remoteTransition != null) { + // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) + mRemoteHandler = new OneShotRemoteHandler( + mTransitions.getMainExecutor(), remoteTransition); + mRemoteHandler.setTransition(transition); + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced Enter recent panel"); + return transition; + } + + void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; mOnFinish.run(); if (mFinishTransaction != null) { @@ -211,14 +229,23 @@ class SplitScreenTransitions { mTransactionPool.release(mFinishTransaction); mFinishTransaction = null; } - mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - mFinishCallback = null; + if (mFinishCallback != null) { + mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */); + mFinishCallback = null; + } if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; } if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { mPendingDismiss = null; } + if (mAnimatingTransition == mPendingRecent) { + // If the wct is not null while finishing recent transition, it indicates it's not + // returning to home and hence needing the wct to reorder tasks. + final boolean toHome = wct == null; + mStageCoordinator.finishRecentAnimation(toHome); + mPendingRecent = null; + } mAnimatingTransition = null; } @@ -240,7 +267,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); }); }; va.addListener(new Animator.AnimatorListener() { @@ -288,7 +315,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); }); }; va.addListener(new AnimatorListenerAdapter() { 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 83830ec56c325..c8120509f0dbb 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 @@ -20,6 +20,7 @@ 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_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.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -236,7 +237,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, deviceStateManager.registerCallback(taskOrganizer.getExecutor(), new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); + mOnTransitionAnimationComplete, this); mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); @@ -266,7 +267,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTDAOrganizer.registerListener(displayId, this); mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); + mOnTransitionAnimationComplete, this); mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; @@ -1271,13 +1272,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int activityType = triggerTask.getActivityType(); if (activityType == ACTIVITY_TYPE_ASSISTANT) { // We don't want assistant panel to dismiss split screen, so do nothing. + } else if (activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_RECENTS) { + // Enter overview panel, so start recent transition. + mSplitTransitions.startRecentTransition(transition, out, this, + request.getRemoteTransition()); } else { - // Going home or occluded by the other fullscreen task, so dismiss both. + // Occluded by the other fullscreen task, so dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - final int exitReason = activityType == ACTIVITY_TYPE_HOME - ? EXIT_REASON_RETURN_HOME : EXIT_REASON_UNKNOWN; mSplitTransitions.startDismissTransition(transition, out, this, - STAGE_TYPE_UNDEFINED, exitReason); + STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } } else { @@ -1307,13 +1311,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mSplitTransitions.mPendingEnter && ( - mSplitTransitions.mPendingDismiss == null + if (transition != mSplitTransitions.mPendingEnter + && transition != mSplitTransitions.mPendingRecent + && (mSplitTransitions.mPendingDismiss == null || mSplitTransitions.mPendingDismiss.mTransition != transition)) { // Not entering or exiting, so just do some house-keeping and validation. // If we're not in split-mode, just abort so something else can handle it. - if (!isSplitScreenVisible()) return false; + if (!mMainStage.isActive()) return false; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); @@ -1356,6 +1361,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean shouldAnimate = true; if (mSplitTransitions.mPendingEnter == transition) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); + } else if (mSplitTransitions.mPendingRecent == transition) { + shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingDismiss != null && mSplitTransitions.mPendingDismiss.mTransition == transition) { shouldAnimate = startPendingDismissAnimation( @@ -1500,6 +1507,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + private boolean startPendingRecentAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + setDividerVisibility(false, t); + return true; + } + + void finishRecentAnimation(boolean toHome) { + if (toHome) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + mSplitTransitions.startDismissTransition(null /* transition */, wct, this, + STAGE_TYPE_UNDEFINED, EXIT_REASON_RETURN_HOME); + } else { + setDividerVisibility(true, null /* t */); + } + } + private void addDividerBarToTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); 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 ea94cf0f75970..59c377a3e13db 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 @@ -251,7 +251,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test - public void testDismissToHome() { + public void testEnterRecents() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() @@ -264,7 +264,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); - assertTrue(containsSplitExit(result)); + assertTrue(result.isEmpty()); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); @@ -284,7 +284,7 @@ public class SplitTransitionTests extends ShellTestCase { mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); - assertFalse(mStageCoordinator.isSplitScreenVisible()); + assertTrue(mStageCoordinator.isSplitScreenVisible()); } @Test diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index 72e3e181aaf12..4d0c443ca5a40 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -17,7 +17,6 @@ package com.android.systemui.shared.system; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; -import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -140,22 +139,11 @@ public class RemoteAnimationTargetCompat { // changes should be ordered top-to-bottom in z final int mode = change.getMode(); - // Don't move anything that isn't independent within its parents - if (!TransitionInfo.isIndependent(change, info)) { - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { - t.show(leash); - t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); - } - return; - } + // Launcher animates leaf tasks directly, so always reparent all task leashes to root leash. + t.reparent(leash, info.getRootLeash()); + t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, + change.getStartAbsBounds().top - info.getRootOffset().y); - boolean hasParent = change.getParent() != null; - - if (!hasParent) { - t.reparent(leash, info.getRootLeash()); - t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, - change.getStartAbsBounds().top - info.getRootOffset().y); - } t.show(leash); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { @@ -266,14 +254,15 @@ public class RemoteAnimationTargetCompat { SurfaceControl.Transaction t, ArrayMap leashMap) { final ArrayList out = new ArrayList<>(); for (int i = 0; i < info.getChanges().size(); i++) { - boolean changeIsWallpaper = - (info.getChanges().get(i).getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; + final TransitionInfo.Change change = info.getChanges().get(i); + final boolean changeIsWallpaper = + (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; if (wallpapers != changeIsWallpaper) continue; - out.add(new RemoteAnimationTargetCompat(info.getChanges().get(i), - info.getChanges().size() - i, info, t)); - if (leashMap == null) continue; - leashMap.put(info.getChanges().get(i).getLeash(), - out.get(out.size() - 1).leash); + + out.add(new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t)); + if (leashMap != null) { + leashMap.put(change.getLeash(), out.get(out.size() - 1).leash); + } } return out.toArray(new RemoteAnimationTargetCompat[out.size()]); }