summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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(