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

Commit 29bdef95 authored by Tony Huang's avatar Tony Huang
Browse files

Make AppTransition animations sharable (5/n)

Handle attachThumbnail animation.

Bug: 178678389
Test: manual
Change-Id: I033a79dcf0a54d782c438c98763c6a5ad00d2bbf
parent a2c4a757
Loading
Loading
Loading
Loading
+178 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;

import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -34,7 +35,12 @@ import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.os.SystemProperties;
import android.util.Slog;
@@ -72,9 +78,15 @@ public class TransitionAnimation {

    public static final int DEFAULT_APP_TRANSITION_DURATION = 336;

    /** Fraction of animation at which the recents thumbnail stays completely transparent */
    private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
    /** Fraction of animation at which the recents thumbnail becomes completely transparent */
    private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;

    /** Interpolator to be used for animations that respond directly to a touch */
    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
            new PathInterpolator(0.3f, 0f, 0.1f, 1f);

    private static final String DEFAULT_PACKAGE = "android";

    private final Context mContext;
@@ -86,7 +98,9 @@ public class TransitionAnimation {
            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
    private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
    private final Interpolator mDecelerateInterpolator;
    private final Interpolator mFastOutLinearInInterpolator;
    private final Interpolator mLinearOutSlowInInterpolator;
    private final Interpolator mThumbnailFadeInInterpolator;
    private final Interpolator mThumbnailFadeOutInterpolator;
    private final Rect mTmpFromClipRect = new Rect();
    private final Rect mTmpToClipRect = new Rect();
@@ -107,8 +121,19 @@ public class TransitionAnimation {

        mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
                com.android.internal.R.interpolator.decelerate_cubic);
        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
                com.android.internal.R.interpolator.fast_out_linear_in);
        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                com.android.internal.R.interpolator.linear_out_slow_in);
        mThumbnailFadeInInterpolator = input -> {
            // Linear response for first fraction, then complete after that.
            if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
                return 0f;
            }
            float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION)
                    / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
            return mFastOutLinearInInterpolator.getInterpolation(t);
        };
        mThumbnailFadeOutInterpolator = input -> {
            // Linear response for first fraction, then complete after that.
            if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
@@ -181,6 +206,13 @@ public class TransitionAnimation {
                DEFAULT_PACKAGE, com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
    }

    @Nullable
    public Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
        final Animation animation = loadCrossProfileAppThumbnailEnterAnimation();
        return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
                appRect.height(), 0, null);
    }

    /** Load animation by resource Id from specific package. */
    @Nullable
    public Animation loadAnimationRes(String packageName, int resId) {
@@ -720,6 +752,147 @@ public class TransitionAnimation {
                THUMBNAIL_APP_TRANSITION_DURATION, mTouchResponseInterpolator);
    }

    /**
     * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
     * when a thumbnail is specified with the pending animation override.
     */
    public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect,
            @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation,
            Rect startRect, Rect defaultStartRect, boolean scaleUp) {
        Animation a;
        final int thumbWidthI = thumbnailHeader.getWidth();
        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
        final int thumbHeightI = thumbnailHeader.getHeight();
        final int appWidth = appRect.width();

        float scaleW = appWidth / thumbWidth;
        getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
        final float fromX;
        float fromY;
        final float toX;
        float toY;
        final float pivotX;
        final float pivotY;
        if (shouldScaleDownThumbnailTransition(orientation)) {
            fromX = mTmpRect.left;
            fromY = mTmpRect.top;

            // For the curved translate animation to work, the pivot points needs to be at the
            // same absolute position as the one from the real surface.
            toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
            toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
            pivotX = mTmpRect.width() / 2;
            pivotY = appRect.height() / 2 / scaleW;
            if (mGridLayoutRecentsEnabled) {
                // In the grid layout, the header is displayed above the thumbnail instead of
                // overlapping it.
                fromY -= thumbHeightI;
                toY -= thumbHeightI * scaleW;
            }
        } else {
            pivotX = 0;
            pivotY = 0;
            fromX = mTmpRect.left;
            fromY = mTmpRect.top;
            toX = appRect.left;
            toY = appRect.top;
        }
        if (scaleUp) {
            // Animation up from the thumbnail to the full screen
            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
            Animation alpha = new AlphaAnimation(1f, 0f);
            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
            alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
            Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);

            mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
            mTmpToClipRect.set(appRect);

            // Containing frame is in screen space, but we need the clip rect in the
            // app space.
            mTmpToClipRect.offsetTo(0, 0);
            mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
            mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);

            if (contentInsets != null) {
                mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
                        (int) (-contentInsets.top * scaleW),
                        (int) (-contentInsets.right * scaleW),
                        (int) (-contentInsets.bottom * scaleW));
            }

            Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
            clipAnim.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);

            // This AnimationSet uses the Interpolators assigned above.
            AnimationSet set = new AnimationSet(false);
            set.addAnimation(scale);
            if (!mGridLayoutRecentsEnabled) {
                // In the grid layout, the header should be shown for the whole animation.
                set.addAnimation(alpha);
            }
            set.addAnimation(translate);
            set.addAnimation(clipAnim);
            a = set;
        } else {
            // Animation down from the full screen to the thumbnail
            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
            Animation alpha = new AlphaAnimation(0f, 1f);
            alpha.setInterpolator(mThumbnailFadeInInterpolator);
            alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
            Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);

            // This AnimationSet uses the Interpolators assigned above.
            AnimationSet set = new AnimationSet(false);
            set.addAnimation(scale);
            if (!mGridLayoutRecentsEnabled) {
                // In the grid layout, the header should be shown for the whole animation.
                set.addAnimation(alpha);
            }
            set.addAnimation(translate);
            a = set;

        }
        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
                null);
    }

    /**
     * Creates an overlay with a background color and a thumbnail for the cross profile apps
     * animation.
     */
    public HardwareBuffer createCrossProfileAppsThumbnail(
            @DrawableRes int thumbnailDrawableRes, Rect frame) {
        final int width = frame.width();
        final int height = frame.height();

        final Picture picture = new Picture();
        final Canvas canvas = picture.beginRecording(width, height);
        canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
        final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
        final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
        drawable.setBounds(
                (width - thumbnailSize) / 2,
                (height - thumbnailSize) / 2,
                (width + thumbnailSize) / 2,
                (height + thumbnailSize) / 2);
        drawable.setTint(mContext.getColor(android.R.color.white));
        drawable.draw(canvas);
        picture.endRecording();

        return Bitmap.createBitmap(picture).getHardwareBuffer();
    }

    /**
     * Prepares the specified animation with a standard duration, interpolator, etc.
     */
