Merge "Add animation for TASK_FRAGMENT_CHANGE transition" into sc-v2-dev
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package androidx.window.extensions.organizer;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.Choreographer;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.SurfaceControl;
|
||||
@@ -31,15 +32,28 @@ class TaskFragmentAnimationAdapter {
|
||||
private final Animation mAnimation;
|
||||
private final RemoteAnimationTarget mTarget;
|
||||
private final SurfaceControl mLeash;
|
||||
private final boolean mSizeChanged;
|
||||
private final Transformation mTransformation = new Transformation();
|
||||
private final float[] mMatrix = new float[9];
|
||||
private final float[] mVecs = new float[4];
|
||||
private final Rect mRect = new Rect();
|
||||
private boolean mIsFirstFrame = true;
|
||||
|
||||
TaskFragmentAnimationAdapter(@NonNull Animation animation,
|
||||
@NonNull RemoteAnimationTarget target) {
|
||||
this(animation, target, target.leash, false /* sizeChanged */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sizeChanged whether the surface size needs to be changed.
|
||||
*/
|
||||
TaskFragmentAnimationAdapter(@NonNull Animation animation,
|
||||
@NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
|
||||
boolean sizeChanged) {
|
||||
mAnimation = animation;
|
||||
mTarget = target;
|
||||
mLeash = target.leash;
|
||||
mLeash = leash;
|
||||
mSizeChanged = sizeChanged;
|
||||
}
|
||||
|
||||
/** Called on frame update. */
|
||||
@@ -56,6 +70,22 @@ class TaskFragmentAnimationAdapter {
|
||||
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
|
||||
t.setAlpha(mLeash, mTransformation.getAlpha());
|
||||
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
|
||||
|
||||
if (mSizeChanged) {
|
||||
// The following applies an inverse scale to the clip-rect so that it crops "after" the
|
||||
// scale instead of before.
|
||||
mVecs[1] = mVecs[2] = 0;
|
||||
mVecs[0] = mVecs[3] = 1;
|
||||
mTransformation.getMatrix().mapVectors(mVecs);
|
||||
mVecs[0] = 1.f / mVecs[0];
|
||||
mVecs[3] = 1.f / mVecs[3];
|
||||
final Rect clipRect = mTransformation.getClipRect();
|
||||
mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
|
||||
mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
|
||||
mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
|
||||
mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
|
||||
t.setWindowCrop(mLeash, mRect);
|
||||
}
|
||||
}
|
||||
|
||||
/** Called after animation finished. */
|
||||
|
||||
@@ -23,8 +23,6 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
@@ -39,10 +37,6 @@ import android.view.animation.Animation;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.policy.AttributeCache;
|
||||
import com.android.internal.policy.TransitionAnimation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -51,13 +45,10 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
|
||||
|
||||
private static final String TAG = "TaskFragAnimationRunner";
|
||||
private final Handler mHandler = new Handler(Looper.myLooper());
|
||||
private final TransitionAnimation mTransitionAnimation;
|
||||
private final TaskFragmentAnimationSpec mAnimationSpec;
|
||||
|
||||
TaskFragmentAnimationRunner() {
|
||||
final Context context = ActivityThread.currentActivityThread().getApplication();
|
||||
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, TAG);
|
||||
// Initialize the AttributeCache for the TransitionAnimation.
|
||||
AttributeCache.init(context);
|
||||
mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -175,11 +166,10 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
|
||||
|
||||
private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
|
||||
@NonNull RemoteAnimationTarget[] targets) {
|
||||
// TODO(b/196173550) We need to customize the animation to handle two open window as one.
|
||||
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
|
||||
for (RemoteAnimationTarget target : targets) {
|
||||
final Animation animation =
|
||||
loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */);
|
||||
mAnimationSpec.loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */);
|
||||
adapters.add(new TaskFragmentAnimationAdapter(animation, target));
|
||||
}
|
||||
return adapters;
|
||||
@@ -187,11 +177,10 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
|
||||
|
||||
private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
|
||||
@NonNull RemoteAnimationTarget[] targets) {
|
||||
// TODO(b/196173550) We need to customize the animation to handle two open window as one.
|
||||
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
|
||||
for (RemoteAnimationTarget target : targets) {
|
||||
final Animation animation =
|
||||
loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */);
|
||||
mAnimationSpec.loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */);
|
||||
adapters.add(new TaskFragmentAnimationAdapter(animation, target));
|
||||
}
|
||||
return adapters;
|
||||
@@ -199,29 +188,29 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
|
||||
|
||||
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
|
||||
@NonNull RemoteAnimationTarget[] targets) {
|
||||
// TODO(b/196173550) We need to hard code the change animation instead of using the default
|
||||
// open. See WindowChangeAnimationSpec.java as an example.
|
||||
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
|
||||
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
|
||||
for (RemoteAnimationTarget target : targets) {
|
||||
// The start leash is snapshot of the previous window. Hide it for now, will need to use
|
||||
// it for the fade in.
|
||||
if (target.startLeash != null) {
|
||||
t.hide(target.startLeash);
|
||||
if (target.startBounds != null) {
|
||||
final Animation[] animations =
|
||||
mAnimationSpec.createChangeBoundsChangeAnimations(target);
|
||||
adapters.add(new TaskFragmentAnimationAdapter(animations[0], target,
|
||||
target.startLeash, false /* sizeChanged */));
|
||||
adapters.add(new TaskFragmentAnimationAdapter(animations[1], target,
|
||||
target.leash, true /* sizeChanged */));
|
||||
continue;
|
||||
}
|
||||
|
||||
final Animation animation;
|
||||
if (target.hasAnimatingParent) {
|
||||
// No-op if it will be covered by the changing parent window.
|
||||
animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
|
||||
} else if (target.mode == MODE_CLOSING) {
|
||||
animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
|
||||
} else {
|
||||
animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
|
||||
}
|
||||
adapters.add(new TaskFragmentAnimationAdapter(animation, target));
|
||||
}
|
||||
t.apply();
|
||||
return createOpenAnimationAdapters(targets);
|
||||
}
|
||||
|
||||
private Animation loadOpenAnimation(boolean isEnter) {
|
||||
return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
|
||||
? R.styleable.WindowAnimation_activityOpenEnterAnimation
|
||||
: R.styleable.WindowAnimation_activityOpenExitAnimation);
|
||||
}
|
||||
|
||||
private Animation loadCloseAnimation(boolean isEnter) {
|
||||
return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
|
||||
? R.styleable.WindowAnimation_activityCloseEnterAnimation
|
||||
: R.styleable.WindowAnimation_activityCloseExitAnimation);
|
||||
return adapters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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 androidx.window.extensions.organizer;
|
||||
|
||||
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
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.ScaleAnimation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.policy.AttributeCache;
|
||||
import com.android.internal.policy.TransitionAnimation;
|
||||
|
||||
/** Animation spec for TaskFragment transition. */
|
||||
class TaskFragmentAnimationSpec {
|
||||
|
||||
private static final String TAG = "TaskFragAnimationSpec";
|
||||
private static final int CHANGE_ANIMATION_DURATION = 517;
|
||||
private static final int CHANGE_ANIMATION_FADE_DURATION = 82;
|
||||
private static final int CHANGE_ANIMATION_FADE_OFFSET = 67;
|
||||
|
||||
private final Context mContext;
|
||||
private final TransitionAnimation mTransitionAnimation;
|
||||
private final Interpolator mFastOutExtraSlowInInterpolator;
|
||||
private float mTransitionAnimationScaleSetting;
|
||||
|
||||
TaskFragmentAnimationSpec(@NonNull Handler handler) {
|
||||
mContext = ActivityThread.currentActivityThread().getApplication();
|
||||
mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
|
||||
// Initialize the AttributeCache for the TransitionAnimation.
|
||||
AttributeCache.init(mContext);
|
||||
mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
|
||||
mContext, android.R.interpolator.fast_out_extra_slow_in);
|
||||
|
||||
// The transition animation should be adjusted based on the developer option.
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
|
||||
Settings.Global.TRANSITION_ANIMATION_SCALE,
|
||||
mContext.getResources().getFloat(
|
||||
R.dimen.config_appTransitionAnimationDurationScaleDefault));
|
||||
resolver.registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
|
||||
new SettingsObserver(handler));
|
||||
}
|
||||
|
||||
/** For target that doesn't need to be animated. */
|
||||
static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
|
||||
// Noop but just keep the target showing/hiding.
|
||||
final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
|
||||
return new AlphaAnimation(alpha, alpha);
|
||||
}
|
||||
|
||||
/** Animation for target that is opening in a change transition. */
|
||||
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
|
||||
final Rect bounds = target.localBounds;
|
||||
// The target will be animated in from left or right depends on its position.
|
||||
final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
|
||||
|
||||
// The position should be 0-based as we will post translate in
|
||||
// TaskFragmentAnimationAdapter#onAnimationUpdate
|
||||
final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
|
||||
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
|
||||
animation.setDuration(CHANGE_ANIMATION_DURATION);
|
||||
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
|
||||
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
|
||||
return animation;
|
||||
}
|
||||
|
||||
/** Animation for target that is closing in a change transition. */
|
||||
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
|
||||
final Rect bounds = target.localBounds;
|
||||
// The target will be animated out to left or right depends on its position.
|
||||
final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
|
||||
|
||||
// The position should be 0-based as we will post translate in
|
||||
// TaskFragmentAnimationAdapter#onAnimationUpdate
|
||||
final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
|
||||
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
|
||||
animation.setDuration(CHANGE_ANIMATION_DURATION);
|
||||
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
|
||||
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
|
||||
return animation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation for target that is changing (bounds change) in a change transition.
|
||||
* @return the return array always has two elements. The first one is for the start leash, and
|
||||
* the second one is for the end leash.
|
||||
*/
|
||||
Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
|
||||
final Rect startBounds = target.startBounds;
|
||||
final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
|
||||
final Rect endBounds = target.localBounds;
|
||||
float scaleX = ((float) startBounds.width()) / endBounds.width();
|
||||
float scaleY = ((float) startBounds.height()) / endBounds.height();
|
||||
// Start leash is a child of the end leash. Reverse the scale so that the start leash won't
|
||||
// be scaled up with its parent.
|
||||
float startScaleX = 1.f / scaleX;
|
||||
float startScaleY = 1.f / scaleY;
|
||||
|
||||
// The start leash will be fade out.
|
||||
final AnimationSet startSet = new AnimationSet(true /* shareInterpolator */);
|
||||
startSet.setInterpolator(mFastOutExtraSlowInInterpolator);
|
||||
final Animation startAlpha = new AlphaAnimation(1f, 0f);
|
||||
startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
|
||||
startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
|
||||
startSet.addAnimation(startAlpha);
|
||||
final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
|
||||
startScaleY);
|
||||
startScale.setDuration(CHANGE_ANIMATION_DURATION);
|
||||
startSet.addAnimation(startScale);
|
||||
startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
|
||||
endBounds.height());
|
||||
startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
|
||||
|
||||
// The end leash will be moved into the end position while scaling.
|
||||
final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
|
||||
endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
|
||||
final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
|
||||
endScale.setDuration(CHANGE_ANIMATION_DURATION);
|
||||
endSet.addAnimation(endScale);
|
||||
// The position should be 0-based as we will post translate in
|
||||
// TaskFragmentAnimationAdapter#onAnimationUpdate
|
||||
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
|
||||
0, 0);
|
||||
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
|
||||
endSet.addAnimation(endTranslate);
|
||||
// The end leash is resizing, we should update the window crop based on the clip rect.
|
||||
final Rect startClip = new Rect(startBounds);
|
||||
final Rect endClip = new Rect(endBounds);
|
||||
startClip.offsetTo(0, 0);
|
||||
endClip.offsetTo(0, 0);
|
||||
final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
|
||||
clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
|
||||
endSet.addAnimation(clipAnim);
|
||||
endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
|
||||
parentBounds.height());
|
||||
endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
|
||||
|
||||
return new Animation[]{startSet, endSet};
|
||||
}
|
||||
|
||||
Animation loadOpenAnimation(boolean isEnter) {
|
||||
// TODO(b/196173550) We need to customize the animation to handle two open window as one.
|
||||
return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
|
||||
? R.styleable.WindowAnimation_activityOpenEnterAnimation
|
||||
: R.styleable.WindowAnimation_activityOpenExitAnimation);
|
||||
}
|
||||
|
||||
Animation loadCloseAnimation(boolean isEnter) {
|
||||
// TODO(b/196173550) We need to customize the animation to handle two open window as one.
|
||||
return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
|
||||
? R.styleable.WindowAnimation_activityCloseEnterAnimation
|
||||
: R.styleable.WindowAnimation_activityCloseExitAnimation);
|
||||
}
|
||||
|
||||
private class SettingsObserver extends ContentObserver {
|
||||
SettingsObserver(@NonNull Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
mTransitionAnimationScaleSetting = Settings.Global.getFloat(
|
||||
mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
|
||||
mTransitionAnimationScaleSetting);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user