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

Commit df04e229 authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Treat two TaskFragment leashes as one for TASK_FRAGMENT_OPEN/CLOSE" into sc-v2-dev

parents 43ba88d7 33a00c00
Loading
Loading
Loading
Loading
+119 −34
Original line number Diff line number Diff line
@@ -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());
    }

    /** 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());
        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());

        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();
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -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;
+63 −18
Original line number Diff line number Diff line
@@ -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<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        final List<TaskFragmentAnimationAdapter> 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<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
        return createOpenCloseAnimationAdapters(targets,
                mAnimationSpec::loadCloseAnimation);
    }

    private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets,
            @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> 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<RemoteAnimationTarget> openingTargets = new ArrayList<>();
        final List<RemoteAnimationTarget> 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<TaskFragmentAnimationAdapter> 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<RemoteAnimationTarget, Rect, Animation> 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<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        final List<TaskFragmentAnimationAdapter> 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.
+16 −6
Original line number Diff line number Diff line
@@ -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 {