diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 23b8ee4a019f5..6430394804d25 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -16,6 +16,12 @@ package android.window; +import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; +import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; +import static android.app.ActivityOptions.ANIM_SCALE_UP; +import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; +import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -31,6 +37,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; @@ -102,6 +109,8 @@ public final class TransitionInfo implements Parcelable { private SurfaceControl mRootLeash; private final Point mRootOffset = new Point(); + private AnimationOptions mOptions; + /** @hide */ public TransitionInfo(@WindowManager.TransitionOldType int type, @WindowManager.TransitionFlags int flags) { @@ -116,6 +125,7 @@ public final class TransitionInfo implements Parcelable { mRootLeash = new SurfaceControl(); mRootLeash.readFromParcel(in); mRootOffset.readFromParcel(in); + mOptions = in.readTypedObject(AnimationOptions.CREATOR); } @Override @@ -126,6 +136,7 @@ public final class TransitionInfo implements Parcelable { dest.writeList(mChanges); mRootLeash.writeToParcel(dest, flags); mRootOffset.writeToParcel(dest, flags); + dest.writeTypedObject(mOptions, flags); } @NonNull @@ -154,6 +165,10 @@ public final class TransitionInfo implements Parcelable { mRootOffset.set(offsetLeft, offsetTop); } + public void setAnimationOptions(AnimationOptions options) { + mOptions = options; + } + public int getType() { return mType; } @@ -182,6 +197,10 @@ public final class TransitionInfo implements Parcelable { return mRootOffset; } + public AnimationOptions getAnimationOptions() { + return mOptions; + } + @NonNull public List getChanges() { return mChanges; @@ -484,4 +503,146 @@ public final class TransitionInfo implements Parcelable { + mStartRotation + "->" + mEndRotation + "}"; } } + + /** Represents animation options during a transition */ + public static final class AnimationOptions implements Parcelable { + + private int mType; + private int mEnterResId; + private int mExitResId; + private boolean mOverrideTaskTransition; + private String mPackageName; + private final Rect mTransitionBounds = new Rect(); + private HardwareBuffer mThumbnail; + + private AnimationOptions(int type) { + mType = type; + } + + public AnimationOptions(Parcel in) { + mType = in.readInt(); + mEnterResId = in.readInt(); + mExitResId = in.readInt(); + mOverrideTaskTransition = in.readBoolean(); + mPackageName = in.readString(); + mTransitionBounds.readFromParcel(in); + mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR); + } + + public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId, + int exitResId, boolean overrideTaskTransition) { + AnimationOptions options = new AnimationOptions(ANIM_CUSTOM); + options.mPackageName = packageName; + options.mEnterResId = enterResId; + options.mExitResId = exitResId; + options.mOverrideTaskTransition = overrideTaskTransition; + return options; + } + + public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width, + int height) { + AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL); + options.mTransitionBounds.set(startX, startY, startX + width, startY + height); + return options; + } + + public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width, + int height) { + AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP); + options.mTransitionBounds.set(startX, startY, startX + width, startY + height); + return options; + } + + public static AnimationOptions makeThumnbnailAnimOptions(HardwareBuffer srcThumb, + int startX, int startY, boolean scaleUp) { + AnimationOptions options = new AnimationOptions( + scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN); + options.mTransitionBounds.set(startX, startY, startX, startY); + options.mThumbnail = srcThumb; + return options; + } + + public static AnimationOptions makeCrossProfileAnimOptions() { + AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS); + return options; + } + + public int getType() { + return mType; + } + + public int getEnterResId() { + return mEnterResId; + } + + public int getExitResId() { + return mExitResId; + } + + public boolean getOverrideTaskTransition() { + return mOverrideTaskTransition; + } + + public String getPackageName() { + return mPackageName; + } + + public Rect getTransitionBounds() { + return mTransitionBounds; + } + + public HardwareBuffer getThumbnail() { + return mThumbnail; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mEnterResId); + dest.writeInt(mExitResId); + dest.writeBoolean(mOverrideTaskTransition); + dest.writeString(mPackageName); + mTransitionBounds.writeToParcel(dest, flags); + dest.writeTypedObject(mThumbnail, flags); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public AnimationOptions createFromParcel(Parcel in) { + return new AnimationOptions(in); + } + + @Override + public AnimationOptions[] newArray(int size) { + return new AnimationOptions[size]; + } + }; + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + @NonNull + private static String typeToString(int mode) { + switch(mode) { + case ANIM_CUSTOM: return "ANIM_CUSTOM"; + case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL"; + case ANIM_SCALE_UP: return "ANIM_SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN"; + case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS"; + default: return ""; + } + } + + @Override + public String toString() { + return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName + + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}"; + } + } } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 60a8d802861f9..d3224b13e312d 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -16,16 +16,20 @@ package com.android.internal.policy; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE; 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 static android.view.WindowManager.TRANSIT_OPEN; +import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -34,12 +38,18 @@ 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; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; +import android.view.WindowManager.TransitionType; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -56,11 +66,17 @@ import java.util.List; /** @hide */ public class TransitionAnimation { + public static final int WALLPAPER_TRANSITION_NONE = 0; + public static final int WALLPAPER_TRANSITION_OPEN = 1; + public static final int WALLPAPER_TRANSITION_CLOSE = 2; + public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3; + public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4; + // These are the possible states for the enter/exit activities during a thumbnail transition - public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; - public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1; - public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2; - public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3; + private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; + private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1; + private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2; + private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3; /** * Maximum duration for the clip reveal animation. This is used when there is a lot of movement @@ -72,9 +88,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 +108,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 +131,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 +216,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) { @@ -347,8 +389,15 @@ public class TransitionAnimation { } } - public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame, - Rect displayFrame, Rect startRect) { + public Animation createClipRevealAnimationLocked(@TransitionType int transit, + int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) { + return createClipRevealAnimationLockedCompat( + getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame, + startRect); + } + + public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit, + boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) { final Animation anim; if (enter) { final int appWidth = appFrame.width(); @@ -458,8 +507,14 @@ public class TransitionAnimation { return anim; } - public Animation createScaleUpAnimationLocked(int transit, boolean enter, - Rect containingFrame, Rect startRect) { + public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit, + boolean enter, Rect containingFrame, Rect startRect) { + return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit), + enter, containingFrame, startRect); + } + + public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit, + boolean enter, Rect containingFrame, Rect startRect) { Animation a; setupDefaultNextAppTransitionStartRect(startRect, mTmpRect); final int appWidth = containingFrame.width(); @@ -514,12 +569,19 @@ public class TransitionAnimation { return a; } + public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp, + Rect containingFrame, @TransitionType int transit, int wallpaperTransit, + HardwareBuffer thumbnailHeader, Rect startRect) { + return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame, + getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect); + } + /** * This animation is created when we are doing a thumbnail transition, for the activity that is * leaving, and the activity that is entering. */ - public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, - Rect containingFrame, int transit, HardwareBuffer thumbnailHeader, + public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp, + Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader, Rect startRect) { final int appWidth = containingFrame.width(); final int appHeight = containingFrame.height(); @@ -529,6 +591,7 @@ public class TransitionAnimation { final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight; final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp); switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { @@ -587,8 +650,8 @@ public class TransitionAnimation { * This alternate animation is created when we are doing a thumbnail transition, for the * activity that is leaving, and the activity that is entering. */ - public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, - int orientation, int transit, Rect containingFrame, Rect contentInsets, + public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter, + boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets, @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform, Rect startRect, Rect defaultStartRect) { Animation a; @@ -601,11 +664,11 @@ public class TransitionAnimation { final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left; final int thumbStartY = mTmpRect.top - containingFrame.top; + final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp); switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { - final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP; if (freeform && scaleUp) { a = createAspectScaledThumbnailEnterFreeformAnimationLocked( containingFrame, surfaceInsets, startRect, defaultStartRect); @@ -719,11 +782,152 @@ 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. */ private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, - int transit) { + @TransitionOldType int transit) { // Pick the desired duration. If this is an inter-activity transition, // it is the standard duration for that. Otherwise we use the longer // task transition duration. @@ -820,6 +1024,22 @@ public class TransitionAnimation { return anim; } + private static @TransitionOldType int getTransitCompatType(@TransitionType int transit, + int wallpaperTransit) { + if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { + return TRANSIT_OLD_WALLPAPER_INTRA_OPEN; + } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { + return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; + } else if (transit == TRANSIT_OPEN) { + return TRANSIT_OLD_ACTIVITY_OPEN; + } else if (transit == TRANSIT_CLOSE) { + return TRANSIT_OLD_ACTIVITY_CLOSE; + } + + // We only do some special handle for above type, so use type NONE for default behavior. + return TRANSIT_OLD_NONE; + } + /** * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that * the start rect is outside of the target rect, and there is a lot of movement going on. @@ -842,11 +1062,34 @@ public class TransitionAnimation { * (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION)); } + /** + * Return the current thumbnail transition state. + */ + private int getThumbnailTransitionState(boolean enter, boolean scaleUp) { + if (enter) { + if (scaleUp) { + return THUMBNAIL_TRANSITION_ENTER_SCALE_UP; + } else { + return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; + } + } else { + if (scaleUp) { + return THUMBNAIL_TRANSITION_EXIT_SCALE_UP; + } else { + return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; + } + } + } + /** * 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); } 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 0726114a6df96..4eadf8c782874 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 @@ -16,6 +16,13 @@ package com.android.wm.shell.transition; +import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; +import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.app.ActivityOptions.ANIM_NONE; +import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; +import static android.app.ActivityOptions.ANIM_SCALE_UP; +import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; +import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; @@ -29,17 +36,28 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; 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; @@ -61,23 +79,45 @@ import java.util.ArrayList; public class DefaultTransitionHandler implements Transitions.TransitionHandler { private static final int MAX_ANIMATION_DURATION = 3000; + /** + * Restrict ability of activities overriding transition animation in a way such that + * an activity can do it only when the transition happens within a same task. + * + * @see android.app.Activity#overridePendingTransition(int, int) + */ + private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY = + "persist.wm.disable_custom_task_animation"; + + /** + * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY + */ + static boolean sDisableCustomTaskAnimationProperty = + 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> 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); } @@ -101,6 +141,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { mAnimations.remove(transition); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); }; + + final int wallpaperTransit = getWallpaperTransitType(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getMode() == TRANSIT_CHANGE) { @@ -118,9 +160,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // Don't animate anything that isn't independent. if (!TransitionInfo.isIndependent(change, info)) continue; - Animation a = loadAnimation(info.getType(), info.getFlags(), change); + Animation a = loadAnimation(info, change, wallpaperTransit); 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()); + } } } startTransaction.apply(); @@ -142,68 +189,111 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } @Nullable - private Animation loadAnimation(int type, int flags, TransitionInfo.Change change) { - // TODO(b/178678389): It should handle more type animation here + private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change, + int wallpaperTransit) { Animation a = null; - final boolean isOpening = Transitions.isOpeningType(type); + final int type = info.getType(); + final int flags = info.getFlags(); final int changeMode = change.getMode(); final int changeFlags = change.getFlags(); + final boolean isOpeningType = Transitions.isOpeningType(type); + final boolean enter = Transitions.isOpeningType(changeMode); + final boolean isTask = change.getTaskInfo() != null; + final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); + final int overrideType = options != null ? options.getType() : ANIM_NONE; + final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true; if (type == TRANSIT_RELAUNCH) { a = mTransitionAnimation.createRelaunchAnimation( - change.getStartAbsBounds(), mInsets, change.getEndAbsBounds()); + change.getEndAbsBounds(), mInsets, change.getEndAbsBounds()); } else if (type == TRANSIT_KEYGUARD_GOING_AWAY) { a = mTransitionAnimation.loadKeyguardExitAnimation(flags, (changeFlags & FLAG_SHOW_WALLPAPER) != 0); } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) { a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(); - } else if (changeMode == TRANSIT_OPEN && isOpening) { - if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - // This received a transferred starting window, so don't animate - return null; - } - - if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { - a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */); - } else if (change.getTaskInfo() != null) { - a = mTransitionAnimation.loadDefaultAnimationAttr( - R.styleable.WindowAnimation_taskOpenEnterAnimation); + } else if (overrideType == ANIM_CUSTOM + && (canCustomContainer || options.getOverrideTaskTransition())) { + a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter + ? options.getEnterResId() : options.getExitResId()); + } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) { + a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); + } else if (overrideType == ANIM_CLIP_REVEAL) { + a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter, + change.getEndAbsBounds(), change.getEndAbsBounds(), + options.getTransitionBounds()); + } else if (overrideType == ANIM_SCALE_UP) { + a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter, + change.getEndAbsBounds(), options.getTransitionBounds()); + } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP + || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) { + final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP; + a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp, + change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(), + options.getTransitionBounds()); + } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation + : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation); + } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation + : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation); + } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation + : R.styleable.WindowAnimation_wallpaperOpenExitAnimation); + } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation + : R.styleable.WindowAnimation_wallpaperCloseExitAnimation); + } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { + if (isOpeningType) { + a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter); } else { - a = mTransitionAnimation.loadDefaultAnimationRes( - (changeFlags & FLAG_TRANSLUCENT) == 0 - ? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter); + a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter); } - } else if (changeMode == TRANSIT_TO_FRONT && isOpening) { - if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - // This received a transferred starting window, so don't animate - return null; - } - - if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { - a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */); + } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) { + // This received a transferred starting window, so don't animate + return null; + } else if (type == TRANSIT_OPEN) { + if (isTask) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_taskOpenEnterAnimation + : R.styleable.WindowAnimation_taskOpenExitAnimation); } else { - a = mTransitionAnimation.loadDefaultAnimationAttr( - R.styleable.WindowAnimation_taskToFrontEnterAnimation); + if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) { + a = mTransitionAnimation.loadDefaultAnimationRes( + R.anim.activity_translucent_open_enter); + } else { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_activityOpenEnterAnimation + : R.styleable.WindowAnimation_activityOpenExitAnimation); + } } - } else if (changeMode == TRANSIT_CLOSE && !isOpening) { - if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { - a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */); - } else if (change.getTaskInfo() != null) { - a = mTransitionAnimation.loadDefaultAnimationAttr( - R.styleable.WindowAnimation_taskCloseExitAnimation); + } else if (type == TRANSIT_TO_FRONT) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_taskToFrontEnterAnimation + : R.styleable.WindowAnimation_taskToFrontExitAnimation); + } else if (type == TRANSIT_CLOSE) { + if (isTask) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_taskCloseEnterAnimation + : R.styleable.WindowAnimation_taskCloseExitAnimation); } else { - a = mTransitionAnimation.loadDefaultAnimationRes( - (changeFlags & FLAG_TRANSLUCENT) == 0 - ? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit); - } - } else if (changeMode == TRANSIT_TO_BACK && !isOpening) { - if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { - a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */); - } else { - a = mTransitionAnimation.loadDefaultAnimationAttr( - R.styleable.WindowAnimation_taskToBackExitAnimation); + if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { + a = mTransitionAnimation.loadDefaultAnimationRes( + R.anim.activity_translucent_close_exit); + } else { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_activityCloseEnterAnimation + : R.styleable.WindowAnimation_activityCloseExitAnimation); + } } + } else if (type == TRANSIT_TO_BACK) { + a = mTransitionAnimation.loadDefaultAnimationAttr(enter + ? R.styleable.WindowAnimation_taskToBackEnterAnimation + : R.styleable.WindowAnimation_taskToBackExitAnimation); } else if (changeMode == TRANSIT_CHANGE) { // In the absence of a specific adapter, we just want to keep everything stationary. a = new AlphaAnimation(1.f, 1.f); @@ -211,17 +301,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } if (a != null) { - Rect start = change.getStartAbsBounds(); - Rect end = change.getEndAbsBounds(); + if (!a.isInitialized()) { + Rect end = change.getEndAbsBounds(); + a.initialize(end.width(), end.height(), end.width(), end.height()); + } a.restrictDuration(MAX_ANIMATION_DURATION); - a.initialize(end.width(), end.height(), start.width(), start.height()); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); } return a; } private void startAnimInternal(@NonNull ArrayList 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(); @@ -232,11 +324,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(() -> { @@ -259,9 +353,112 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { mAnimExecutor.execute(va::start); } + private void attachThumbnail(@NonNull ArrayList 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 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 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 int getWallpaperTransitType(TransitionInfo info) { + boolean hasOpenWallpaper = false; + boolean hasCloseWallpaper = false; + + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) { + if (Transitions.isOpeningType(change.getMode())) { + hasOpenWallpaper = true; + } else if (Transitions.isClosingType(change.getMode())) { + hasCloseWallpaper = true; + } + } + } + + if (hasOpenWallpaper && hasCloseWallpaper) { + return Transitions.isOpeningType(info.getType()) + ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE; + } else if (hasOpenWallpaper) { + return WALLPAPER_TRANSITION_OPEN; + } else if (hasCloseWallpaper) { + return WALLPAPER_TRANSITION_CLOSE; + } else { + return WALLPAPER_TRANSITION_NONE; + } + } + 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()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java new file mode 100644 index 0000000000000..2c668ed3d84d0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java @@ -0,0 +1,71 @@ +/* + * 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; + } +} diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 26f475ec8b293..579d620eed6ca 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -68,6 +68,7 @@ import android.service.voice.VoiceInteractionManagerInternal; import android.util.Slog; import android.view.RemoteAnimationDefinition; import android.window.SizeConfigurationBuckets; +import android.window.TransitionInfo; import com.android.internal.app.AssistUtils; import com.android.internal.policy.IKeyguardDismissCallback; @@ -1014,6 +1015,9 @@ class ActivityClientController extends IActivityClientController.Stub { r.mDisplayContent.mAppTransition.overridePendingAppTransition( packageName, enterAnim, exitAnim, null, null, r.mOverrideTaskTransition); + mService.getTransitionController().setOverrideAnimation( + TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName, + enterAnim, exitAnim, r.mOverrideTaskTransition)); } } Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 065dc6e4f8edb..84782c420e2ca 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -313,6 +313,7 @@ import android.window.IRemoteTransition; import android.window.SizeConfigurationBuckets; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; +import android.window.TransitionInfo.AnimationOptions; import android.window.WindowContainerToken; import com.android.internal.R; @@ -4166,6 +4167,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void applyOptionsAnimation(ActivityOptions pendingOptions, Intent intent) { final int animationType = pendingOptions.getAnimationType(); final DisplayContent displayContent = getDisplayContent(); + AnimationOptions options = null; switch (animationType) { case ANIM_CUSTOM: displayContent.mAppTransition.overridePendingAppTransition( @@ -4175,11 +4177,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingOptions.getAnimationStartedListener(), pendingOptions.getAnimationFinishedListener(), pendingOptions.getOverrideTaskTransition()); + options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(), + pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), + pendingOptions.getOverrideTaskTransition()); break; case ANIM_CLIP_REVEAL: displayContent.mAppTransition.overridePendingAppTransitionClipReveal( pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getWidth(), pendingOptions.getHeight()); + options = AnimationOptions.makeClipRevealAnimOptions( + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight()); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -4191,6 +4199,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A displayContent.mAppTransition.overridePendingAppTransitionScaleUp( pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getWidth(), pendingOptions.getHeight()); + options = AnimationOptions.makeScaleUpAnimOptions( + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight()); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -4206,6 +4217,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getAnimationStartedListener(), scaleUp); + options = AnimationOptions.makeThumnbnailAnimOptions(buffer, + pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp); if (intent.getSourceBounds() == null && buffer != null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -4245,6 +4258,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A case ANIM_OPEN_CROSS_PROFILE_APPS: displayContent.mAppTransition .overridePendingAppTransitionStartCrossProfileApps(); + options = AnimationOptions.makeCrossProfileAnimOptions(); break; case ANIM_NONE: case ANIM_UNDEFINED: @@ -4253,6 +4267,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType); break; } + + if (options != null) { + mAtmService.getTransitionController().setOverrideAnimation(options); + } } void clearAllDrawn() { @@ -6597,8 +6615,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 diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c1b287ff8077a..ac687dc064ce7 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -78,10 +78,7 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; -import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP; +import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE; @@ -102,12 +99,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; 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.Binder; import android.os.Debug; @@ -132,7 +124,6 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; -import android.view.animation.ClipRectAnimation; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.view.animation.ScaleAnimation; @@ -638,24 +629,6 @@ public class AppTransition implements Dump { (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION)); } - /** - * Prepares the specified animation with a standard duration, interpolator, etc. - */ - Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth, - int appHeight, long duration, Interpolator interpolator) { - if (a != null) { - if (duration > 0) { - a.setDuration(duration); - } - a.setFillAfter(true); - if (interpolator != null) { - a.setInterpolator(interpolator); - } - a.initialize(appWidth, appHeight, appWidth, appHeight); - } - return a; - } - /** * Prepares the specified animation with a standard duration, interpolator, etc. */ @@ -677,57 +650,17 @@ public class AppTransition implements Dump { mDecelerateInterpolator); } - /** - * Return the current thumbnail transition state. - */ - int getThumbnailTransitionState(boolean enter) { - if (enter) { - if (mNextAppTransitionScaleUp) { - return THUMBNAIL_TRANSITION_ENTER_SCALE_UP; - } else { - return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; - } - } else { - if (mNextAppTransitionScaleUp) { - return THUMBNAIL_TRANSITION_EXIT_SCALE_UP; - } else { - return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; - } - } - } - /** * Creates an overlay with a background color and a thumbnail for the cross profile apps * animation. */ 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 = mService.mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.cross_profile_apps_thumbnail_size); - final Drawable drawable = mService.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(); + return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame); } Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) { - final Animation animation = - mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation(); - return prepareThumbnailAnimationWithDuration(animation, appRect.width(), - appRect.height(), 0, null); + return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect); } /** @@ -735,115 +668,14 @@ public class AppTransition implements Dump { * when a thumbnail is specified with the pending animation override. */ Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets, - HardwareBuffer thumbnailHeader, WindowContainer container, int uiMode, - int orientation) { - 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(container, mTmpRect); - final float fromX; - float fromY; - final float toX; - float toY; - final float pivotX; - final float pivotY; - if (shouldScaleDownThumbnailTransition(uiMode, 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; - } - final long duration = getAspectScaleDuration(); - final Interpolator interpolator = getAspectScaleInterpolator(); - if (mNextAppTransitionScaleUp) { - // Animation up from the thumbnail to the full screen - Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY); - scale.setInterpolator(interpolator); - scale.setDuration(duration); - Animation alpha = new AlphaAnimation(1f, 0f); - alpha.setInterpolator(mThumbnailFadeOutInterpolator); - alpha.setDuration(duration); - Animation translate = createCurvedMotion(fromX, toX, fromY, toY); - translate.setInterpolator(interpolator); - translate.setDuration(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(interpolator); - clipAnim.setDuration(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(interpolator); - scale.setDuration(duration); - Animation alpha = new AlphaAnimation(0f, 1f); - alpha.setInterpolator(mThumbnailFadeInInterpolator); - alpha.setDuration(duration); - Animation translate = createCurvedMotion(toX, fromX, toY, fromY); - translate.setInterpolator(interpolator); - translate.setDuration(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); + HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) { + AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get( + container.hashCode()); + return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect, + contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null, + mNextAppTransitionScaleUp); } private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) { @@ -1043,7 +875,7 @@ public class AppTransition implements Dump { + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { - a = mTransitionAnimation.createClipRevealAnimationLocked( + a = mTransitionAnimation.createClipRevealAnimationLockedCompat( transit, enter, frame, displayFrame, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); @@ -1052,7 +884,7 @@ public class AppTransition implements Dump { + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { - a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame, + a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, @@ -1064,8 +896,8 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container); - a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), frame, transit, thumbnailHeader, + a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter, + mNextAppTransitionScaleUp, frame, transit, thumbnailHeader, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, @@ -1080,9 +912,9 @@ public class AppTransition implements Dump { (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP); AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get( container.hashCode()); - a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), orientation, transit, frame, - insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null, + a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter, + mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets, + stableInsets, freeform, spec != null ? spec.rect : null, mDefaultNextAppTransitionAnimationSpec != null ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 42e2d2fc79d38..17419b7f14f79 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -16,7 +16,6 @@ package com.android.server.wm; - import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -124,6 +123,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** The final animation targets derived from participants after promotion. */ private ArraySet mTargets = null; + private TransitionInfo.AnimationOptions mOverrideOptions; + private @TransitionState int mState = STATE_COLLECTING; private boolean mReadyCalled = false; @@ -206,6 +207,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mChanges.get(wc).mExistenceChanged = true; } + /** + * Set animation options for collecting transition by ActivityRecord. + * @param options AnimationOptions captured from ActivityOptions + */ + void setOverrideAnimation(TransitionInfo.AnimationOptions options) { + if (mSyncId < 0) return; + mOverrideOptions = options; + } + /** * Call this when all known changes related to this transition have been applied. Until * all participants have finished drawing, the transition can still collect participants. @@ -327,6 +337,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.mAtm.mRootWindowContainer.getDisplayContent(displayId) .getPendingTransaction().merge(transaction); mSyncId = -1; + mOverrideOptions = null; return; } @@ -340,6 +351,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Resolve the animating targets from the participants mTargets = calculateTargets(mParticipants, mChanges); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges); + info.setAnimationOptions(mOverrideOptions); handleNonAppWindowsInTransition(displayId, mType, mFlags); @@ -375,6 +387,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe cleanUpOnFailure(); } mSyncId = -1; + mOverrideOptions = null; } /** diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index cc63c4922c326..47e40bf886fbd 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -28,6 +28,7 @@ import android.util.Slog; import android.view.WindowManager; import android.window.IRemoteTransition; import android.window.ITransitionPlayer; +import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import com.android.internal.protolog.ProtoLogGroup; @@ -240,6 +241,12 @@ class TransitionController { mCollectingTransition.collectExistenceChange(wc); } + /** @see Transition#setOverrideAnimation */ + void setOverrideAnimation(TransitionInfo.AnimationOptions options) { + if (mCollectingTransition == null) return; + mCollectingTransition.setOverrideAnimation(options); + } + /** @see Transition#setReady */ void setReady(boolean ready) { if (mCollectingTransition == null) return;