Loading libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java 0 → 100644 +288 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.animation; import static com.android.wm.shell.transition.DefaultSurfaceAnimator.setupValueAnimator; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.graphics.Matrix; import android.graphics.Rect; import android.view.Choreographer; import android.view.SurfaceControl; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.ClipRectAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.Transformation; import android.view.animation.TranslateAnimation; import java.util.function.Consumer; /** * Animation implementation for size-changing window container animations. Ported from * {@link com.android.server.wm.WindowChangeAnimationSpec}. * <p> * This animation behaves slightly differently depending on whether the window is growing * or shrinking: * <ul> * <li>If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old) * snapshot. * <li>If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into * place. * </ul> */ public class SizeChangeAnimation { private final Rect mTmpRect = new Rect(); final Transformation mTmpTransform = new Transformation(); final Matrix mTmpMatrix = new Matrix(); final float[] mTmpFloats = new float[9]; final float[] mTmpVecs = new float[4]; private final Animation mAnimation; private final Animation mSnapshotAnim; private final ValueAnimator mAnimator = ValueAnimator.ofFloat(0f, 1f); /** * The maximum of stretching applied to any surface during interpolation (since the animation * is a combination of stretching/cropping/fading). */ private static final float SCALE_FACTOR = 0.7f; /** * Since this animation is made of several sub-animations, we want to pre-arrange the * sub-animations on a "virtual timeline" and then drive the overall progress in lock-step. * * To do this, we have a single value-animator which animates progress from 0-1 with an * arbitrary duration and interpolator. Then we convert the progress to a frame in our virtual * timeline to get the interpolated transforms. * * The APIs for arranging the sub-animations use integral frame numbers, so we need to pick * an integral "duration" for our virtual timeline. That's what this constant specifies. It * is effectively an animation "resolution" since it divides-up the 0-1 interpolation-space. */ private static final int ANIMATION_RESOLUTION = 1000; public SizeChangeAnimation(Rect startBounds, Rect endBounds) { mAnimation = buildContainerAnimation(startBounds, endBounds); mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds); } /** * Initialize a size-change animation for a container leash. */ public void initialize(SurfaceControl leash, SurfaceControl snapshot, SurfaceControl.Transaction startT) { startT.reparent(snapshot, leash); startT.setPosition(snapshot, 0, 0); startT.show(snapshot); startT.show(leash); apply(startT, leash, snapshot, 0.f); } /** * Initialize a size-change animation for a view containing the leash surface(s). * * Note that this **will** apply {@param startToApply}! */ public void initialize(View view, SurfaceControl leash, SurfaceControl snapshot, SurfaceControl.Transaction startToApply) { startToApply.reparent(snapshot, leash); startToApply.setPosition(snapshot, 0, 0); startToApply.show(snapshot); startToApply.show(leash); apply(view, startToApply, leash, snapshot, 0.f); } private ValueAnimator buildAnimatorInner(ValueAnimator.AnimatorUpdateListener updater, SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish, SurfaceControl.Transaction transaction, @Nullable View view) { return setupValueAnimator(mAnimator, updater, (anim) -> { transaction.reparent(snapshot, null); if (view != null) { view.setClipBounds(null); view.setAnimationMatrix(null); transaction.setCrop(leash, null); } transaction.apply(); transaction.close(); onFinish.accept(anim); }); } /** * Build an animator which works on a pair of surface controls (where the snapshot is assumed * to be a child of the main leash). * * @param onFinish Called when animation finishes. This is called on the anim thread! */ public ValueAnimator buildAnimator(SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish) { final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); Choreographer choreographer = Choreographer.getInstance(); return buildAnimatorInner(animator -> { // The finish callback in buildSurfaceAnimation will ensure that the animation ends // with fraction 1. final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f); apply(transaction, leash, snapshot, progress); transaction.setFrameTimelineVsync(choreographer.getVsyncId()); transaction.apply(); }, leash, snapshot, onFinish, transaction, null /* view */); } /** * Build an animator which works on a view that contains a pair of surface controls (where * the snapshot is assumed to be a child of the main leash). * * @param onFinish Called when animation finishes. This is called on the anim thread! */ public ValueAnimator buildViewAnimator(View view, SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish) { final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); return buildAnimatorInner(animator -> { // The finish callback in buildSurfaceAnimation will ensure that the animation ends // with fraction 1. final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f); apply(view, transaction, leash, snapshot, progress); }, leash, snapshot, onFinish, transaction, view); } /** Animation for the whole container (snapshot is inside this container). */ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds) { final long duration = ANIMATION_RESOLUTION; boolean growing = endBounds.width() - startBounds.width() + endBounds.height() - startBounds.height() >= 0; long scalePeriod = (long) (duration * SCALE_FACTOR); float startScaleX = SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width() + (1.f - SCALE_FACTOR); float startScaleY = SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height() + (1.f - SCALE_FACTOR); final AnimationSet animSet = new AnimationSet(true); final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1); scaleAnim.setDuration(scalePeriod); if (!growing) { scaleAnim.setStartOffset(duration - scalePeriod); } animSet.addAnimation(scaleAnim); final Animation translateAnim = new TranslateAnimation(startBounds.left, endBounds.left, startBounds.top, endBounds.top); translateAnim.setDuration(duration); animSet.addAnimation(translateAnim); Rect startClip = new Rect(startBounds); Rect endClip = new Rect(endBounds); startClip.offsetTo(0, 0); endClip.offsetTo(0, 0); final Animation clipAnim = new ClipRectAnimation(startClip, endClip); clipAnim.setDuration(duration); animSet.addAnimation(clipAnim); animSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), endBounds.height()); return animSet; } /** The snapshot surface is assumed to be a child of the container surface. */ private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds) { final long duration = ANIMATION_RESOLUTION; boolean growing = endBounds.width() - startBounds.width() + endBounds.height() - startBounds.height() >= 0; long scalePeriod = (long) (duration * SCALE_FACTOR); float endScaleX = 1.f / (SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width() + (1.f - SCALE_FACTOR)); float endScaleY = 1.f / (SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height() + (1.f - SCALE_FACTOR)); AnimationSet snapAnimSet = new AnimationSet(true); // Animation for the "old-state" snapshot that is atop the task. final Animation snapAlphaAnim = new AlphaAnimation(1.f, 0.f); snapAlphaAnim.setDuration(scalePeriod); if (!growing) { snapAlphaAnim.setStartOffset(duration - scalePeriod); } snapAnimSet.addAnimation(snapAlphaAnim); final Animation snapScaleAnim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY); snapScaleAnim.setDuration(duration); snapAnimSet.addAnimation(snapScaleAnim); snapAnimSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), endBounds.height()); return snapAnimSet; } private void calcCurrentClipBounds(Rect outClip, Transformation fromTransform) { // The following applies an inverse scale to the clip-rect so that it crops "after" the // scale instead of before. mTmpVecs[1] = mTmpVecs[2] = 0; mTmpVecs[0] = mTmpVecs[3] = 1; fromTransform.getMatrix().mapVectors(mTmpVecs); mTmpVecs[0] = 1.f / mTmpVecs[0]; mTmpVecs[3] = 1.f / mTmpVecs[3]; final Rect clipRect = fromTransform.getClipRect(); outClip.left = (int) (clipRect.left * mTmpVecs[0] + 0.5f); outClip.right = (int) (clipRect.right * mTmpVecs[0] + 0.5f); outClip.top = (int) (clipRect.top * mTmpVecs[3] + 0.5f); outClip.bottom = (int) (clipRect.bottom * mTmpVecs[3] + 0.5f); } private void apply(SurfaceControl.Transaction t, SurfaceControl leash, SurfaceControl snapshot, float progress) { long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress); // update thumbnail surface mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform); t.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats); t.setAlpha(snapshot, mTmpTransform.getAlpha()); // update container surface mAnimation.getTransformation(currentPlayTime, mTmpTransform); final Matrix matrix = mTmpTransform.getMatrix(); t.setMatrix(leash, matrix, mTmpFloats); calcCurrentClipBounds(mTmpRect, mTmpTransform); t.setCrop(leash, mTmpRect); } private void apply(View view, SurfaceControl.Transaction tmpT, SurfaceControl leash, SurfaceControl snapshot, float progress) { long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress); // update thumbnail surface mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform); tmpT.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats); tmpT.setAlpha(snapshot, mTmpTransform.getAlpha()); // update container surface mAnimation.getTransformation(currentPlayTime, mTmpTransform); final Matrix matrix = mTmpTransform.getMatrix(); mTmpMatrix.set(matrix); // animationMatrix is applied after getTranslation, so "move" the translate to the end. mTmpMatrix.preTranslate(-view.getTranslationX(), -view.getTranslationY()); mTmpMatrix.postTranslate(view.getTranslationX(), view.getTranslationY()); view.setAnimationMatrix(mTmpMatrix); calcCurrentClipBounds(mTmpRect, mTmpTransform); tmpT.setCrop(leash, mTmpRect); view.setClipBounds(mTmpRect); // this takes stuff out of mTmpT so mTmpT can be re-used immediately view.getViewRootImpl().applyTransactionOnDraw(tmpT); } } libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java +53 −36 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.TransactionPool; import java.util.ArrayList; import java.util.function.Consumer; public class DefaultSurfaceAnimator { Loading @@ -58,42 +59,12 @@ public class DefaultSurfaceAnimator { // Animation length is already expected to be scaled. va.overrideDurationScale(1.0f); va.setDuration(anim.computeDurationHint()); va.addUpdateListener(updateListener); va.addListener(new AnimatorListenerAdapter() { // It is possible for the end/cancel to be called more than once, which may cause // issues if the animating surface has already been released. Track the finished // state here to skip duplicate callbacks. See b/252872225. private boolean mFinished; @Override public void onAnimationEnd(Animator animation) { onFinish(); } @Override public void onAnimationCancel(Animator animation) { onFinish(); } private void onFinish() { if (mFinished) return; mFinished = true; // Apply transformation of end state in case the animation is canceled. if (va.getAnimatedFraction() < 1f) { va.setCurrentFraction(1f); } setupValueAnimator(va, updateListener, (vanim) -> { pool.release(transaction); mainExecutor.execute(() -> { animations.remove(va); animations.remove(vanim); finishCallback.run(); }); // The update listener can continue to be called after the animation has ended if // end() is called manually again before the finisher removes the animation. // Remove it manually here to prevent animating a released surface. // See b/252872225. va.removeUpdateListener(updateListener); } }); animations.add(va); } Loading Loading @@ -188,4 +159,50 @@ public class DefaultSurfaceAnimator { } } } /** * Setup some callback logic on a value-animator. This helper ensures that a value animator * finishes at its final fraction (1f) and that relevant callbacks are only called once. */ public static ValueAnimator setupValueAnimator(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener, Consumer<ValueAnimator> afterFinish) { animator.addUpdateListener(updateListener); animator.addListener(new AnimatorListenerAdapter() { // It is possible for the end/cancel to be called more than once, which may cause // issues if the animating surface has already been released. Track the finished // state here to skip duplicate callbacks. See b/252872225. private boolean mFinished; @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { onFinish(); } @Override public void onAnimationCancel(Animator animation) { onFinish(); } private void onFinish() { if (mFinished) return; mFinished = true; // Apply transformation of end state in case the animation is canceled. if (animator.getAnimatedFraction() < 1f) { animator.setCurrentFraction(1f); } afterFinish.accept(animator); // The update listener can continue to be called after the animation has ended if // end() is called manually again before the finisher removes the animation. // Remove it manually here to prevent animating a released surface. // See b/252872225. animator.removeUpdateListener(updateListener); } }); return animator; } } libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +26 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; Loading @@ -69,6 +70,7 @@ import static com.android.wm.shell.transition.TransitionAnimationHelper.isCovere import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -104,6 +106,7 @@ import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.animation.SizeChangeAnimation; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; Loading Loading @@ -422,6 +425,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish); continue; } if (Flags.portWindowSizeAnimation() && isTask && TransitionInfo.isIndependent(change, info) && change.getSnapshot() != null) { startBoundsChangeAnimation(startTransaction, animations, change, onAnimFinish, mMainExecutor); continue; } } // Hide the invisible surface directly without animating it if there is a display Loading Loading @@ -734,6 +745,21 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } private void startBoundsChangeAnimation(@NonNull SurfaceControl.Transaction startT, @NonNull ArrayList<Animator> animations, @NonNull TransitionInfo.Change change, @NonNull Runnable finishCb, @NonNull ShellExecutor mainExecutor) { final SizeChangeAnimation sca = new SizeChangeAnimation(change.getStartAbsBounds(), change.getEndAbsBounds()); sca.initialize(change.getLeash(), change.getSnapshot(), startT); final ValueAnimator va = sca.buildAnimator(change.getLeash(), change.getSnapshot(), (animator) -> mainExecutor.execute(() -> { animations.remove(animator); finishCb.run(); })); va.setDuration(DEFAULT_APP_TRANSITION_DURATION); animations.add(va); } @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java 0 → 100644 +288 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.animation; import static com.android.wm.shell.transition.DefaultSurfaceAnimator.setupValueAnimator; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.graphics.Matrix; import android.graphics.Rect; import android.view.Choreographer; import android.view.SurfaceControl; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.ClipRectAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.Transformation; import android.view.animation.TranslateAnimation; import java.util.function.Consumer; /** * Animation implementation for size-changing window container animations. Ported from * {@link com.android.server.wm.WindowChangeAnimationSpec}. * <p> * This animation behaves slightly differently depending on whether the window is growing * or shrinking: * <ul> * <li>If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old) * snapshot. * <li>If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into * place. * </ul> */ public class SizeChangeAnimation { private final Rect mTmpRect = new Rect(); final Transformation mTmpTransform = new Transformation(); final Matrix mTmpMatrix = new Matrix(); final float[] mTmpFloats = new float[9]; final float[] mTmpVecs = new float[4]; private final Animation mAnimation; private final Animation mSnapshotAnim; private final ValueAnimator mAnimator = ValueAnimator.ofFloat(0f, 1f); /** * The maximum of stretching applied to any surface during interpolation (since the animation * is a combination of stretching/cropping/fading). */ private static final float SCALE_FACTOR = 0.7f; /** * Since this animation is made of several sub-animations, we want to pre-arrange the * sub-animations on a "virtual timeline" and then drive the overall progress in lock-step. * * To do this, we have a single value-animator which animates progress from 0-1 with an * arbitrary duration and interpolator. Then we convert the progress to a frame in our virtual * timeline to get the interpolated transforms. * * The APIs for arranging the sub-animations use integral frame numbers, so we need to pick * an integral "duration" for our virtual timeline. That's what this constant specifies. It * is effectively an animation "resolution" since it divides-up the 0-1 interpolation-space. */ private static final int ANIMATION_RESOLUTION = 1000; public SizeChangeAnimation(Rect startBounds, Rect endBounds) { mAnimation = buildContainerAnimation(startBounds, endBounds); mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds); } /** * Initialize a size-change animation for a container leash. */ public void initialize(SurfaceControl leash, SurfaceControl snapshot, SurfaceControl.Transaction startT) { startT.reparent(snapshot, leash); startT.setPosition(snapshot, 0, 0); startT.show(snapshot); startT.show(leash); apply(startT, leash, snapshot, 0.f); } /** * Initialize a size-change animation for a view containing the leash surface(s). * * Note that this **will** apply {@param startToApply}! */ public void initialize(View view, SurfaceControl leash, SurfaceControl snapshot, SurfaceControl.Transaction startToApply) { startToApply.reparent(snapshot, leash); startToApply.setPosition(snapshot, 0, 0); startToApply.show(snapshot); startToApply.show(leash); apply(view, startToApply, leash, snapshot, 0.f); } private ValueAnimator buildAnimatorInner(ValueAnimator.AnimatorUpdateListener updater, SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish, SurfaceControl.Transaction transaction, @Nullable View view) { return setupValueAnimator(mAnimator, updater, (anim) -> { transaction.reparent(snapshot, null); if (view != null) { view.setClipBounds(null); view.setAnimationMatrix(null); transaction.setCrop(leash, null); } transaction.apply(); transaction.close(); onFinish.accept(anim); }); } /** * Build an animator which works on a pair of surface controls (where the snapshot is assumed * to be a child of the main leash). * * @param onFinish Called when animation finishes. This is called on the anim thread! */ public ValueAnimator buildAnimator(SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish) { final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); Choreographer choreographer = Choreographer.getInstance(); return buildAnimatorInner(animator -> { // The finish callback in buildSurfaceAnimation will ensure that the animation ends // with fraction 1. final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f); apply(transaction, leash, snapshot, progress); transaction.setFrameTimelineVsync(choreographer.getVsyncId()); transaction.apply(); }, leash, snapshot, onFinish, transaction, null /* view */); } /** * Build an animator which works on a view that contains a pair of surface controls (where * the snapshot is assumed to be a child of the main leash). * * @param onFinish Called when animation finishes. This is called on the anim thread! */ public ValueAnimator buildViewAnimator(View view, SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish) { final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); return buildAnimatorInner(animator -> { // The finish callback in buildSurfaceAnimation will ensure that the animation ends // with fraction 1. final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f); apply(view, transaction, leash, snapshot, progress); }, leash, snapshot, onFinish, transaction, view); } /** Animation for the whole container (snapshot is inside this container). */ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds) { final long duration = ANIMATION_RESOLUTION; boolean growing = endBounds.width() - startBounds.width() + endBounds.height() - startBounds.height() >= 0; long scalePeriod = (long) (duration * SCALE_FACTOR); float startScaleX = SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width() + (1.f - SCALE_FACTOR); float startScaleY = SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height() + (1.f - SCALE_FACTOR); final AnimationSet animSet = new AnimationSet(true); final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1); scaleAnim.setDuration(scalePeriod); if (!growing) { scaleAnim.setStartOffset(duration - scalePeriod); } animSet.addAnimation(scaleAnim); final Animation translateAnim = new TranslateAnimation(startBounds.left, endBounds.left, startBounds.top, endBounds.top); translateAnim.setDuration(duration); animSet.addAnimation(translateAnim); Rect startClip = new Rect(startBounds); Rect endClip = new Rect(endBounds); startClip.offsetTo(0, 0); endClip.offsetTo(0, 0); final Animation clipAnim = new ClipRectAnimation(startClip, endClip); clipAnim.setDuration(duration); animSet.addAnimation(clipAnim); animSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), endBounds.height()); return animSet; } /** The snapshot surface is assumed to be a child of the container surface. */ private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds) { final long duration = ANIMATION_RESOLUTION; boolean growing = endBounds.width() - startBounds.width() + endBounds.height() - startBounds.height() >= 0; long scalePeriod = (long) (duration * SCALE_FACTOR); float endScaleX = 1.f / (SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width() + (1.f - SCALE_FACTOR)); float endScaleY = 1.f / (SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height() + (1.f - SCALE_FACTOR)); AnimationSet snapAnimSet = new AnimationSet(true); // Animation for the "old-state" snapshot that is atop the task. final Animation snapAlphaAnim = new AlphaAnimation(1.f, 0.f); snapAlphaAnim.setDuration(scalePeriod); if (!growing) { snapAlphaAnim.setStartOffset(duration - scalePeriod); } snapAnimSet.addAnimation(snapAlphaAnim); final Animation snapScaleAnim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY); snapScaleAnim.setDuration(duration); snapAnimSet.addAnimation(snapScaleAnim); snapAnimSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), endBounds.height()); return snapAnimSet; } private void calcCurrentClipBounds(Rect outClip, Transformation fromTransform) { // The following applies an inverse scale to the clip-rect so that it crops "after" the // scale instead of before. mTmpVecs[1] = mTmpVecs[2] = 0; mTmpVecs[0] = mTmpVecs[3] = 1; fromTransform.getMatrix().mapVectors(mTmpVecs); mTmpVecs[0] = 1.f / mTmpVecs[0]; mTmpVecs[3] = 1.f / mTmpVecs[3]; final Rect clipRect = fromTransform.getClipRect(); outClip.left = (int) (clipRect.left * mTmpVecs[0] + 0.5f); outClip.right = (int) (clipRect.right * mTmpVecs[0] + 0.5f); outClip.top = (int) (clipRect.top * mTmpVecs[3] + 0.5f); outClip.bottom = (int) (clipRect.bottom * mTmpVecs[3] + 0.5f); } private void apply(SurfaceControl.Transaction t, SurfaceControl leash, SurfaceControl snapshot, float progress) { long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress); // update thumbnail surface mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform); t.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats); t.setAlpha(snapshot, mTmpTransform.getAlpha()); // update container surface mAnimation.getTransformation(currentPlayTime, mTmpTransform); final Matrix matrix = mTmpTransform.getMatrix(); t.setMatrix(leash, matrix, mTmpFloats); calcCurrentClipBounds(mTmpRect, mTmpTransform); t.setCrop(leash, mTmpRect); } private void apply(View view, SurfaceControl.Transaction tmpT, SurfaceControl leash, SurfaceControl snapshot, float progress) { long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress); // update thumbnail surface mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform); tmpT.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats); tmpT.setAlpha(snapshot, mTmpTransform.getAlpha()); // update container surface mAnimation.getTransformation(currentPlayTime, mTmpTransform); final Matrix matrix = mTmpTransform.getMatrix(); mTmpMatrix.set(matrix); // animationMatrix is applied after getTranslation, so "move" the translate to the end. mTmpMatrix.preTranslate(-view.getTranslationX(), -view.getTranslationY()); mTmpMatrix.postTranslate(view.getTranslationX(), view.getTranslationY()); view.setAnimationMatrix(mTmpMatrix); calcCurrentClipBounds(mTmpRect, mTmpTransform); tmpT.setCrop(leash, mTmpRect); view.setClipBounds(mTmpRect); // this takes stuff out of mTmpT so mTmpT can be re-used immediately view.getViewRootImpl().applyTransactionOnDraw(tmpT); } }
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java +53 −36 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.TransactionPool; import java.util.ArrayList; import java.util.function.Consumer; public class DefaultSurfaceAnimator { Loading @@ -58,42 +59,12 @@ public class DefaultSurfaceAnimator { // Animation length is already expected to be scaled. va.overrideDurationScale(1.0f); va.setDuration(anim.computeDurationHint()); va.addUpdateListener(updateListener); va.addListener(new AnimatorListenerAdapter() { // It is possible for the end/cancel to be called more than once, which may cause // issues if the animating surface has already been released. Track the finished // state here to skip duplicate callbacks. See b/252872225. private boolean mFinished; @Override public void onAnimationEnd(Animator animation) { onFinish(); } @Override public void onAnimationCancel(Animator animation) { onFinish(); } private void onFinish() { if (mFinished) return; mFinished = true; // Apply transformation of end state in case the animation is canceled. if (va.getAnimatedFraction() < 1f) { va.setCurrentFraction(1f); } setupValueAnimator(va, updateListener, (vanim) -> { pool.release(transaction); mainExecutor.execute(() -> { animations.remove(va); animations.remove(vanim); finishCallback.run(); }); // The update listener can continue to be called after the animation has ended if // end() is called manually again before the finisher removes the animation. // Remove it manually here to prevent animating a released surface. // See b/252872225. va.removeUpdateListener(updateListener); } }); animations.add(va); } Loading Loading @@ -188,4 +159,50 @@ public class DefaultSurfaceAnimator { } } } /** * Setup some callback logic on a value-animator. This helper ensures that a value animator * finishes at its final fraction (1f) and that relevant callbacks are only called once. */ public static ValueAnimator setupValueAnimator(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener, Consumer<ValueAnimator> afterFinish) { animator.addUpdateListener(updateListener); animator.addListener(new AnimatorListenerAdapter() { // It is possible for the end/cancel to be called more than once, which may cause // issues if the animating surface has already been released. Track the finished // state here to skip duplicate callbacks. See b/252872225. private boolean mFinished; @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { onFinish(); } @Override public void onAnimationCancel(Animator animation) { onFinish(); } private void onFinish() { if (mFinished) return; mFinished = true; // Apply transformation of end state in case the animation is canceled. if (animator.getAnimatedFraction() < 1f) { animator.setCurrentFraction(1f); } afterFinish.accept(animator); // The update listener can continue to be called after the animation has ended if // end() is called manually again before the finisher removes the animation. // Remove it manually here to prevent animating a released surface. // See b/252872225. animator.removeUpdateListener(updateListener); } }); return animator; } }
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +26 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; Loading @@ -69,6 +70,7 @@ import static com.android.wm.shell.transition.TransitionAnimationHelper.isCovere import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -104,6 +106,7 @@ import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.animation.SizeChangeAnimation; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; Loading Loading @@ -422,6 +425,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish); continue; } if (Flags.portWindowSizeAnimation() && isTask && TransitionInfo.isIndependent(change, info) && change.getSnapshot() != null) { startBoundsChangeAnimation(startTransaction, animations, change, onAnimFinish, mMainExecutor); continue; } } // Hide the invisible surface directly without animating it if there is a display Loading Loading @@ -734,6 +745,21 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } private void startBoundsChangeAnimation(@NonNull SurfaceControl.Transaction startT, @NonNull ArrayList<Animator> animations, @NonNull TransitionInfo.Change change, @NonNull Runnable finishCb, @NonNull ShellExecutor mainExecutor) { final SizeChangeAnimation sca = new SizeChangeAnimation(change.getStartAbsBounds(), change.getEndAbsBounds()); sca.initialize(change.getLeash(), change.getSnapshot(), startT); final ValueAnimator va = sca.buildAnimator(change.getLeash(), change.getSnapshot(), (animator) -> mainExecutor.execute(() -> { animations.remove(animator); finishCb.run(); })); va.setDuration(DEFAULT_APP_TRANSITION_DURATION); animations.add(va); } @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, Loading