Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 200c4c4c authored by Riddle Hsu's avatar Riddle Hsu Committed by Android (Google) Code Review
Browse files

Merge "Revert^2 "Extra common logic of default transition animation"" into main

parents 9bda1c92 e698208f
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -69,6 +69,17 @@ flag {
  bug: "291870756"
}

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"
+188 −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.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);
                }
            }
        }
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -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();
+34 −0
Original line number Diff line number Diff line
@@ -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.
@@ -186,6 +192,34 @@ public class DefaultTransitionHandlerTest extends ShellTestCase {
        verify(startT, never()).setColor(any(), any());
    }

    @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)