@@ -865,8 +1038,12 @@ public class TransitionAnimation {
    /**
     * Prepares the specified animation with a standard duration, interpolator, etc.
     */
    private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
    public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
            int appHeight, long duration, Interpolator interpolator) {
        if (a == null) {
            return null;
        }

        if (duration > 0) {
            a.setDuration(duration);
        }
+100 −6
Original line number Diff line number Diff line
@@ -42,12 +42,16 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
@@ -85,22 +89,29 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);

    private final TransactionPool mTransactionPool;
    private final Context mContext;
    private final ShellExecutor mMainExecutor;
    private final ShellExecutor mAnimExecutor;
    private final TransitionAnimation mTransitionAnimation;

    private final SurfaceSession mSurfaceSession = new SurfaceSession();

    /** Keeps track of the currently-running animations associated with each transition. */
    private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();

    private final Rect mInsets = new Rect(0, 0, 0, 0);
    private float mTransitionAnimationScaleSetting = 1.0f;

    private final int mCurrentUserId;

    DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
        mTransactionPool = transactionPool;
        mContext = context;
        mMainExecutor = mainExecutor;
        mAnimExecutor = animExecutor;
        mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
        mCurrentUserId = UserHandle.myUserId();

        AttributeCache.init(context);
    }
@@ -142,7 +153,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {

            Animation a = loadAnimation(info, change);
            if (a != null) {
                startAnimInternal(animations, a, change.getLeash(), onAnimFinish);
                startAnimInternal(animations, a, change.getLeash(), onAnimFinish,
                        null /* position */);

                if (info.getAnimationOptions() != null) {
                    attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
                }
            }
        }
        t.apply();
