Merge changes I1ea02fa9,I033a79dc,Iac219a8e into sc-v2-dev

* changes:
  Make AppTransition animations sharable (6/n)
  Make AppTransition animations sharable (5/n)
  Make AppTransition animations sharable (4/n)
This commit is contained in:
Tony Huang
2021-05-21 07:46:13 +00:00
committed by Android (Google) Code Review
9 changed files with 802 additions and 257 deletions

View File

@@ -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<Change> 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<AnimationOptions> CREATOR =
new Creator<AnimationOptions>() {
@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 "<unknown:" + mode + ">";
}
}
@Override
public String toString() {
return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+ " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
}
}
}

View File

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

View File

@@ -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<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);
}
@@ -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<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();
@@ -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<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 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());

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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<WindowContainer> 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;
}
/**

View File

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