Fix clip reveal animation in docked window case
- Move ClipRectTB/LRAnimation to wm package, because that's the only
place we use it.
- Extend ClipRectTBAnimation to combine it with translation animation
so the clipping gets applied after the translation.
- Fix clip reveal transitions when a window is docked.
- Make the docked divider minimizing animations synchronized with clip
reveal animation.
Bug: 27154882
Bug: 22174716
Change-Id: If5c94c777f3b51c6f53f6f34cc261bf3439cfc88
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ad12c66..ba9e640 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -46,7 +46,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Debug;
@@ -64,8 +63,6 @@
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.ClipRectAnimation;
-import android.view.animation.ClipRectLRAnimation;
-import android.view.animation.ClipRectTBAnimation;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
@@ -74,10 +71,11 @@
import com.android.internal.util.DumpUtils.Dump;
import com.android.server.AttributeCache;
import com.android.server.wm.WindowManagerService.H;
+import com.android.server.wm.animation.ClipRectLRAnimation;
+import com.android.server.wm.animation.ClipRectTBAnimation;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -136,6 +134,16 @@
private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+
+ /** Interpolator to be used for animations that respond directly to a touch */
+ static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+ /**
+ * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
+ * involved, to make it more understandable.
+ */
+ private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
private static final int THUMBNAIL_APP_TRANSITION_ALPHA_DURATION = 336;
private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
@@ -200,13 +208,10 @@
private final Interpolator mFastOutLinearInInterpolator;
private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
- /** Interpolator to be used for animations that respond directly to a touch */
- private final Interpolator mTouchResponseInterpolator =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
private final int mClipRevealTranslationY;
private int mCurrentUserId = 0;
+ private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
@@ -636,50 +641,101 @@
bitmap, new Rect(left, top, left + width, top + height));
}
- private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) {
+ long getLastClipRevealTransitionDuration() {
+ return mLastClipRevealTransitionDuration;
+ }
+
+ /**
+ * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
+ * the start rect is outside of the target rect, and there is a lot of movement going on.
+ *
+ * @param cutOff whether the start rect was not fully contained by the end rect
+ * @param translationX the total translation the surface moves in x direction
+ * @param translationY the total translation the surfaces moves in y direction
+ * @param displayFrame our display frame
+ *
+ * @return the duration of the clip reveal animation, in milliseconds
+ */
+ private long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
+ float translationY, Rect displayFrame) {
+ if (!cutOff) {
+ return DEFAULT_APP_TRANSITION_DURATION;
+ }
+ final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
+ Math.abs(translationY) / displayFrame.height());
+ return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction *
+ (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
+ }
+
+ private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
+ Rect displayFrame) {
final Animation anim;
if (enter) {
- // Reveal will expand and move faster in horizontal direction
-
final int appWidth = appFrame.width();
final int appHeight = appFrame.height();
+
// mTmpRect will contain an area around the launcher icon that was pressed. We will
// clip reveal from that area in the final area of the app.
getDefaultNextAppTransitionStartRect(mTmpRect);
float t = 0f;
if (appHeight > 0) {
- t = (float) mTmpRect.left / appHeight;
+ t = (float) mTmpRect.top / displayFrame.height();
}
- int translationY = mClipRevealTranslationY + (int)(appHeight / 7f * t);
-
+ int translationY = mClipRevealTranslationY + (int)(displayFrame.height() / 7f * t);
+ int translationX = 0;
+ int translationYCorrection = translationY;
int centerX = mTmpRect.centerX();
int centerY = mTmpRect.centerY();
int halfWidth = mTmpRect.width() / 2;
int halfHeight = mTmpRect.height() / 2;
+ int clipStartX = centerX - halfWidth - appFrame.left;
+ int clipStartY = centerY - halfHeight - appFrame.top;
+ boolean cutOff = false;
+
+ // If the starting rectangle is fully or partially outside of the target rectangle, we
+ // need to start the clipping at the edge and then achieve the rest with translation
+ // and extending the clip rect from that edge.
+ if (appFrame.top > centerY - halfHeight) {
+ translationY = (centerY - halfHeight) - appFrame.top;
+ translationYCorrection = 0;
+ clipStartY = 0;
+ cutOff = true;
+ }
+ if (appFrame.left > centerX - halfWidth) {
+ translationX = (centerX - halfWidth) - appFrame.left;
+ clipStartX = 0;
+ cutOff = true;
+ }
+ if (appFrame.right < centerX + halfWidth) {
+ translationX = (centerX + halfWidth) - appFrame.right;
+ clipStartX = appWidth - mTmpRect.width();
+ cutOff = true;
+ }
+ final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
+ translationY, displayFrame);
// Clip third of the from size of launch icon, expand to full width/height
Animation clipAnimLR = new ClipRectLRAnimation(
- centerX - halfWidth, centerX + halfWidth, 0, appWidth);
+ clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
- clipAnimLR.setDuration((long) (DEFAULT_APP_TRANSITION_DURATION / 2.5f));
+ clipAnimLR.setDuration((long) (duration / 2.5f));
- Animation clipAnimTB = new ClipRectTBAnimation(centerY - halfHeight - translationY,
- centerY + halfHeight/ 2 - translationY, 0, appHeight);
- clipAnimTB.setInterpolator(mTouchResponseInterpolator);
- clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
+ translate.setInterpolator(cutOff ? TOUCH_RESPONSE_INTERPOLATOR
+ : mLinearOutSlowInInterpolator);
+ translate.setDuration(duration);
- // We might be animating entrance of a docked task, so we need the translate to account
- // for the app frame in which the window will reside. Every other calculation here
- // is performed as if the window started at 0,0.
- translationY -= appFrame.top;
- TranslateAnimation translate = new TranslateAnimation(-appFrame.left, 0, translationY,
- 0);
- translate.setInterpolator(mLinearOutSlowInInterpolator);
- translate.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ Animation clipAnimTB = new ClipRectTBAnimation(
+ clipStartY, clipStartY + mTmpRect.height(),
+ 0, appHeight,
+ translationYCorrection, 0,
+ mLinearOutSlowInInterpolator);
+ clipAnimTB.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+ clipAnimTB.setDuration(duration);
// Quick fade-in from icon to app window
- final int alphaDuration = DEFAULT_APP_TRANSITION_DURATION / 4;
+ final long alphaDuration = duration / 4;
AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
alpha.setDuration(alphaDuration);
alpha.setInterpolator(mLinearOutSlowInInterpolator);
@@ -692,6 +748,7 @@
set.setZAdjustment(Animation.ZORDER_TOP);
set.initialize(appWidth, appHeight, appWidth, appHeight);
anim = set;
+ mLastClipRevealTransitionDuration = duration;
} else {
final long duration;
switch (transit) {
@@ -798,7 +855,7 @@
// Animation up from the thumbnail to the full screen
Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
- scale.setInterpolator(mTouchResponseInterpolator);
+ scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
Animation alpha = new AlphaAnimation(1f, 0f);
alpha.setInterpolator(mThumbnailFadeOutInterpolator);
@@ -806,7 +863,7 @@
final float toX = appRect.left + appRect.width() / 2 -
(mTmpRect.left + thumbWidth / 2);
Animation translate = new TranslateAnimation(0, toX, 0, toY);
- translate.setInterpolator(mTouchResponseInterpolator);
+ translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
// This AnimationSet uses the Interpolators assigned above.
@@ -819,7 +876,7 @@
// Animation down from the full screen to the thumbnail
Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
- scale.setInterpolator(mTouchResponseInterpolator);
+ scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
Animation alpha = new AlphaAnimation(0f, 1f);
alpha.setInterpolator(mThumbnailFadeInInterpolator);
@@ -827,7 +884,7 @@
final float toX = appRect.left + appRect.width() / 2 -
(mTmpRect.left + thumbWidth / 2);
Animation translate = new TranslateAnimation(toX, 0, toY, 0);
- translate.setInterpolator(mTouchResponseInterpolator);
+ translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
// This AnimationSet uses the Interpolators assigned above.
@@ -839,7 +896,7 @@
}
return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
- mTouchResponseInterpolator);
+ TOUCH_RESPONSE_INTERPOLATOR);
}
/**
@@ -971,7 +1028,7 @@
int duration = Math.max(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION,
THUMBNAIL_APP_TRANSITION_DURATION);
return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
- mTouchResponseInterpolator);
+ TOUCH_RESPONSE_INTERPOLATOR);
}
private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
@@ -1223,8 +1280,9 @@
* bigger.
*/
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets,
- boolean isVoiceInteraction, boolean freeform, int taskId) {
+ int orientation, Rect frame, Rect displayFrame, Rect insets,
+ @Nullable Rect surfaceInsets, boolean isVoiceInteraction, boolean freeform,
+ int taskId) {
Animation a;
if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
@@ -1269,7 +1327,7 @@
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = createClipRevealAnimationLocked(transit, enter, frame);
+ a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ec99d1f..d7c1b32 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -28,6 +28,7 @@
import android.view.SurfaceControl;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import com.android.server.wm.DimLayer.DimLayerUser;
@@ -39,6 +40,8 @@
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
+import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -49,8 +52,6 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
- private static final long MINIMIZED_DOCK_ANIMATION_DURATION = 400;
-
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final int mDividerWindowWidth;
@@ -71,6 +72,7 @@
private long mAnimationStartTime;
private float mAnimationStart;
private float mAnimationTarget;
+ private long mAnimationDuration;
private final Interpolator mMinimizedDockInterpolator;
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
@@ -331,6 +333,10 @@
notifyDockedStackMinimizedChanged(minimized, 0);
}
+ private boolean isAnimationMaximizing() {
+ return mAnimationTarget == 0f;
+ }
+
public boolean animate(long now) {
if (!mAnimating) {
return false;
@@ -339,12 +345,17 @@
if (!mAnimationStarted) {
mAnimationStarted = true;
mAnimationStartTime = now;
+ final long transitionDuration = isAnimationMaximizing()
+ ? mService.mAppTransition.getLastClipRevealTransitionDuration()
+ : DEFAULT_APP_TRANSITION_DURATION;
+ mAnimationDuration = (long)
+ (transitionDuration * mService.getTransitionAnimationScaleLocked());
notifyDockedStackMinimizedChanged(mMinimizedDock,
- MINIMIZED_DOCK_ANIMATION_DURATION);
+ mAnimationDuration);
}
- float t = Math.min(1f, (float) (now - mAnimationStartTime)
- / MINIMIZED_DOCK_ANIMATION_DURATION);
- t = mMinimizedDockInterpolator.getInterpolation(t);
+ float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
+ t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
+ .getInterpolation(t);
final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
if (stack != null) {
final float amount = t * mAnimationTarget + (1 - t) * mAnimationStart;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b41ff1c..882ac57 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2968,6 +2968,8 @@
// Determine the visible rect to calculate the thumbnail clip
final WindowState win = atoken.findMainWindow();
final Rect frame = new Rect(0, 0, width, height);
+ final Rect displayFrame = new Rect(0, 0,
+ displayInfo.logicalWidth, displayInfo.logicalHeight);
final Rect insets = new Rect();
Rect surfaceInsets = null;
final boolean freeform = win != null && win.inFreeformWorkspace();
@@ -2995,8 +2997,8 @@
+ " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
Animation a = mAppTransition.loadAnimation(lp, transit, enter,
- mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
- freeform, atoken.mTask.mTaskId);
+ mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets,
+ isVoiceInteraction, freeform, atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
final int containingWidth = frame.width();
diff --git a/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java b/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java
new file mode 100644
index 0000000..0db4c70
--- /dev/null
+++ b/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.server.wm.animation;
+
+import android.graphics.Rect;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Transformation;
+
+/**
+ * Special case of ClipRectAnimation that animates only the left/right
+ * dimensions of the clip, picking up the other dimensions from whatever is
+ * set on the transform already.
+ *
+ * @hide
+ */
+public class ClipRectLRAnimation extends ClipRectAnimation {
+
+ /**
+ * Constructor. Passes in 0 for Top/Bottom parameters of ClipRectAnimation
+ */
+ public ClipRectLRAnimation(int fromL, int fromR, int toL, int toR) {
+ super(fromL, 0, fromR, 0, toL, 0, toR, 0);
+ }
+
+ /**
+ * Calculates and sets clip rect on given transformation. It uses existing values
+ * on the Transformation for Top/Bottom clip parameters.
+ */
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ Rect oldClipRect = tr.getClipRect();
+ tr.setClipRect(mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it),
+ oldClipRect.top,
+ mFromRect.right + (int) ((mToRect.right - mFromRect.right) * it),
+ oldClipRect.bottom);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java b/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java
new file mode 100644
index 0000000..1f5b1a3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 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.server.wm.animation;
+
+import android.graphics.Rect;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+/**
+ * Special case of ClipRectAnimation that animates only the top/bottom
+ * dimensions of the clip, picking up the other dimensions from whatever is
+ * set on the transform already. In addition to that, information about a vertical translation
+ * animation can be specified so this animation simulates as the clip would be applied after instead
+ * of before applying the translation.
+ */
+public class ClipRectTBAnimation extends ClipRectAnimation {
+
+ private final int mFromTranslateY;
+ private final int mToTranslateY;
+ private final Interpolator mTranslateInterpolator;
+ private float mNormalizedTime;
+
+ /**
+ * Constructor. Passes in 0 for Left/Right parameters of ClipRectAnimation
+ */
+ public ClipRectTBAnimation(int fromT, int fromB, int toT, int toB,
+ int fromTranslateY, int toTranslateY, Interpolator translateInterpolator) {
+ super(0, fromT, 0, fromB, 0, toT, 0, toB);
+ mFromTranslateY = fromTranslateY;
+ mToTranslateY = toTranslateY;
+ mTranslateInterpolator = translateInterpolator;
+ }
+
+ @Override
+ public boolean getTransformation(long currentTime, Transformation outTransformation) {
+
+ // Hack: Because translation animation has a different interpolator, we need to duplicate
+ // code from Animation here and use it to calculate/store the uninterpolated normalized
+ // time.
+ final long startOffset = getStartOffset();
+ final long duration = getDuration();
+ float normalizedTime;
+ if (duration != 0) {
+ normalizedTime = ((float) (currentTime - (getStartTime() + startOffset))) /
+ (float) duration;
+ } else {
+ // time is a step-change with a zero duration
+ normalizedTime = currentTime < getStartTime() ? 0.0f : 1.0f;
+ }
+ mNormalizedTime = normalizedTime;
+ return super.getTransformation(currentTime, outTransformation);
+ }
+
+ /**
+ * Calculates and sets clip rect on given transformation. It uses existing values
+ * on the Transformation for Left/Right clip parameters.
+ */
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ float translationT = mTranslateInterpolator.getInterpolation(mNormalizedTime);
+ int translation =
+ (int) (mFromTranslateY + (mToTranslateY - mFromTranslateY) * translationT);
+ Rect oldClipRect = tr.getClipRect();
+ tr.setClipRect(oldClipRect.left,
+ mFromRect.top - translation + (int) ((mToRect.top - mFromRect.top) * it),
+ oldClipRect.right,
+ mFromRect.bottom - translation + (int) ((mToRect.bottom - mFromRect.bottom) * it));
+ }
+
+}