@@ -165,7 +181,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {

    @Nullable
    private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change) {
        // TODO(b/178678389): It should handle more type animation here
        Animation a = null;

        final int type = info.getType();
@@ -269,7 +284,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
    }

    private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback) {
            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback,
            @Nullable Point position) {
        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
        final Transformation transformation = new Transformation();
@@ -280,11 +296,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
        va.addUpdateListener(animation -> {
            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());

            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix);
            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
                    position);
        });

        final Runnable finisher = () -> {
            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix);
            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
                    position);

            mTransactionPool.release(transaction);
            mMainExecutor.execute(() -> {
@@ -307,9 +325,85 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
        mAnimExecutor.execute(va::start);
    }

    private void attachThumbnail(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, TransitionInfo.Change change,
            TransitionInfo.AnimationOptions options) {
        final boolean isTask = change.getTaskInfo() != null;
        final boolean isOpen = Transitions.isOpeningType(change.getMode());
        final boolean isClose = Transitions.isClosingType(change.getMode());
        if (isOpen) {
            if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
                attachCrossProfileThunmbnailAnimation(animations, finishCallback, change);
            } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
                attachThumbnailAnimation(animations, finishCallback, change, options);
            }
        } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
            attachThumbnailAnimation(animations, finishCallback, change, options);
        }
    }

    private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, TransitionInfo.Change change) {
        final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
                ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
        final Rect bounds = change.getEndAbsBounds();
        final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
                thumbnailDrawableRes, bounds);
        if (thumbnail == null) {
            return;
        }

        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
        final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
                change.getLeash(), thumbnail, transaction);
        final Animation a =
                mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds);
        if (a == null) {
            return;
        }

        final Runnable finisher = () -> {
            wt.destroy(transaction);
            mTransactionPool.release(transaction);

            finishCallback.run();
        };
        a.restrictDuration(MAX_ANIMATION_DURATION);
        a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
        startAnimInternal(animations, a, wt.getSurface(), finisher,
                new Point(bounds.left, bounds.top));
    }

    private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, TransitionInfo.Change change,
            TransitionInfo.AnimationOptions options) {
        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
        final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
                change.getLeash(), options.getThumbnail(), transaction);
        final Rect bounds = change.getEndAbsBounds();
        final int orientation = mContext.getResources().getConfiguration().orientation;
        final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds,
                mInsets, options.getThumbnail(), orientation, null /* startRect */,
                options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP);

        final Runnable finisher = () -> {
            wt.destroy(transaction);
            mTransactionPool.release(transaction);

            finishCallback.run();
        };
        a.restrictDuration(MAX_ANIMATION_DURATION);
        a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
        startAnimInternal(animations, a, wt.getSurface(), finisher, null /* position */);
    }

    private static void applyTransformation(long time, SurfaceControl.Transaction t,
            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) {
            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
            Point position) {
        anim.getTransformation(time, transformation);
        if (position != null) {
            transformation.getMatrix().postTranslate(position.x, position.y);
        }
        t.setMatrix(leash, transformation.getMatrix(), matrix);
        t.setAlpha(leash, transformation.getAlpha());
        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.hardware.HardwareBuffer;
import android.view.SurfaceControl;
import android.view.SurfaceSession;

/**
 * Represents a surface that is displayed over a transition surface.
 */
class WindowThumbnail {

    private SurfaceControl mSurfaceControl;

    private WindowThumbnail() {}

    /** Create a thumbnail surface and attach it over a parent surface. */
    static WindowThumbnail createAndAttach(SurfaceSession surfaceSession, SurfaceControl parent,
            HardwareBuffer thumbnailHeader, SurfaceControl.Transaction t) {
        WindowThumbnail windowThumbnail = new WindowThumbnail();
        windowThumbnail.mSurfaceControl = new SurfaceControl.Builder(surfaceSession)
                .setParent(parent)
                .setName("WindowThumanil : " + parent.toString())
                .setCallsite("WindowThumanil")
                .setFormat(PixelFormat.TRANSLUCENT)
                .build();

        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(thumbnailHeader);
        t.setBuffer(windowThumbnail.mSurfaceControl, graphicBuffer);
        t.setColorSpace(windowThumbnail.mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
        t.setLayer(windowThumbnail.mSurfaceControl, Integer.MAX_VALUE);
        t.show(windowThumbnail.mSurfaceControl);
        t.apply();

        return windowThumbnail;
    }

    SurfaceControl getSurface() {
        return mSurfaceControl;
    }

    /** Remove the thumbnail surface and release the surface. */
    void destroy(SurfaceControl.Transaction t) {
        if (mSurfaceControl == null) {
            return;
        }

        t.remove(mSurfaceControl);
        t.apply();
        mSurfaceControl.release();
        mSurfaceControl = null;
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -6514,8 +6514,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        }
        final Configuration displayConfig = mDisplayContent.getConfiguration();
        return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
                appRect, insets, thumbnailHeader, task, displayConfig.uiMode,
                displayConfig.orientation);
                appRect, insets, thumbnailHeader, task, displayConfig.orientation);
    }

    @Override
+11 −156

File changed.

Preview size limit exceeded, changes collapsed.