Merge "Play seekable animation for customize activity transition API.(2/N)" into udc-dev

This commit is contained in:
Wei Sheng Shih
2023-03-28 11:43:09 +00:00
committed by Android (Google) Code Review
4 changed files with 225 additions and 34 deletions

View File

@@ -16,6 +16,8 @@
package android.window;
import android.annotation.AnimRes;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -245,6 +247,9 @@ public final class BackNavigationInfo implements Parcelable {
public static final class CustomAnimationInfo implements Parcelable {
private final String mPackageName;
private int mWindowAnimations;
@AnimRes private int mCustomExitAnim;
@AnimRes private int mCustomEnterAnim;
@ColorInt private int mCustomBackground;
/**
* The package name of the windowAnimations.
@@ -261,6 +266,27 @@ public final class BackNavigationInfo implements Parcelable {
return mWindowAnimations;
}
/**
* The exit animation resource Id of customize activity transition.
*/
public int getCustomExitAnim() {
return mCustomExitAnim;
}
/**
* The entering animation resource Id of customize activity transition.
*/
public int getCustomEnterAnim() {
return mCustomEnterAnim;
}
/**
* The background color of customize activity transition.
*/
public int getCustomBackground() {
return mCustomBackground;
}
public CustomAnimationInfo(@NonNull String packageName) {
this.mPackageName = packageName;
}
@@ -274,11 +300,17 @@ public final class BackNavigationInfo implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mPackageName);
dest.writeInt(mWindowAnimations);
dest.writeInt(mCustomEnterAnim);
dest.writeInt(mCustomExitAnim);
dest.writeInt(mCustomBackground);
}
private CustomAnimationInfo(@NonNull Parcel in) {
mPackageName = in.readString8();
mWindowAnimations = in.readInt();
mCustomEnterAnim = in.readInt();
mCustomExitAnim = in.readInt();
mCustomBackground = in.readInt();
}
@Override
@@ -349,10 +381,25 @@ public final class BackNavigationInfo implements Parcelable {
* Set windowAnimations for customize animation.
*/
public Builder setWindowAnimations(String packageName, int windowAnimations) {
mCustomAnimationInfo = new CustomAnimationInfo(packageName);
if (mCustomAnimationInfo == null) {
mCustomAnimationInfo = new CustomAnimationInfo(packageName);
}
mCustomAnimationInfo.mWindowAnimations = windowAnimations;
return this;
}
/**
* Set resources ids for customize activity animation.
*/
public Builder setCustomAnimation(String packageName, @AnimRes int enterResId,
@AnimRes int exitResId, @ColorInt int backgroundColor) {
if (mCustomAnimationInfo == null) {
mCustomAnimationInfo = new CustomAnimationInfo(packageName);
}
mCustomAnimationInfo.mCustomExitAnim = exitResId;
mCustomAnimationInfo.mCustomEnterAnim = enterResId;
mCustomAnimationInfo.mCustomBackground = backgroundColor;
return this;
}
/**
* Builds and returns an instance of {@link BackNavigationInfo}

View File

@@ -25,7 +25,10 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.FloatProperty;
@@ -34,6 +37,7 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
@@ -43,6 +47,7 @@ import android.window.BackNavigationInfo;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
import com.android.internal.R;
import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
@@ -78,6 +83,7 @@ class CustomizeActivityAnimation {
final CustomAnimationLoader mCustomAnimationLoader;
private Animation mEnterAnimation;
private Animation mCloseAnimation;
private int mNextBackgroundColor;
final Transformation mTransformation = new Transformation();
private final Choreographer mChoreographer;
@@ -144,8 +150,9 @@ class CustomizeActivityAnimation {
// Draw background with task background color.
if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
mBackground.ensureBackground(
mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
mBackground.ensureBackground(mNextBackgroundColor == Color.TRANSPARENT
? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
: mNextBackgroundColor, mTransaction);
}
}
@@ -191,6 +198,7 @@ class CustomizeActivityAnimation {
mTransaction.apply();
mTransformation.clear();
mLatestProgress = 0;
mNextBackgroundColor = Color.TRANSPARENT;
if (mFinishCallback != null) {
try {
mFinishCallback.onAnimationFinished();
@@ -252,11 +260,11 @@ class CustomizeActivityAnimation {
* Load customize animation before animation start.
*/
boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
mCloseAnimation = mCustomAnimationLoader.load(
animationInfo, false /* enterAnimation */);
if (mCloseAnimation != null) {
mEnterAnimation = mCustomAnimationLoader.load(
animationInfo, true /* enterAnimation */);
final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
if (result != null) {
mCloseAnimation = result.mCloseAnimation;
mEnterAnimation = result.mEnterAnimation;
mNextBackgroundColor = result.mBackgroundColor;
return true;
}
return false;
@@ -318,35 +326,79 @@ class CustomizeActivityAnimation {
}
}
static final class AnimationLoadResult {
Animation mCloseAnimation;
Animation mEnterAnimation;
int mBackgroundColor;
}
/**
* Helper class to load custom animation.
*/
static class CustomAnimationLoader {
private final TransitionAnimation mTransitionAnimation;
final TransitionAnimation mTransitionAnimation;
CustomAnimationLoader(Context context) {
mTransitionAnimation = new TransitionAnimation(
context, false /* debug */, "CustomizeBackAnimation");
}
Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
/**
* Load both enter and exit animation for the close activity transition.
* Note that the result is only valid if the exit animation has set and loaded success.
* If the entering animation has not set(i.e. 0), here will load the default entering
* animation for it.
*
* @param animationInfo The information of customize animation, which can be set from
* {@link Activity#overrideActivityTransition} and/or
* {@link LayoutParams#windowAnimations}
*/
AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
if (animationInfo.getPackageName().isEmpty()) {
return null;
}
final Animation close = loadAnimation(animationInfo, false);
if (close == null) {
return null;
}
final Animation open = loadAnimation(animationInfo, true);
AnimationLoadResult result = new AnimationLoadResult();
result.mCloseAnimation = close;
result.mEnterAnimation = open;
result.mBackgroundColor = animationInfo.getCustomBackground();
return result;
}
/**
* Load enter or exit animation from CustomAnimationInfo
* @param animationInfo The information for customize animation.
* @param enterAnimation true when load for enter animation, false for exit animation.
* @return Loaded animation.
*/
@Nullable
Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
boolean enterAnimation) {
final String packageName = animationInfo.getPackageName();
if (packageName.isEmpty()) {
return null;
Animation a = null;
// Activity#overrideActivityTransition has higher priority than windowAnimations
// Try to get animation from Activity#overrideActivityTransition
if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
|| (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
a = mTransitionAnimation.loadAppTransitionAnimation(
animationInfo.getPackageName(),
enterAnimation ? animationInfo.getCustomEnterAnim()
: animationInfo.getCustomExitAnim());
} else if (animationInfo.getWindowAnimations() != 0) {
// try to get animation from LayoutParams#windowAnimations
a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
animationInfo.getWindowAnimations(), enterAnimation
? R.styleable.WindowAnimation_activityCloseEnterAnimation
: R.styleable.WindowAnimation_activityCloseExitAnimation,
false /* translucent */);
}
final int windowAnimations = animationInfo.getWindowAnimations();
if (windowAnimations == 0) {
return null;
}
final int attrs = enterAnimation
? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
: com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
attrs, false /* translucent */);
// Only allow to load default animation for opening target.
if (a == null && enterAnimation) {
a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
a = loadDefaultOpenAnimation();
}
if (a != null) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
@@ -355,5 +407,11 @@ class CustomizeActivityAnimation {
}
return a;
}
private Animation loadDefaultOpenAnimation() {
return mTransitionAnimation.loadDefaultAnimationAttr(
R.styleable.WindowAnimation_activityCloseEnterAnimation,
false /* translucent */);
}
}
}

