Implement resize enter animation

Implement same animation as legacy transition.
Drag drop to split use fade in animation and the other cases
use fling animation.

Bug: 245472831
Fix: 279176453
Fix: 280400154
Test: manual
Test: pass existing tests
Change-Id: Ib722b75af6ee7125d9631c6fb0ea842708bbff64
This commit is contained in:
Tony Huang
2023-05-05 08:51:30 +00:00
parent 9ccb30e3ae
commit e5bca046ec
6 changed files with 107 additions and 128 deletions

View File

@@ -22,6 +22,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -58,7 +60,6 @@ public class SplitDecorManager extends WindowlessWindowManager {
private static final String TAG = SplitDecorManager.class.getSimpleName();
private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
private static final String GAP_BACKGROUND_SURFACE_NAME = "GapBackground";
private static final long FADE_DURATION = 133;
private final IconProvider mIconProvider;
private final SurfaceSession mSurfaceSession;
@@ -261,6 +262,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
mScreenshotAnimator = ValueAnimator.ofFloat(1, 0);
mScreenshotAnimator.setDuration(FADE_DURATION);
mScreenshotAnimator.addUpdateListener(valueAnimator -> {
final float progress = (float) valueAnimator.getAnimatedValue();
animT.setAlpha(mScreenshot, progress);

View File

@@ -43,6 +43,11 @@ public class SplitScreenConstants {
*/
public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1;
/**
* Duration used for every split fade-in or fade-out.
*/
public static final int FADE_DURATION = 133;
@IntDef(prefix = {"SPLIT_POSITION_"}, value = {
SPLIT_POSITION_UNDEFINED,
SPLIT_POSITION_TOP_OR_LEFT,

View File

@@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -34,7 +35,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -63,7 +63,7 @@ class SplitScreenTransitions {
private final Runnable mOnFinish;
DismissSession mPendingDismiss = null;
TransitSession mPendingEnter = null;
EnterSession mPendingEnter = null;
TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
@@ -120,6 +120,7 @@ class SplitScreenTransitions {
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
// Play some place-holder fade animations
final boolean isEnter = isPendingEnter(transition);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
@@ -142,17 +143,23 @@ class SplitScreenTransitions {
change.getEndRelOffset().x, change.getEndRelOffset().y);
}
}
boolean isRootOrSplitSideRoot = change.getParent() == null
|| topRoot.equals(change.getParent());
boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
// For enter or exit, we only want to animate side roots and the divider but not the
// top-root.
if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
continue;
}
if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer())
|| sideRoot.equals(change.getContainer()))) {
final boolean isTopRoot = topRoot.equals(change.getContainer());
final boolean isMainRoot = mainRoot.equals(change.getContainer());
final boolean isSideRoot = sideRoot.equals(change.getContainer());
final boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
final boolean isMainChild = mainRoot.equals(change.getParent());
final boolean isSideChild = sideRoot.equals(change.getParent());
if (isEnter && (isMainChild || isSideChild)) {
// Reset child tasks bounds on finish.
mFinishTransaction.setPosition(leash,
change.getEndRelOffset().x, change.getEndRelOffset().y);
mFinishTransaction.setCrop(leash, null);
} else if (isEnter && isTopRoot) {
// Ensure top root is visible at start.
t.setAlpha(leash, 1.f);
t.show(leash);
} else if (isEnter && isMainRoot || isSideRoot) {
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
@@ -161,10 +168,24 @@ class SplitScreenTransitions {
t.setLayer(leash, Integer.MAX_VALUE);
t.show(leash);
}
// These container changes we don't want to animate them.
// We should only animate stage root, divider and child tasks are not under stage root.
if (isTopRoot || isMainChild || isSideChild || change.getTaskInfo() == null) {
continue;
}
if (isEnter && mPendingEnter.mResizeAnim) {
// We will run animation in next transition so skip anim here
continue;
} else if (isEnter && isMainRoot) {
// Main stage already on top so skip fade in animation to reduce flicker.
continue;
}
boolean isOpening = TransitionUtil.isOpeningType(info.getType());
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
// fade in
startExampleAnimation(leash, true /* show */);
startFadeAnimation(leash, true /* show */);
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
// fade out
if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
@@ -173,7 +194,7 @@ class SplitScreenTransitions {
// and don't animate it so it doesn't pop-in when reparented.
t.setAlpha(leash, 0.f);
} else {
startExampleAnimation(leash, false /* show */);
startFadeAnimation(leash, false /* show */);
}
}
}
@@ -265,7 +286,7 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback,
int extraTransitType) {
int extraTransitType, boolean resizeAnim) {
if (mPendingEnter != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start enter split transition since it already exist. ");
@@ -273,7 +294,7 @@ class SplitScreenTransitions {
}
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback,
extraTransitType);
extraTransitType, resizeAnim);
return transition;
}
@@ -282,9 +303,10 @@ class SplitScreenTransitions {
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback,
int extraTransitType) {
mPendingEnter = new TransitSession(
transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType);
int extraTransitType, boolean resizeAnim) {
mPendingEnter = new EnterSession(
transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType,
resizeAnim);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -407,78 +429,27 @@ class SplitScreenTransitions {
}
}
// TODO(shell-transitions): real animations
private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) {
final float end = show ? 1.f : 0.f;
final float start = 1.f - end;
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
va.setDuration(500);
va.setDuration(FADE_DURATION);
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
transaction.apply();
});
final Runnable finisher = () -> {
transaction.setAlpha(leash, end);
transaction.apply();
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
onFinish(null /* wct */, null /* wctCB */);
});
};
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
finisher.run();
}
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
}
// TODO(shell-transitions): real animations
private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
@NonNull Rect startBounds, @NonNull Rect endBounds) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
va.setDuration(500);
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
transaction.setWindowCrop(leash,
(int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
(int) (startBounds.height() * (1.f - fraction)
+ endBounds.height() * fraction));
transaction.setPosition(leash,
startBounds.left * (1.f - fraction) + endBounds.left * fraction,
startBounds.top * (1.f - fraction) + endBounds.top * fraction);
transaction.apply();
});
final Runnable finisher = () -> {
transaction.setWindowCrop(leash, 0, 0);
transaction.setPosition(leash, endBounds.left, endBounds.top);
transaction.apply();
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
onFinish(null /* wct */, null /* wctCB */);
});
};
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
finisher.run();
transaction.setAlpha(leash, end);
transaction.apply();
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
onFinish(null /* wct */, null /* wctCB */);
});
}
});
mAnimations.add(va);
@@ -567,6 +538,21 @@ class SplitScreenTransitions {
}
}
/** Bundled information of enter transition. */
class EnterSession extends TransitSession {
final boolean mResizeAnim;
EnterSession(IBinder transition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback,
@Nullable RemoteTransition remoteTransition,
int extraTransitType, boolean resizeAnim) {
super(transition, consumedCallback, finishedCallback, remoteTransition,
extraTransitType);
this.mResizeAnim = resizeAnim;
}
}
/** Bundled information of dismiss transition. */
class DismissSession extends TransitSession {
final int mReason;

View File

@@ -394,7 +394,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
null, this, null /* consumedCallback */, null /* finishedCallback */,
isSplitScreenVisible()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
!mIsDropEntering);
} else {
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -502,7 +503,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
null /* consumedCallback */, null /* finishedCallback */, extraTransitType);
null /* consumedCallback */, null /* finishedCallback */, extraTransitType,
!mIsDropEntering);
}
/** Launches an activity into split by legacy transition. */
@@ -660,7 +662,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.startEnterTransition(
TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
setEnterInstanceId(instanceId);
}
@@ -712,7 +714,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.startEnterTransition(
TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
setEnterInstanceId(instanceId);
}
@@ -1493,14 +1495,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void prepareBringSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
StageTaskListener targetStage;
if (isSplitScreenVisible()) {
// If the split screen is foreground, retrieves target stage based on position.
targetStage = startPosition == mSideStagePosition ? mSideStage : mMainStage;
} else {
targetStage = mSideStage;
}
if (taskInfo != null) {
wct.startTask(taskInfo.taskId,
resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1509,13 +1503,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// and evict all tasks current under its.
if (!isSplitScreenVisible()) {
// Recreate so we need to reset position rather than keep position of background split.
mSplitLayout.resetDividerPosition();
updateWindowBounds(mSplitLayout, wct);
final StageTaskListener anotherStage = targetStage == mMainStage
? mSideStage : mMainStage;
anotherStage.reparentTopTask(wct);
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
mMainStage.reparentTopTask(wct);
prepareSplitLayout(wct);
}
}
@@ -1531,8 +1520,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.addTask(taskInfo, wct);
}
mMainStage.activate(wct, true /* includingTopTask */);
mSplitLayout.resetDividerPosition();
prepareSplitLayout(wct);
}
private void prepareSplitLayout(WindowContainerTransaction wct) {
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
} else {
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
updateWindowBounds(mSplitLayout, wct);
if (!mIsDropEntering) {
// Reset its smallest width dp to avoid is change layout before it actually resized to
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
}
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
}
@@ -1547,6 +1550,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
t.show(mRootTaskLeash);
setSplitsVisible(true);
mIsDropEntering = false;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
@@ -1776,18 +1780,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
&& mSplitRequest == null) {
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
} else {
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
mMainStage.reparentTopTask(wct);
prepareEnterSplitScreen(wct);
mMainStage.evictAllChildren(wct);
mSideStage.evictOtherChildren(wct, taskId);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1982,20 +1978,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
mSplitLayout.init();
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mIsDropEntering) {
prepareEnterSplitScreen(wct);
} else {
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
onSplitScreenEnter();
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
}
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -2344,7 +2328,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
null /* consumedCallback */, null /* finishedCallback */,
0 /* extraTransitType */);
0 /* extraTransitType */, !mIsDropEntering);
}
}
return out;
@@ -2583,7 +2567,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private boolean startPendingEnterAnimation(
@NonNull SplitScreenTransitions.TransitSession enterTransition,
@NonNull SplitScreenTransitions.EnterSession enterTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
// First, verify that we actually have opened apps in both splits.
@@ -2641,7 +2625,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ " to have been called with " + sideChild.getTaskInfo().taskId
+ " before startAnimation().");
}
final TransitionInfo.Change finalMainChild = mainChild;
final TransitionInfo.Change finalSideChild = sideChild;
enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
@@ -2651,6 +2634,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (finalSideChild != null) {
mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId);
}
if (enterTransition.mResizeAnim) {
mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
finishEnterSplitScreen(finishT);

View File

@@ -182,7 +182,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -408,7 +408,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(new TestRemoteTransition(), "Test"),
mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,

View File

@@ -158,7 +158,6 @@ public class StageCoordinatorTests extends ShellTestCase {
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
verify(mMainStage).reparentTopTask(eq(wct));
verify(mSplitLayout).resetDividerPosition();
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}