summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Li <lihongyu@google.com> 2021-09-15 14:24:56 +0800
committer Chris Li <lihongyu@google.com> 2021-09-21 08:44:21 +0800
commit1e0e7a6848d88c25e6439cb6a045a3dd012daa77 (patch)
treed245a3a06c3fccec4e48617b22b6329ccb419b0e
parentd4a6bad8cd4bf5670eb77665acee4c228d432181 (diff)
Add animation for TASK_FRAGMENT_CHANGE transition
Bug: 196173550 Test: test with demo app Change-Id: I2f2b9450ceefa60020707a3dd7d168c90ed33f31
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationAdapter.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationRunner.java61
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentAnimationSpec.java199
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);
+ }
+ }
+}