Merge "Play seekable animation for customize activity transition API." into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
06dfb81049
@@ -92,6 +92,8 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
@Nullable
|
||||
private final IOnBackInvokedCallback mOnBackInvokedCallback;
|
||||
private final boolean mPrepareRemoteAnimation;
|
||||
@Nullable
|
||||
private final CustomAnimationInfo mCustomAnimationInfo;
|
||||
|
||||
/**
|
||||
* Create a new {@link BackNavigationInfo} instance.
|
||||
@@ -104,11 +106,13 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
private BackNavigationInfo(@BackTargetType int type,
|
||||
@Nullable RemoteCallback onBackNavigationDone,
|
||||
@Nullable IOnBackInvokedCallback onBackInvokedCallback,
|
||||
boolean isPrepareRemoteAnimation) {
|
||||
boolean isPrepareRemoteAnimation,
|
||||
@Nullable CustomAnimationInfo customAnimationInfo) {
|
||||
mType = type;
|
||||
mOnBackNavigationDone = onBackNavigationDone;
|
||||
mOnBackInvokedCallback = onBackInvokedCallback;
|
||||
mPrepareRemoteAnimation = isPrepareRemoteAnimation;
|
||||
mCustomAnimationInfo = customAnimationInfo;
|
||||
}
|
||||
|
||||
private BackNavigationInfo(@NonNull Parcel in) {
|
||||
@@ -116,6 +120,7 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
|
||||
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
|
||||
mPrepareRemoteAnimation = in.readBoolean();
|
||||
mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -125,6 +130,7 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
dest.writeTypedObject(mOnBackNavigationDone, flags);
|
||||
dest.writeStrongInterface(mOnBackInvokedCallback);
|
||||
dest.writeBoolean(mPrepareRemoteAnimation);
|
||||
dest.writeTypedObject(mCustomAnimationInfo, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,6 +178,15 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customize animation info.
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public CustomAnimationInfo getCustomAnimationInfo() {
|
||||
return mCustomAnimationInfo;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public int describeContents() {
|
||||
@@ -197,6 +212,7 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
+ "mType=" + typeToString(mType) + " (" + mType + ")"
|
||||
+ ", mOnBackNavigationDone=" + mOnBackNavigationDone
|
||||
+ ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
|
||||
+ ", mCustomizeAnimationInfo=" + mCustomAnimationInfo
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@@ -222,6 +238,67 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
return String.valueOf(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Information for customize back animation.
|
||||
* @hide
|
||||
*/
|
||||
public static final class CustomAnimationInfo implements Parcelable {
|
||||
private final String mPackageName;
|
||||
private int mWindowAnimations;
|
||||
|
||||
/**
|
||||
* The package name of the windowAnimations.
|
||||
*/
|
||||
@NonNull
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The resource Id of window animations.
|
||||
*/
|
||||
public int getWindowAnimations() {
|
||||
return mWindowAnimations;
|
||||
}
|
||||
|
||||
public CustomAnimationInfo(@NonNull String packageName) {
|
||||
this.mPackageName = packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString8(mPackageName);
|
||||
dest.writeInt(mWindowAnimations);
|
||||
}
|
||||
|
||||
private CustomAnimationInfo(@NonNull Parcel in) {
|
||||
mPackageName = in.readString8();
|
||||
mWindowAnimations = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomAnimationInfo, package name= " + mPackageName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Creator<CustomAnimationInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public CustomAnimationInfo createFromParcel(Parcel in) {
|
||||
return new CustomAnimationInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAnimationInfo[] newArray(int size) {
|
||||
return new CustomAnimationInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -233,6 +310,7 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
@Nullable
|
||||
private IOnBackInvokedCallback mOnBackInvokedCallback = null;
|
||||
private boolean mPrepareRemoteAnimation;
|
||||
private CustomAnimationInfo mCustomAnimationInfo;
|
||||
|
||||
/**
|
||||
* @see BackNavigationInfo#getType()
|
||||
@@ -267,13 +345,23 @@ public final class BackNavigationInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set windowAnimations for customize animation.
|
||||
*/
|
||||
public Builder setWindowAnimations(String packageName, int windowAnimations) {
|
||||
mCustomAnimationInfo = new CustomAnimationInfo(packageName);
|
||||
mCustomAnimationInfo.mWindowAnimations = windowAnimations;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns an instance of {@link BackNavigationInfo}
|
||||
*/
|
||||
public BackNavigationInfo build() {
|
||||
return new BackNavigationInfo(mType, mOnBackNavigationDone,
|
||||
mOnBackInvokedCallback,
|
||||
mPrepareRemoteAnimation);
|
||||
mPrepareRemoteAnimation,
|
||||
mCustomAnimationInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +37,13 @@ oneway interface IBackAnimationRunner {
|
||||
|
||||
/**
|
||||
* Called when the system is ready for the handler to start animating all the visible tasks.
|
||||
* @param type The back navigation type.
|
||||
* @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
|
||||
windows to animate,
|
||||
* @param wallpapers The list of wallpapers to animate.
|
||||
* @param nonApps The list of non-app windows such as Bubbles to animate.
|
||||
* @param finishedCallback The callback to invoke when the animation is finished.
|
||||
*/
|
||||
void onAnimationStart(in int type,
|
||||
void onAnimationStart(
|
||||
in RemoteAnimationTarget[] apps,
|
||||
in RemoteAnimationTarget[] wallpapers,
|
||||
in RemoteAnimationTarget[] nonApps,
|
||||
|
||||
@@ -265,6 +265,34 @@ public class TransitionAnimation {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Get animation resId by attribute Id from specific LayoutParams */
|
||||
public int getAnimationResId(LayoutParams lp, int animAttr, int transit) {
|
||||
int resId = Resources.ID_NULL;
|
||||
if (animAttr >= 0) {
|
||||
AttributeCache.Entry ent = getCachedAnimations(lp);
|
||||
if (ent != null) {
|
||||
resId = ent.array.getResourceId(animAttr, 0);
|
||||
}
|
||||
}
|
||||
resId = updateToTranslucentAnimIfNeeded(resId, transit);
|
||||
return resId;
|
||||
}
|
||||
|
||||
/** Get default animation resId */
|
||||
public int getDefaultAnimationResId(int animAttr, int transit) {
|
||||
int resId = Resources.ID_NULL;
|
||||
if (animAttr >= 0) {
|
||||
AttributeCache.Entry ent = getCachedAnimations(DEFAULT_PACKAGE,
|
||||
mDefaultWindowAnimationStyleResId);
|
||||
if (ent != null) {
|
||||
resId = ent.array.getResourceId(animAttr, 0);
|
||||
}
|
||||
}
|
||||
resId = updateToTranslucentAnimIfNeeded(resId, transit);
|
||||
return resId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load animation by attribute Id from a specific AnimationStyle resource.
|
||||
*
|
||||
|
||||
@@ -120,6 +120,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
@Nullable
|
||||
private IOnBackInvokedCallback mActiveCallback;
|
||||
|
||||
private CrossActivityAnimation mDefaultActivityAnimation;
|
||||
private CustomizeActivityAnimation mCustomizeActivityAnimation;
|
||||
|
||||
@VisibleForTesting
|
||||
final RemoteCallback mNavigationObserver = new RemoteCallback(
|
||||
new RemoteCallback.OnResultListener() {
|
||||
@@ -194,10 +197,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
new CrossTaskBackAnimation(mContext, mAnimationBackground);
|
||||
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
|
||||
crossTaskAnimation.mBackAnimationRunner);
|
||||
final CrossActivityAnimation crossActivityAnimation =
|
||||
mDefaultActivityAnimation =
|
||||
new CrossActivityAnimation(mContext, mAnimationBackground);
|
||||
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
|
||||
crossActivityAnimation.mBackAnimationRunner);
|
||||
mDefaultActivityAnimation.mBackAnimationRunner);
|
||||
mCustomizeActivityAnimation =
|
||||
new CustomizeActivityAnimation(mContext, mAnimationBackground);
|
||||
// TODO (236760237): register dialog close animation when it's completed.
|
||||
}
|
||||
|
||||
@@ -368,7 +373,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
|
||||
if (shouldDispatchToAnimator) {
|
||||
if (mAnimationDefinition.contains(backType)) {
|
||||
mActiveCallback = mAnimationDefinition.get(backType).getCallback();
|
||||
mAnimationDefinition.get(backType).startGesture();
|
||||
} else {
|
||||
mActiveCallback = null;
|
||||
@@ -542,13 +546,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
}
|
||||
|
||||
final int backType = mBackNavigationInfo.getType();
|
||||
final BackAnimationRunner runner = mAnimationDefinition.get(backType);
|
||||
// Simply trigger and finish back navigation when no animator defined.
|
||||
if (!shouldDispatchToAnimator() || mActiveCallback == null) {
|
||||
if (!shouldDispatchToAnimator() || runner == null) {
|
||||
invokeOrCancelBack();
|
||||
return;
|
||||
}
|
||||
|
||||
final BackAnimationRunner runner = mAnimationDefinition.get(backType);
|
||||
if (runner.isWaitingAnimation()) {
|
||||
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
|
||||
return;
|
||||
@@ -607,6 +610,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
mShouldStartOnNextMoveEvent = false;
|
||||
mTouchTracker.reset();
|
||||
mActiveCallback = null;
|
||||
// reset to default
|
||||
if (mDefaultActivityAnimation != null
|
||||
&& mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
|
||||
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
|
||||
mDefaultActivityAnimation.mBackAnimationRunner);
|
||||
}
|
||||
if (mBackNavigationInfo != null) {
|
||||
mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
|
||||
mBackNavigationInfo = null;
|
||||
@@ -614,14 +623,35 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
mTriggerBack = false;
|
||||
}
|
||||
|
||||
private BackAnimationRunner getAnimationRunnerAndInit() {
|
||||
int type = mBackNavigationInfo.getType();
|
||||
// Initiate customized cross-activity animation, or fall back to cross activity animation
|
||||
if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
|
||||
final BackNavigationInfo.CustomAnimationInfo animationInfo =
|
||||
mBackNavigationInfo.getCustomAnimationInfo();
|
||||
if (animationInfo != null && mCustomizeActivityAnimation != null
|
||||
&& mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
|
||||
mAnimationDefinition.get(type).resetWaitingAnimation();
|
||||
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner);
|
||||
}
|
||||
}
|
||||
return mAnimationDefinition.get(type);
|
||||
}
|
||||
|
||||
private void createAdapter() {
|
||||
IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
|
||||
@Override
|
||||
public void onAnimationStart(int type, RemoteAnimationTarget[] apps,
|
||||
public void onAnimationStart(RemoteAnimationTarget[] apps,
|
||||
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
|
||||
IBackAnimationFinishedCallback finishedCallback) {
|
||||
mShellExecutor.execute(() -> {
|
||||
final BackAnimationRunner runner = mAnimationDefinition.get(type);
|
||||
if (mBackNavigationInfo == null) {
|
||||
Log.e(TAG, "Lack of navigation info to start animation.");
|
||||
return;
|
||||
}
|
||||
final int type = mBackNavigationInfo.getType();
|
||||
final BackAnimationRunner runner = getAnimationRunnerAndInit();
|
||||
if (runner == null) {
|
||||
Log.e(TAG, "Animation didn't be defined for type "
|
||||
+ BackNavigationInfo.typeToString(type));
|
||||
@@ -634,6 +664,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
}
|
||||
return;
|
||||
}
|
||||
mActiveCallback = runner.getCallback();
|
||||
mBackAnimationFinishedCallback = finishedCallback;
|
||||
|
||||
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
|
||||
@@ -645,11 +676,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|
||||
mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
|
||||
}
|
||||
|
||||
// Dispatch the first progress after animation start for smoothing the initial
|
||||
// animation, instead of waiting for next onMove.
|
||||
final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
|
||||
dispatchOnBackProgressed(mActiveCallback, backFinish);
|
||||
if (!mBackGestureStarted) {
|
||||
// if the down -> up gesture happened before animation start, we have to
|
||||
// trigger the uninterruptible transition to finish the back animation.
|
||||
final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
|
||||
dispatchOnBackProgressed(mActiveCallback, backFinish);
|
||||
startPostCommitAnimation();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -99,4 +99,8 @@ class BackAnimationRunner {
|
||||
boolean isAnimationCancelled() {
|
||||
return mAnimationCancelled;
|
||||
}
|
||||
|
||||
void resetWaitingAnimation() {
|
||||
mWaitingAnimation = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.back;
|
||||
|
||||
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
||||
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.RemoteException;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.Choreographer;
|
||||
import android.view.IRemoteAnimationFinishedCallback;
|
||||
import android.view.IRemoteAnimationRunner;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Transformation;
|
||||
import android.window.BackEvent;
|
||||
import android.window.BackMotionEvent;
|
||||
import android.window.BackNavigationInfo;
|
||||
import android.window.BackProgressAnimator;
|
||||
import android.window.IOnBackInvokedCallback;
|
||||
|
||||
import com.android.internal.dynamicanimation.animation.SpringAnimation;
|
||||
import com.android.internal.dynamicanimation.animation.SpringForce;
|
||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
||||
import com.android.internal.policy.TransitionAnimation;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.common.annotations.ShellMainThread;
|
||||
|
||||
/**
|
||||
* Class that handle customized close activity transition animation.
|
||||
*/
|
||||
@ShellMainThread
|
||||
class CustomizeActivityAnimation {
|
||||
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
|
||||
final BackAnimationRunner mBackAnimationRunner;
|
||||
private final float mCornerRadius;
|
||||
private final SurfaceControl.Transaction mTransaction;
|
||||
private final BackAnimationBackground mBackground;
|
||||
private RemoteAnimationTarget mEnteringTarget;
|
||||
private RemoteAnimationTarget mClosingTarget;
|
||||
private IRemoteAnimationFinishedCallback mFinishCallback;
|
||||
/** Duration of post animation after gesture committed. */
|
||||
private static final int POST_ANIMATION_DURATION = 250;
|
||||
|
||||
private static final int SCALE_FACTOR = 1000;
|
||||
private final SpringAnimation mProgressSpring;
|
||||
private float mLatestProgress = 0.0f;
|
||||
|
||||
private static final float TARGET_COMMIT_PROGRESS = 0.5f;
|
||||
|
||||
private final float[] mTmpFloat9 = new float[9];
|
||||
private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
|
||||
|
||||
final CustomAnimationLoader mCustomAnimationLoader;
|
||||
private Animation mEnterAnimation;
|
||||
private Animation mCloseAnimation;
|
||||
final Transformation mTransformation = new Transformation();
|
||||
|
||||
private final Choreographer mChoreographer;
|
||||
|
||||
CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
|
||||
this(context, background, new SurfaceControl.Transaction(), null);
|
||||
}
|
||||
|
||||
CustomizeActivityAnimation(Context context, BackAnimationBackground background,
|
||||
SurfaceControl.Transaction transaction, Choreographer choreographer) {
|
||||
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
|
||||
mBackground = background;
|
||||
mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
|
||||
mCustomAnimationLoader = new CustomAnimationLoader(context);
|
||||
|
||||
mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
|
||||
mProgressSpring.setSpring(new SpringForce()
|
||||
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
|
||||
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
|
||||
mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
|
||||
mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
|
||||
}
|
||||
|
||||
private float getLatestProgress() {
|
||||
return mLatestProgress * SCALE_FACTOR;
|
||||
}
|
||||
private void setLatestProgress(float value) {
|
||||
mLatestProgress = value / SCALE_FACTOR;
|
||||
applyTransformTransaction(mLatestProgress);
|
||||
}
|
||||
|
||||
private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
|
||||
new FloatProperty<>("enter") {
|
||||
@Override
|
||||
public void setValue(CustomizeActivityAnimation anim, float value) {
|
||||
anim.setLatestProgress(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(CustomizeActivityAnimation object) {
|
||||
return object.getLatestProgress();
|
||||
}
|
||||
};
|
||||
|
||||
// The target will lose focus when alpha == 0, so keep a minimum value for it.
|
||||
private static float keepMinimumAlpha(float transAlpha) {
|
||||
return Math.max(transAlpha, 0.005f);
|
||||
}
|
||||
|
||||
private static void initializeAnimation(Animation animation, Rect bounds) {
|
||||
final int width = bounds.width();
|
||||
final int height = bounds.height();
|
||||
animation.initialize(width, height, width, height);
|
||||
}
|
||||
|
||||
private void startBackAnimation() {
|
||||
if (mEnteringTarget == null || mClosingTarget == null
|
||||
|| mCloseAnimation == null || mEnterAnimation == null) {
|
||||
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
|
||||
return;
|
||||
}
|
||||
initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
|
||||
initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
|
||||
|
||||
// Draw background with task background color.
|
||||
if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
|
||||
mBackground.ensureBackground(
|
||||
mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTransformTransaction(float progress) {
|
||||
if (mClosingTarget == null || mEnteringTarget == null) {
|
||||
return;
|
||||
}
|
||||
applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
|
||||
applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
|
||||
mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
|
||||
mTransaction.apply();
|
||||
}
|
||||
|
||||
private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
|
||||
mTransformation.clear();
|
||||
animation.getTransformationAt(progress, mTransformation);
|
||||
mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
|
||||
mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
|
||||
mTransaction.setCornerRadius(leash, mCornerRadius);
|
||||
}
|
||||
|
||||
void finishAnimation() {
|
||||
if (mCloseAnimation != null) {
|
||||
mCloseAnimation.reset();
|
||||
mCloseAnimation = null;
|
||||
}
|
||||
if (mEnterAnimation != null) {
|
||||
mEnterAnimation.reset();
|
||||
mEnterAnimation = null;
|
||||
}
|
||||
if (mEnteringTarget != null) {
|
||||
mEnteringTarget.leash.release();
|
||||
mEnteringTarget = null;
|
||||
}
|
||||
if (mClosingTarget != null) {
|
||||
mClosingTarget.leash.release();
|
||||
mClosingTarget = null;
|
||||
}
|
||||
if (mBackground != null) {
|
||||
mBackground.removeBackground(mTransaction);
|
||||
}
|
||||
mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
|
||||
mTransaction.apply();
|
||||
mTransformation.clear();
|
||||
mLatestProgress = 0;
|
||||
if (mFinishCallback != null) {
|
||||
try {
|
||||
mFinishCallback.onAnimationFinished();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mFinishCallback = null;
|
||||
}
|
||||
mProgressSpring.animateToFinalPosition(0);
|
||||
mProgressSpring.skipToEnd();
|
||||
}
|
||||
|
||||
void onGestureProgress(@NonNull BackEvent backEvent) {
|
||||
if (mEnteringTarget == null || mClosingTarget == null
|
||||
|| mCloseAnimation == null || mEnterAnimation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final float progress = backEvent.getProgress();
|
||||
|
||||
float springProgress = (progress > 0.1f
|
||||
? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
|
||||
: mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
|
||||
|
||||
mProgressSpring.animateToFinalPosition(springProgress);
|
||||
}
|
||||
|
||||
static float mapLinear(float x, float a1, float a2, float b1, float b2) {
|
||||
return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
|
||||
}
|
||||
|
||||
void onGestureCommitted() {
|
||||
if (mEnteringTarget == null || mClosingTarget == null
|
||||
|| mCloseAnimation == null || mEnterAnimation == null) {
|
||||
finishAnimation();
|
||||
return;
|
||||
}
|
||||
mProgressSpring.cancel();
|
||||
|
||||
// Enter phase 2 of the animation
|
||||
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
|
||||
.setDuration(POST_ANIMATION_DURATION);
|
||||
valueAnimator.setInterpolator(mDecelerateInterpolator);
|
||||
valueAnimator.addUpdateListener(animation -> {
|
||||
float progress = (float) animation.getAnimatedValue();
|
||||
applyTransformTransaction(progress);
|
||||
});
|
||||
|
||||
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finishAnimation();
|
||||
}
|
||||
});
|
||||
valueAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 */);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private final class Callback extends IOnBackInvokedCallback.Default {
|
||||
@Override
|
||||
public void onBackStarted(BackMotionEvent backEvent) {
|
||||
mProgressAnimator.onBackStarted(backEvent,
|
||||
CustomizeActivityAnimation.this::onGestureProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
|
||||
mProgressAnimator.onBackProgressed(backEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackCancelled() {
|
||||
mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
mProgressAnimator.reset();
|
||||
onGestureCommitted();
|
||||
}
|
||||
}
|
||||
|
||||
private final class Runner extends IRemoteAnimationRunner.Default {
|
||||
@Override
|
||||
public void onAnimationStart(
|
||||
int transit,
|
||||
RemoteAnimationTarget[] apps,
|
||||
RemoteAnimationTarget[] wallpapers,
|
||||
RemoteAnimationTarget[] nonApps,
|
||||
IRemoteAnimationFinishedCallback finishedCallback) {
|
||||
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
|
||||
for (RemoteAnimationTarget a : apps) {
|
||||
if (a.mode == MODE_CLOSING) {
|
||||
mClosingTarget = a;
|
||||
}
|
||||
if (a.mode == MODE_OPENING) {
|
||||
mEnteringTarget = a;
|
||||
}
|
||||
}
|
||||
if (mCloseAnimation == null || mEnterAnimation == null) {
|
||||
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
||||
"No animation loaded, should choose cross-activity animation?");
|
||||
}
|
||||
|
||||
startBackAnimation();
|
||||
mFinishCallback = finishedCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancelled(boolean isKeyguardOccluded) {
|
||||
finishAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to load custom animation.
|
||||
*/
|
||||
static class CustomAnimationLoader {
|
||||
private final TransitionAnimation mTransitionAnimation;
|
||||
|
||||
CustomAnimationLoader(Context context) {
|
||||
mTransitionAnimation = new TransitionAnimation(
|
||||
context, false /* debug */, "CustomizeBackAnimation");
|
||||
}
|
||||
|
||||
Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
|
||||
boolean enterAnimation) {
|
||||
final String packageName = animationInfo.getPackageName();
|
||||
if (packageName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
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 */);
|
||||
}
|
||||
if (a != null) {
|
||||
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
|
||||
} else {
|
||||
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,7 +422,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
|
||||
RemoteAnimationTarget animationTarget = createAnimationTarget();
|
||||
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
|
||||
if (mController.mBackAnimationAdapter != null) {
|
||||
mController.mBackAnimationAdapter.getRunner().onAnimationStart(type,
|
||||
mController.mBackAnimationAdapter.getRunner().onAnimationStart(
|
||||
targets, null, null, mBackAnimationFinishedCallback);
|
||||
mShellExecutor.flushAll();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.back;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.WindowConfiguration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.RemoteException;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.Choreographer;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.animation.Animation;
|
||||
import android.window.BackNavigationInfo;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class CustomizeActivityAnimationTest extends ShellTestCase {
|
||||
private static final int BOUND_SIZE = 100;
|
||||
@Mock
|
||||
private BackAnimationBackground mBackAnimationBackground;
|
||||
@Mock
|
||||
private Animation mMockCloseAnimation;
|
||||
@Mock
|
||||
private Animation mMockOpenAnimation;
|
||||
|
||||
private CustomizeActivityAnimation mCustomizeActivityAnimation;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
|
||||
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));
|
||||
}
|
||||
|
||||
RemoteAnimationTarget createAnimationTarget(boolean open) {
|
||||
SurfaceControl topWindowLeash = new SurfaceControl();
|
||||
return new RemoteAnimationTarget(1,
|
||||
open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
|
||||
topWindowLeash, false, new Rect(), new Rect(), -1,
|
||||
new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
|
||||
new WindowConfiguration(), true, null, null, null, false, -1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveFinishAfterInvoke() throws InterruptedException {
|
||||
mCustomizeActivityAnimation.prepareNextAnimation(
|
||||
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
|
||||
final RemoteAnimationTarget close = createAnimationTarget(false);
|
||||
final RemoteAnimationTarget open = createAnimationTarget(true);
|
||||
// start animation with remote animation targets
|
||||
final CountDownLatch finishCalled = new CountDownLatch(1);
|
||||
final Runnable finishCallback = finishCalled::countDown;
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
|
||||
new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
|
||||
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
|
||||
eq(BOUND_SIZE), eq(BOUND_SIZE));
|
||||
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
|
||||
eq(BOUND_SIZE), eq(BOUND_SIZE));
|
||||
|
||||
try {
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
|
||||
} catch (RemoteException r) {
|
||||
fail("onBackInvoked throw remote exception");
|
||||
}
|
||||
verify(mCustomizeActivityAnimation).onGestureCommitted();
|
||||
finishCalled.await(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveFinishAfterCancel() throws InterruptedException {
|
||||
mCustomizeActivityAnimation.prepareNextAnimation(
|
||||
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
|
||||
final RemoteAnimationTarget close = createAnimationTarget(false);
|
||||
final RemoteAnimationTarget open = createAnimationTarget(true);
|
||||
// start animation with remote animation targets
|
||||
final CountDownLatch finishCalled = new CountDownLatch(1);
|
||||
final Runnable finishCallback = finishCalled::countDown;
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
|
||||
new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
|
||||
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
|
||||
eq(BOUND_SIZE), eq(BOUND_SIZE));
|
||||
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
|
||||
eq(BOUND_SIZE), eq(BOUND_SIZE));
|
||||
|
||||
try {
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
|
||||
} catch (RemoteException r) {
|
||||
fail("onBackCancelled throw remote exception");
|
||||
}
|
||||
finishCalled.await(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
|
||||
mCustomizeActivityAnimation.prepareNextAnimation(
|
||||
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
|
||||
// start animation without any remote animation targets
|
||||
final CountDownLatch finishCalled = new CountDownLatch(1);
|
||||
final Runnable finishCallback = finishCalled::countDown;
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
|
||||
new RemoteAnimationTarget[]{}, null, null, finishCallback);
|
||||
|
||||
try {
|
||||
mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
|
||||
} catch (RemoteException r) {
|
||||
fail("onBackInvoked throw remote exception");
|
||||
}
|
||||
verify(mCustomizeActivityAnimation).onGestureCommitted();
|
||||
finishCalled.await(1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
||||
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
|
||||
import static android.view.WindowManager.TRANSIT_CLOSE;
|
||||
import static android.view.WindowManager.TRANSIT_OLD_NONE;
|
||||
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
|
||||
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
|
||||
@@ -31,6 +32,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.res.ResourceId;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
@@ -51,12 +53,14 @@ import android.window.OnBackInvokedCallbackInfo;
|
||||
import android.window.TaskSnapshot;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.policy.TransitionAnimation;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.wm.utils.InsetUtils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -80,6 +84,8 @@ class BackNavigationController {
|
||||
// re-parenting leashes and set launch behind, etc. Will be handled when transition finished.
|
||||
private AnimationHandler.ScheduleAnimationBuilder mPendingAnimationBuilder;
|
||||
|
||||
private static int sDefaultAnimationResId;
|
||||
|
||||
/**
|
||||
* true if the back predictability feature is enabled
|
||||
*/
|
||||
@@ -255,9 +261,19 @@ class BackNavigationController {
|
||||
} else if (prevActivity != null) {
|
||||
if (!isOccluded || prevActivity.canShowWhenLocked()) {
|
||||
// We have another Activity in the same currentTask to go to
|
||||
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
|
||||
final WindowContainer parent = currentActivity.getParent();
|
||||
final boolean isCustomize = parent != null
|
||||
&& (parent.asTask() != null
|
||||
|| (parent.asTaskFragment() != null
|
||||
&& parent.canCustomizeAppTransition()))
|
||||
&& isCustomizeExitAnimation(window);
|
||||
if (isCustomize) {
|
||||
infoBuilder.setWindowAnimations(
|
||||
window.mAttrs.packageName, window.mAttrs.windowAnimations);
|
||||
}
|
||||
removedWindowContainer = currentActivity;
|
||||
prevTask = prevActivity.getTask();
|
||||
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
|
||||
} else {
|
||||
backType = BackNavigationInfo.TYPE_CALLBACK;
|
||||
}
|
||||
@@ -370,6 +386,37 @@ class BackNavigationController {
|
||||
return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
|
||||
}
|
||||
|
||||
/**
|
||||
* There are two ways to customize activity exit animation, one is to provide the
|
||||
* windowAnimationStyle by Activity#setTheme, another one is to set resId by
|
||||
* Window#setWindowAnimations.
|
||||
* Not all run-time customization methods can be checked from here, such as
|
||||
* overridePendingTransition, which the animation resource will be set just before the
|
||||
* transition is about to happen.
|
||||
*/
|
||||
private static boolean isCustomizeExitAnimation(WindowState window) {
|
||||
// The default animation ResId is loaded from system package, so the result must match.
|
||||
if (Objects.equals(window.mAttrs.packageName, "android")) {
|
||||
return false;
|
||||
}
|
||||
if (window.mAttrs.windowAnimations != 0) {
|
||||
final TransitionAnimation transitionAnimation = window.getDisplayContent()
|
||||
.mAppTransition.mTransitionAnimation;
|
||||
final int attr = com.android.internal.R.styleable
|
||||
.WindowAnimation_activityCloseExitAnimation;
|
||||
final int appResId = transitionAnimation.getAnimationResId(
|
||||
window.mAttrs, attr, TRANSIT_OLD_NONE);
|
||||
if (ResourceId.isValid(appResId)) {
|
||||
if (sDefaultAnimationResId == 0) {
|
||||
sDefaultAnimationResId = transitionAnimation.getDefaultAnimationResId(attr,
|
||||
TRANSIT_OLD_NONE);
|
||||
}
|
||||
return sDefaultAnimationResId != appResId;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// For legacy transition.
|
||||
/**
|
||||
* Once we find the transition targets match back animation targets, remove the target from
|
||||
@@ -997,7 +1044,7 @@ class BackNavigationController {
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
mBackAnimationAdapter.getRunner().onAnimationStart(mType,
|
||||
mBackAnimationAdapter.getRunner().onAnimationStart(
|
||||
targets, null, null, callback);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -26,6 +26,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
|
||||
import static android.window.BackNavigationInfo.typeToString;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
@@ -148,6 +149,28 @@ public class BackNavigationControllerTests extends WindowTestsBase {
|
||||
.isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void backTypeCrossActivityWithCustomizeExitAnimation() {
|
||||
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
|
||||
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
|
||||
testCase.windowFront.mAttrs.windowAnimations = 0x10;
|
||||
spyOn(mDisplayContent.mAppTransition.mTransitionAnimation);
|
||||
doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation)
|
||||
.getAnimationResId(any(), anyInt(), anyInt());
|
||||
doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation)
|
||||
.getDefaultAnimationResId(anyInt(), anyInt());
|
||||
|
||||
BackNavigationInfo backNavigationInfo = startBackNavigation();
|
||||
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
|
||||
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
|
||||
assertThat(backNavigationInfo.getCustomAnimationInfo().getWindowAnimations())
|
||||
.isEqualTo(testCase.windowFront.mAttrs.windowAnimations);
|
||||
assertThat(typeToString(backNavigationInfo.getType()))
|
||||
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
|
||||
// verify if back animation would start.
|
||||
assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void backTypeCrossActivityWhenBackToPreviousActivity() {
|
||||
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
|
||||
@@ -158,6 +181,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
|
||||
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
|
||||
assertThat(typeToString(backNavigationInfo.getType()))
|
||||
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
|
||||
// verify if back animation would start.
|
||||
assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
|
||||
|
||||
// reset drawing status
|
||||
testCase.recordFront.forAllWindows(w -> {
|
||||
@@ -510,6 +535,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
|
||||
testCase.task = task;
|
||||
testCase.recordBack = record1;
|
||||
testCase.recordFront = record2;
|
||||
testCase.windowBack = window1;
|
||||
testCase.windowFront = window2;
|
||||
return testCase;
|
||||
}
|
||||
|
||||
@@ -525,6 +552,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
|
||||
private class CrossActivityTestCase {
|
||||
public Task task;
|
||||
public ActivityRecord recordBack;
|
||||
public WindowState windowBack;
|
||||
public ActivityRecord recordFront;
|
||||
public WindowState windowFront;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user