View File

@@ -18,14 +18,20 @@ package com.android.wm.shell.back;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.WindowConfiguration;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -69,11 +75,7 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
mock(Choreographer.class));
spyOn(mCustomizeActivityAnimation);
spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
.load(any(), eq(false));
doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
.load(any(), eq(true));
spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
}
RemoteAnimationTarget createAnimationTarget(boolean open) {
@@ -87,6 +89,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
@Test
public void receiveFinishAfterInvoke() throws InterruptedException {
spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
.loadAnimation(any(), eq(false));
doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
.loadAnimation(any(), eq(true));
mCustomizeActivityAnimation.prepareNextAnimation(
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -112,6 +120,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
@Test
public void receiveFinishAfterCancel() throws InterruptedException {
spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
.loadAnimation(any(), eq(false));
doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
.loadAnimation(any(), eq(true));
mCustomizeActivityAnimation.prepareNextAnimation(
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -152,4 +166,67 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
verify(mCustomizeActivityAnimation).onGestureCommitted();
finishCalled.await(1, TimeUnit.SECONDS);
}
@Test
public void testLoadCustomAnimation() {
testLoadCustomAnimation(10, 20, 0);
}
@Test
public void testLoadCustomAnimationNoEnter() {
testLoadCustomAnimation(0, 10, 0);
}
@Test
public void testLoadWindowAnimations() {
testLoadCustomAnimation(0, 0, 30);
}
@Test
public void testCustomAnimationHigherThanWindowAnimations() {
testLoadCustomAnimation(10, 20, 30);
}
private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
final String testPackage = "TestPackage";
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
.setWindowAnimations(testPackage, windowAnimations);
final BackNavigationInfo.CustomAnimationInfo info = builder.build()
.getCustomAnimationInfo();
doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
.mTransitionAnimation)
.loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
.mTransitionAnimation)
.loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
.mTransitionAnimation)
.loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
.mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());
CustomizeActivityAnimation.AnimationLoadResult result =
mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);
if (exitResId != 0) {
if (enterResId == 0) {
verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
.loadDefaultAnimationAttr(anyInt(), anyBoolean());
} else {
assertEquals(result.mEnterAnimation, mMockOpenAnimation);
}
assertEquals(result.mBackgroundColor, Color.GREEN);
assertEquals(result.mCloseAnimation, mMockCloseAnimation);
verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
.loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
} else if (windowAnimations != 0) {
verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
assertEquals(result.mCloseAnimation, mMockCloseAnimation);
}
}
}

View File

@@ -262,14 +262,23 @@ class BackNavigationController {
if (!isOccluded || prevActivity.canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
final WindowContainer parent = currentActivity.getParent();
final boolean isCustomize = parent != null
final boolean canCustomize = parent != null
&& (parent.asTask() != null
|| (parent.asTaskFragment() != null
&& parent.canCustomizeAppTransition()))
&& isCustomizeExitAnimation(window);
if (isCustomize) {
infoBuilder.setWindowAnimations(
window.mAttrs.packageName, window.mAttrs.windowAnimations);
&& parent.canCustomizeAppTransition()));
if (canCustomize) {
if (isCustomizeExitAnimation(window)) {
infoBuilder.setWindowAnimations(
window.mAttrs.packageName, window.mAttrs.windowAnimations);
}
final ActivityRecord.CustomAppTransition customAppTransition =
currentActivity.getCustomAnimation(false/* open */);
if (customAppTransition != null) {
infoBuilder.setCustomAnimation(currentActivity.packageName,
customAppTransition.mExitAnim,
customAppTransition.mEnterAnim,
customAppTransition.mBackgroundColor);
}
}
removedWindowContainer = currentActivity;
prevTask = prevActivity.getTask();