summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Riddle Hsu <riddlehsu@google.com> 2024-09-24 17:41:11 +0800
committer Riddle Hsu <riddlehsu@google.com> 2024-09-24 12:19:26 +0000
commite698208f582641f7227288d311cf5e297c00e42a (patch)
tree913295f65ecae417bbb3123ee6e8b8ce14f012a6
parent0776993706eb3c832fbb4ce191a04fa2120e8fb1 (diff)
Revert^2 "Extra common logic of default transition animation"
Extra common logic of default transition animation Make it more flexible to run other animation implementation. This reverts commit 625ff9d80e2896e45e441e141a4e9a13029a169b. Reason for revert: reland The previous patch has a problem by calling AnimationAdapter#onAnimationUpdate again at the end of animation. Originally it uses va.getDuration() as currentPlayTime, which will apply the end state of transformation. The problem is that the adapter uses animator.getCurrentPlayTime(), which will be 0 if the animation is finished. That causes the transformation to apply the start state. Then the target surface could move to original offset which may be outside screen. The fix is to check if it is animating the last frame, then use va.getDuration() as currentPlayTime. This also reduces duplicated surface transactions for the last frame if the animation ends normally. Note that the animated fraction will be 1 if the animation plays to end or ValueAnimator#end is called before animation finishes. Only ValueAnimator#cancel may satisfy the condition "va.getAnimatedFraction() < 1f". So far there is no cancel usages. Just make sure that end state is always applied. Bug: 326331384 Flag: com.android.window.flags.common_surface_animator Test: atest DefaultTransitionHandler#testBuildSurfaceAnimation Change-Id: I082bfe2e57e4374cca9fb30c260051fbafbd21f7
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java188
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java34
4 files changed, 238 insertions, 0 deletions
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index b6aad1145880..25af5d1456a3 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -70,6 +70,17 @@ flag {
}
flag {
+ name: "common_surface_animator"
+ namespace: "windowing_frontend"
+ description: "A reusable surface animator for default transition"
+ bug: "326331384"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "reduce_keyguard_transitions"
namespace: "windowing_frontend"
description: "Avoid setting keyguard transitions ready unless there are no other changes"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
new file mode 100644
index 000000000000..a1a9ca9fd2bd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -0,0 +1,188 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.TransactionPool;
+
+import java.util.ArrayList;
+
+public class DefaultSurfaceAnimator {
+
+ /** Builds an animator for the surface and adds it to the `animations` list. */
+ static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Animation anim, @NonNull SurfaceControl leash,
+ @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
+ @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
+ @Nullable Rect clipRect, boolean isActivity) {
+ final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash,
+ position, clipRect, cornerRadius, isActivity);
+ buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter);
+ }
+
+ /** Builds an animator for the surface and adds it to the `animations` list. */
+ static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Animation anim, @NonNull Runnable finishCallback,
+ @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor,
+ @NonNull AnimationAdapter updateListener) {
+ final SurfaceControl.Transaction transaction = pool.acquire();
+ updateListener.setTransaction(transaction);
+ final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+ // 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);
+ }
+
+ pool.release(transaction);
+ mainExecutor.execute(() -> {
+ animations.remove(va);
+ 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);
+ }
+
+ /** The animation adapter for buildSurfaceAnimation. */
+ abstract static class AnimationAdapter implements ValueAnimator.AnimatorUpdateListener {
+ @NonNull final SurfaceControl mLeash;
+ @NonNull SurfaceControl.Transaction mTransaction;
+ private Choreographer mChoreographer;
+
+ AnimationAdapter(@NonNull SurfaceControl leash) {
+ mLeash = leash;
+ }
+
+ void setTransaction(@NonNull SurfaceControl.Transaction transaction) {
+ mTransaction = transaction;
+ }
+
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animator) {
+ // The finish callback in buildSurfaceAnimation will ensure that the animation ends
+ // with fraction 1.
+ final long currentPlayTime = animator.getAnimatedFraction() >= 1f
+ ? animator.getDuration()
+ : Math.min(animator.getDuration(), animator.getCurrentPlayTime());
+ applyTransformation(animator, currentPlayTime);
+ if (mChoreographer == null) {
+ mChoreographer = Choreographer.getInstance();
+ }
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ }
+
+ abstract void applyTransformation(@NonNull ValueAnimator animator, long currentPlayTime);
+ }
+
+ private static class DefaultAnimationAdapter extends AnimationAdapter {
+ final Transformation mTransformation = new Transformation();
+ final float[] mMatrix = new float[9];
+ @NonNull final Animation mAnim;
+ @Nullable final Point mPosition;
+ @Nullable final Rect mClipRect;
+ final float mCornerRadius;
+ final boolean mIsActivity;
+
+ DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash,
+ @Nullable Point position, @Nullable Rect clipRect, float cornerRadius,
+ boolean isActivity) {
+ super(leash);
+ mAnim = anim;
+ mPosition = (position != null && (position.x != 0 || position.y != 0))
+ ? position : null;
+ mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
+ mCornerRadius = cornerRadius;
+ mIsActivity = isActivity;
+ }
+
+ @Override
+ void applyTransformation(@NonNull ValueAnimator animator, long currentPlayTime) {
+ final Transformation transformation = mTransformation;
+ final SurfaceControl.Transaction t = mTransaction;
+ final SurfaceControl leash = mLeash;
+ transformation.clear();
+ mAnim.getTransformation(currentPlayTime, transformation);
+ if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+ && mIsActivity && mAnim.getExtensionEdges() != 0) {
+ t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges());
+ }
+ if (mPosition != null) {
+ transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
+ }
+ t.setMatrix(leash, transformation.getMatrix(), mMatrix);
+ t.setAlpha(leash, transformation.getAlpha());
+
+ if (mClipRect != null) {
+ Rect clipRect = mClipRect;
+ final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+ if (!extensionInsets.equals(Insets.NONE)) {
+ // Clip out any overflowing edge extension.
+ clipRect = new Rect(mClipRect);
+ clipRect.inset(extensionInsets);
+ t.setCrop(leash, clipRect);
+ }
+ if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) {
+ // Rounded corner can only be applied if a crop is set.
+ t.setCrop(leash, clipRect);
+ t.setCornerRadius(leash, mCornerRadius);
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f40e0bac1b4e..d3bed59f7994 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -826,6 +826,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
@Nullable Rect clipRect, boolean isActivity) {
+ if (Flags.commonSurfaceAnimator()) {
+ DefaultSurfaceAnimator.buildSurfaceAnimation(animations, anim, leash, finishCallback,
+ pool, mainExecutor, position, cornerRadius, clipRect, isActivity);
+ return;
+ }
final SurfaceControl.Transaction transaction = pool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final Transformation transformation = new Transformation();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 0c18229f38d0..e540322a96a1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -25,11 +25,14 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_SYNC;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
@@ -37,6 +40,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.view.SurfaceControl;
+import android.view.animation.AlphaAnimation;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -56,6 +60,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
/**
* Tests for the default animation handler that is used if no other special-purpose handler picks
* up an animation request.
@@ -187,6 +193,34 @@ public class DefaultTransitionHandlerTest extends ShellTestCase {
}
@Test
+ public void testBuildSurfaceAnimation() {
+ final ArrayList<Animator> animators = new ArrayList<>();
+ final AlphaAnimation animation = new AlphaAnimation(0, 1);
+ final long durationMs = 500;
+ animation.setDuration(durationMs);
+ final long[] lastCurrentPlayTime = new long[1];
+ final int[] finishCount = new int[1];
+ final Runnable finishCallback = () -> finishCount[0]++;
+ DefaultSurfaceAnimator.buildSurfaceAnimation(animators, animation, finishCallback,
+ mTransactionPool, mMainExecutor,
+ new DefaultSurfaceAnimator.AnimationAdapter(mock(SurfaceControl.class)) {
+ @Override
+ void applyTransformation(ValueAnimator animator, long currentPlayTime) {
+ lastCurrentPlayTime[0] = currentPlayTime;
+ }
+ });
+ final ValueAnimator animator = (ValueAnimator) animators.get(0);
+ mAnimExecutor.execute(() -> {
+ animator.start();
+ animator.end();
+ });
+ flushHandlers();
+ assertEquals(durationMs, lastCurrentPlayTime[0]);
+ assertEquals(1f, animator.getAnimatedFraction(), 0f /* delta */);
+ assertEquals(1, finishCount[0]);
+ }
+
+ @Test
public void startAnimation_freeformOpenChange_doesntReparentTask() {
final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
.setTask(createTaskInfo(