diff options
| author | 2021-09-15 14:24:56 +0800 | |
|---|---|---|
| committer | 2021-09-21 08:44:21 +0800 | |
| commit | 1e0e7a6848d88c25e6439cb6a045a3dd012daa77 (patch) | |
| tree | d245a3a06c3fccec4e48617b22b6329ccb419b0e | |
| parent | d4a6bad8cd4bf5670eb77665acee4c228d432181 (diff) | |
Add animation for TASK_FRAGMENT_CHANGE transition
Bug: 196173550
Test: test with demo app
Change-Id: I2f2b9450ceefa60020707a3dd7d168c90ed33f31
3 files changed, 255 insertions, 37 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java index 2fa0045953e0..155c649d72a4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java @@ -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. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java index 7ac11185c302..da3d116455ef 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java @@ -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; } - } - 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); + 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)); + } + return adapters; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationSpec.java new file mode 100644 index 000000000000..ddcb27ddb512 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationSpec.java @@ -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); + } + } +} |