diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index 06f6228623d8b..194b6330d92cf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -16,7 +16,8 @@ package androidx.window.extensions.embedding; -import android.graphics.Point; +import static android.graphics.Matrix.MSCALE_X; + import android.graphics.Rect; import android.view.Choreographer; import android.view.RemoteAnimationTarget; @@ -25,58 +26,151 @@ import android.view.animation.Animation; import android.view.animation.Transformation; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; /** * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}. + * + * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close. */ class TaskFragmentAnimationAdapter { - private final Animation mAnimation; - private final RemoteAnimationTarget mTarget; - private final SurfaceControl mLeash; - private final boolean mSizeChanged; - private final Point mPosition; - private final Transformation mTransformation = new Transformation(); - private final float[] mMatrix = new float[9]; - private final float[] mVecs = new float[4]; - private final Rect mRect = new Rect(); + final Animation mAnimation; + final RemoteAnimationTarget mTarget; + final SurfaceControl mLeash; + + final Transformation mTransformation = new Transformation(); + final float[] mMatrix = new float[9]; private boolean mIsFirstFrame = true; TaskFragmentAnimationAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { - this(animation, target, target.leash, false /* sizeChanged */, null /* position */); + this(animation, target, target.leash); } /** - * @param sizeChanged whether the surface size needs to be changed. + * @param leash the surface to animate. */ TaskFragmentAnimationAdapter(@NonNull Animation animation, - @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash, - boolean sizeChanged, @Nullable Point position) { + @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) { mAnimation = animation; mTarget = target; mLeash = leash; - mSizeChanged = sizeChanged; - mPosition = position != null - ? position - : new Point(target.localBounds.left, target.localBounds.top); } /** Called on frame update. */ - void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { + final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { if (mIsFirstFrame) { t.show(mLeash); mIsFirstFrame = false; } - currentPlayTime = Math.min(currentPlayTime, mAnimation.getDuration()); - mAnimation.getTransformation(currentPlayTime, mTransformation); - mTransformation.getMatrix().postTranslate(mPosition.x, mPosition.y); + // Extract the transformation to the current time. + mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()), + mTransformation); + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + onAnimationUpdateInner(t); + } + + /** To be overridden by subclasses to adjust the animation surface change. */ + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + mTransformation.getMatrix().postTranslate( + mTarget.localBounds.left, mTarget.localBounds.top); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); - t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + } + + /** Called after animation finished. */ + final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { + onAnimationUpdate(t, mAnimation.getDuration()); + } + + final long getDurationHint() { + return mAnimation.computeDurationHint(); + } + + /** + * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to + * animate together as one. This adapter will offset the animation leash to make the animate of + * two windows look like a single window. + */ + static class SplitAdapter extends TaskFragmentAnimationAdapter { + private final boolean mIsLeftHalf; + private final int mWholeAnimationWidth; + + /** + * @param isLeftHalf whether this is the left half of the animation. + * @param wholeAnimationWidth the whole animation windows width. + */ + SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target, + boolean isLeftHalf, int wholeAnimationWidth) { + super(animation, target); + mIsLeftHalf = isLeftHalf; + mWholeAnimationWidth = wholeAnimationWidth; + if (wholeAnimationWidth == 0) { + throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth"); + } + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + float posX = mTarget.localBounds.left; + final float posY = mTarget.localBounds.top; + // This window is half of the whole animation window. Offset left/right to make it + // look as one with the other half. + mTransformation.getMatrix().getValues(mMatrix); + final int targetWidth = mTarget.localBounds.width(); + final float scaleX = mMatrix[MSCALE_X]; + final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2; + final float curOffset = targetWidth * (1 - scaleX) / 2; + final float offsetDiff = totalOffset - curOffset; + if (mIsLeftHalf) { + posX += offsetDiff; + } else { + posX -= offsetDiff; + } + mTransformation.getMatrix().postTranslate(posX, posY); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + } + + /** + * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has + * size change. + */ + static class SnapshotAdapter extends TaskFragmentAnimationAdapter { + + SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { + // Start leash is the snapshot of the starting surface. + super(animation, target, target.startLeash); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + // Snapshot should always be placed at the top left of the animation leash. + mTransformation.getMatrix().postTranslate(0, 0); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + } + + /** + * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change. + */ + static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter { + private final float[] mVecs = new float[4]; + private final Rect mRect = new Rect(); + + BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { + super(animation, target); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + mTransformation.getMatrix().postTranslate( + mTarget.localBounds.left, mTarget.localBounds.top); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); - if (mSizeChanged) { // The following applies an inverse scale to the clip-rect so that it crops "after" the // scale instead of before. mVecs[1] = mVecs[2] = 0; @@ -92,13 +186,4 @@ class TaskFragmentAnimationAdapter { t.setWindowCrop(mLeash, mRect); } } - - /** Called after animation finished. */ - void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { - onAnimationUpdate(t, mAnimation.getDuration()); - } - - long getDurationHint() { - return mAnimation.computeDurationHint(); - } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java index 65797667687e1..535dac1a51016 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java @@ -29,7 +29,6 @@ import android.window.TaskFragmentOrganizer; class TaskFragmentAnimationController { private static final String TAG = "TaskFragAnimationCtrl"; - // TODO(b/196173550) turn off when finalize static final boolean DEBUG = false; private final TaskFragmentOrganizer mOrganizer; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 3980d077a7dd5..412559e34070a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -23,7 +23,7 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import android.animation.Animator; import android.animation.ValueAnimator; -import android.graphics.Point; +import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -40,6 +40,7 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; /** To run the TaskFragment animations. */ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { @@ -167,42 +168,86 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { private List createOpenAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { - final List adapters = new ArrayList<>(); - for (RemoteAnimationTarget target : targets) { - final Animation animation = - mAnimationSpec.loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */); - adapters.add(new TaskFragmentAnimationAdapter(animation, target)); - } - return adapters; + return createOpenCloseAnimationAdapters(targets, + mAnimationSpec::loadOpenAnimation); } private List createCloseAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { - final List adapters = new ArrayList<>(); + return createOpenCloseAnimationAdapters(targets, + mAnimationSpec::loadCloseAnimation); + } + + private List createOpenCloseAnimationAdapters( + @NonNull RemoteAnimationTarget[] targets, + @NonNull BiFunction animationProvider) { + // We need to know if the target window is only a partial of the whole animation screen. + // If so, we will need to adjust it to make the whole animation screen looks like one. + final List openingTargets = new ArrayList<>(); + final List closingTargets = new ArrayList<>(); + final Rect openingWholeScreenBounds = new Rect(); + final Rect closingWholeScreenBounds = new Rect(); for (RemoteAnimationTarget target : targets) { - final Animation animation = - mAnimationSpec.loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */); - adapters.add(new TaskFragmentAnimationAdapter(animation, target)); + if (target.mode != MODE_CLOSING) { + openingTargets.add(target); + openingWholeScreenBounds.union(target.localBounds); + } else { + closingTargets.add(target); + closingWholeScreenBounds.union(target.localBounds); + } + } + + final List adapters = new ArrayList<>(); + for (RemoteAnimationTarget target : openingTargets) { + adapters.add(createOpenCloseAnimationAdapter(target, animationProvider, + openingWholeScreenBounds)); + } + for (RemoteAnimationTarget target : closingTargets) { + adapters.add(createOpenCloseAnimationAdapter(target, animationProvider, + closingWholeScreenBounds)); } return adapters; } + private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter( + @NonNull RemoteAnimationTarget target, + @NonNull BiFunction animationProvider, + @NonNull Rect wholeAnimationBounds) { + final Animation animation = animationProvider.apply(target, wholeAnimationBounds); + final Rect targetBounds = target.localBounds; + if (targetBounds.left == wholeAnimationBounds.left + && targetBounds.right != wholeAnimationBounds.right) { + // This is the left split of the whole animation window. + return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target, + true /* isLeftHalf */, wholeAnimationBounds.width()); + } else if (targetBounds.left != wholeAnimationBounds.left + && targetBounds.right == wholeAnimationBounds.right) { + // This is the right split of the whole animation window. + return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target, + false /* isLeftHalf */, wholeAnimationBounds.width()); + } + // Open/close window that fills the whole animation. + return new TaskFragmentAnimationAdapter(animation, target); + } + private List createChangeAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { final List adapters = new ArrayList<>(); for (RemoteAnimationTarget target : targets) { if (target.startBounds != null) { + // This is the target with bounds change. final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(target); - // The snapshot surface will always be at (0, 0) of its parent. - adapters.add(new TaskFragmentAnimationAdapter(animations[0], target, - target.startLeash, false /* sizeChanged */, new Point(0, 0))); - // The end surface will have size change for scaling. - adapters.add(new TaskFragmentAnimationAdapter(animations[1], target, - target.leash, true /* sizeChanged */, null /* position */)); + // Adapter for the starting snapshot leash. + adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter( + animations[0], target)); + // Adapter for the ending bounds changed leash. + adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter( + animations[1], target)); continue; } + // These are the other targets that don't have bounds change in the same transition. final Animation animation; if (target.hasAnimatingParent) { // No-op if it will be covered by the changing parent window. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index 11a79b2f4a27d..c0908a548501d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -176,18 +176,28 @@ class TaskFragmentAnimationSpec { return new Animation[]{startSet, endSet}; } - Animation loadOpenAnimation(boolean isEnter) { - // TODO(b/196173550) We need to customize the animation to handle two open window as one. - return mTransitionAnimation.loadDefaultAnimationAttr(isEnter + Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = target.mode != MODE_CLOSING; + final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter ? R.styleable.WindowAnimation_activityOpenEnterAnimation : R.styleable.WindowAnimation_activityOpenExitAnimation); + animation.initialize(target.localBounds.width(), target.localBounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; } - Animation loadCloseAnimation(boolean isEnter) { - // TODO(b/196173550) We need to customize the animation to handle two open window as one. - return mTransitionAnimation.loadDefaultAnimationAttr(isEnter + Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = target.mode != MODE_CLOSING; + final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter ? R.styleable.WindowAnimation_activityCloseEnterAnimation : R.styleable.WindowAnimation_activityCloseExitAnimation); + animation.initialize(target.localBounds.width(), target.localBounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; } private class SettingsObserver extends ContentObserver {