Fix enter/exit horizontal ActivityEmbedding split with Shell transition

1. For animation spec, before, we only consider animation to/from
   left/right. Now, we also check top/bottom.
2. For animation adapter, before, we always use the window end bounds,
   which may be empty when closing a window that is also resizing during
   the transition. Now, for closing window, we use the start bounds
   instead.

Bug: 241043533
Test: Verify with launching activity into horizontal split.
Test: Verify with switching between split and stack.
Change-Id: I4fddcb2327d05e90b0c12605490fa7d7aa987fa0
This commit is contained in:
Chris Li
2022-11-17 13:59:16 +08:00
parent 63cd60eeb2
commit 60040ea71f
3 changed files with 76 additions and 20 deletions

View File

@@ -30,6 +30,8 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import com.android.wm.shell.transition.Transitions;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
* {@link SurfaceControl.Transaction}.
@@ -50,6 +52,16 @@ class ActivityEmbeddingAnimationAdapter {
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull
private final Rect mWholeAnimationBounds = new Rect();
/**
* Area in absolute coordinate that should represent all the content to show for this window.
* This should be the end bounds for opening window, and start bounds for closing window in case
* the window is resizing during the open/close transition.
*/
@NonNull
private final Rect mContentBounds = new Rect();
/** Offset relative to the window parent surface for {@link #mContentBounds}. */
@NonNull
private final Point mContentRelOffset = new Point();
@NonNull
final Transformation mTransformation = new Transformation();
@@ -80,6 +92,21 @@ class ActivityEmbeddingAnimationAdapter {
mChange = change;
mLeash = leash;
mWholeAnimationBounds.set(wholeAnimationBounds);
if (Transitions.isClosingType(change.getMode())) {
// When it is closing, we want to show the content at the start position in case the
// window is resizing as well. For example, when the activities is changing from split
// to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
final Rect startBounds = change.getStartAbsBounds();
final Rect endBounds = change.getEndAbsBounds();
mContentBounds.set(startBounds);
mContentRelOffset.set(change.getEndRelOffset());
mContentRelOffset.offset(
startBounds.left - endBounds.left,
startBounds.top - endBounds.top);
} else {
mContentBounds.set(change.getEndAbsBounds());
mContentRelOffset.set(change.getEndRelOffset());
}
}
/**
@@ -110,8 +137,7 @@ class ActivityEmbeddingAnimationAdapter {
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
final Point offset = mChange.getEndRelOffset();
mTransformation.getMatrix().postTranslate(offset.x, offset.y);
mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -119,8 +145,8 @@ class ActivityEmbeddingAnimationAdapter {
// positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
final Rect cropRect = new Rect(mChange.getEndAbsBounds());
cropRect.offset(positionX - offset.x, positionY - offset.y);
final Rect cropRect = new Rect(mContentBounds);
cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
// Store the current offset of the surface top left from (0,0) in absolute coordinate.
final int offsetX = cropRect.left;
@@ -133,7 +159,7 @@ class ActivityEmbeddingAnimationAdapter {
} else if (mAnimation.hasExtension()) {
// Allow the surface to be shown in its original bounds in case we want to use edge
// extensions.
cropRect.union(mChange.getEndAbsBounds());
cropRect.union(mContentBounds);
}
// cropRect is in absolute coordinate, so we need to translate it to surface top left.

View File

@@ -304,6 +304,7 @@ class ActivityEmbeddingAnimationRunner {
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
Animation changeAnimation = null;
Rect parentBounds = new Rect();
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -326,10 +327,15 @@ class ActivityEmbeddingAnimationRunner {
}
}
// The TaskFragment may be enter/exit split, so we take the union of both as the parent
// size.
parentBounds.union(boundsAnimationChange.getStartAbsBounds());
parentBounds.union(boundsAnimationChange.getEndAbsBounds());
// There are two animations in the array. The first one is for the start leash
// (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
boundsAnimationChange.getEndAbsBounds());
parentBounds);
// Keep track as we might need to add background color for the animation.
// Although there may be multiple change animation, record one of them is sufficient
// because the background color will be added to the root leash for the whole animation.
@@ -352,6 +358,11 @@ class ActivityEmbeddingAnimationRunner {
animations[1], boundsAnimationChange));
}
if (parentBounds.isEmpty()) {
throw new IllegalStateException(
"There should be at least one changing window to play the change animation");
}
// If there is no corresponding open/close window with the change, we should show background
// color to cover the empty part of the screen.
boolean shouldShouldBackgroundColor = true;
@@ -368,10 +379,10 @@ class ActivityEmbeddingAnimationRunner {
// No-op if it will be covered by the changing parent window.
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
shouldShouldBackgroundColor = false;
} else {
animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
shouldShouldBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));

View File

@@ -21,7 +21,6 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -80,15 +79,25 @@ class ActivityEmbeddingAnimationSpec {
/** Animation for window that is opening in a change transition. */
@NonNull
Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
@NonNull Rect parentBounds) {
// Use end bounds for opening.
final Rect bounds = change.getEndAbsBounds();
final Point offset = change.getEndRelOffset();
// The window will be animated in from left or right depends on its position.
final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
final int startLeft;
final int startTop;
if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
// The window will be animated in from left or right depends on its position.
startTop = 0;
startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
} else {
// The window will be animated in from top or bottom depends on its position.
startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
startLeft = 0;
}
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -98,15 +107,25 @@ class ActivityEmbeddingAnimationSpec {
/** Animation for window that is closing in a change transition. */
@NonNull
Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
final Rect bounds = change.getEndAbsBounds();
final Point offset = change.getEndRelOffset();
// The window will be animated out to left or right depends on its position.
final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
@NonNull Rect parentBounds) {
// Use start bounds for closing.
final Rect bounds = change.getStartAbsBounds();
final int endTop;
final int endLeft;
if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
// The window will be animated out to left or right depends on its position.
endTop = 0;
endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
} else {
// The window will be animated out to top or bottom depends on its position.
endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
endLeft = 0;
}
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());