From f97ed9271fef192f5411206e781eb3519c144ace Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Thu, 18 Feb 2016 18:57:07 -0800 Subject: [PATCH] Improve maximizing dock/clip reveal animation There was a very visible hole at the end of the transition when both the docked stack expands and the other app is doing a clip reveal animation, but only if the app had to travel a far distance to meet it's target rectangle. To fix this, we interpolate between two animation curves for maximizing divider: One is the original curve, and the other one is an adjusted one which simulate if the divider would start at the edge of the app surface that plays the clip reveal animation. At the start of the animation, we use the original curve, but then about in the middle of the animation we interpolated towards the adjusted curve, so the divider meets the edge of the app surface that plays the clip reveal animation, eliminating that gap while still preserving the overall motion. Bug: 27154882 Change-Id: I37716588f88ddc643ed6176c2ccd56ca174e8919 --- .../com/android/server/wm/AppTransition.java | 29 +++++++ .../wm/DockedStackDividerController.java | 83 +++++++++++++++++-- .../java/com/android/server/wm/TaskStack.java | 20 +++++ 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index ba9e6404cf662..5cb709936bd1a 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -216,6 +216,9 @@ public class AppTransition implements Dump { private final ArrayList mListeners = new ArrayList<>(); private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor(); + private int mLastClipRevealMaxTranslation; + private boolean mLastHadClipReveal; + AppTransition(Context context, WindowManagerService service) { mContext = context; mService = service; @@ -337,6 +340,9 @@ public class AppTransition implements Dump { if (!isRunning()) { mAppTransitionState = APP_STATE_IDLE; notifyAppTransitionPendingLocked(); + mLastHadClipReveal = false; + mLastClipRevealMaxTranslation = 0; + mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION; return true; } return false; @@ -641,10 +647,27 @@ public class AppTransition implements Dump { bitmap, new Rect(left, top, left + width, top + height)); } + /** + * @return the duration of the last clip reveal animation + */ long getLastClipRevealTransitionDuration() { return mLastClipRevealTransitionDuration; } + /** + * @return the maximum distance the app surface is traveling of the last clip reveal animation + */ + int getLastClipRevealMaxTranslation() { + return mLastClipRevealMaxTranslation; + } + + /** + * @return true if in the last app transition had a clip reveal animation, false otherwise + */ + boolean hadClipRevealAnimation() { + return mLastHadClipReveal; + } + /** * 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. @@ -748,7 +771,13 @@ public class AppTransition implements Dump { set.setZAdjustment(Animation.ZORDER_TOP); set.initialize(appWidth, appHeight, appWidth, appHeight); anim = set; + mLastHadClipReveal = true; mLastClipRevealTransitionDuration = duration; + + // If the start rect was full inside the target rect (cutOff == false), we don't need + // to store the translation, because it's only used if cutOff == true. + mLastClipRevealMaxTranslation = cutOff + ? Math.max(Math.abs(translationY), Math.abs(translationX)) : 0; } else { final long duration; switch (transit) { diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index d7c1b328ad2d3..b6aa3f2ed4b09 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -21,14 +21,12 @@ import android.graphics.Rect; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.ArraySet; -import android.util.Log; import android.util.Slog; import android.view.DisplayInfo; import android.view.IDockedStackListener; 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; @@ -52,6 +50,30 @@ public class DockedStackDividerController implements DimLayerUser { private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM; + /** + * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip + * revealing surface at the earliest. + */ + private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f; + + /** + * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip + * revealing surface at the latest. + */ + private static final float CLIP_REVEAL_MEET_LAST = 1f; + + /** + * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start + * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}. + */ + private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f; + + /** + * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, + * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}. + */ + private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f; + private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final int mDividerWindowWidth; @@ -74,6 +96,7 @@ public class DockedStackDividerController implements DimLayerUser { private float mAnimationTarget; private long mAnimationDuration; private final Interpolator mMinimizedDockInterpolator; + private float mMaximizeMeetFraction; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -342,6 +365,7 @@ public class DockedStackDividerController implements DimLayerUser { return false; } + final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked(); if (!mAnimationStarted) { mAnimationStarted = true; mAnimationStartTime = now; @@ -350,16 +374,15 @@ public class DockedStackDividerController implements DimLayerUser { : DEFAULT_APP_TRANSITION_DURATION; mAnimationDuration = (long) (transitionDuration * mService.getTransitionAnimationScaleLocked()); + mMaximizeMeetFraction = getClipRevealMeetFraction(stack); notifyDockedStackMinimizedChanged(mMinimizedDock, - mAnimationDuration); + (long) (mAnimationDuration * mMaximizeMeetFraction)); } 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; - if (stack.setAdjustedForMinimizedDock(amount)) { + if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) { mService.mWindowPlacerLocked.performSurfacePlacement(); } } @@ -371,6 +394,54 @@ public class DockedStackDividerController implements DimLayerUser { } } + /** + * Gets the amount how much to minimize a stack depending on the interpolated fraction t. + */ + private float getMinimizeAmount(TaskStack stack, float t) { + final float naturalAmount = t * mAnimationTarget + (1 - t) * mAnimationStart; + if (isAnimationMaximizing()) { + return adjustMaximizeAmount(stack, t, naturalAmount); + } else { + return naturalAmount; + } + } + + /** + * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount + * during the transition such that the edge of the clip reveal rect is met earlier in the + * transition so we don't create a visible "hole", but only if both the clip reveal and the + * docked stack divider start from about the same portion on the screen. + */ + private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) { + if (mMaximizeMeetFraction == 1f) { + return naturalAmount; + } + final int minimizeDistance = stack.getMinimizeDistance(); + float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation() + / (float) minimizeDistance; + final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime; + final float t2 = Math.min(t / mMaximizeMeetFraction, 1); + return amountPrime * t2 + naturalAmount * (1 - t2); + } + + /** + * Retrieves the animation fraction at which the docked stack has to meet the clip reveal + * edge. See {@link #adjustMaximizeAmount}. + */ + private float getClipRevealMeetFraction(TaskStack stack) { + if (!isAnimationMaximizing() || stack == null || + !mService.mAppTransition.hadClipRevealAnimation()) { + return 1f; + } + final int minimizeDistance = stack.getMinimizeDistance(); + final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation()) + / (float) minimizeDistance; + final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN) + / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN))); + return CLIP_REVEAL_MEET_EARLIEST + + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); + } + @Override public boolean isFullscreen() { return false; diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index c9873a5cdd2ed..2293e4d69e569 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -876,6 +876,26 @@ public class TaskStack implements DimLayer.DimLayerUser, return true; } + /** + * @return the distance in pixels how much the stack gets minimized from it's original size + */ + int getMinimizeDistance() { + final int dockSide = getDockSide(); + if (dockSide == DOCKED_INVALID) { + return 0; + } + + if (dockSide == DOCKED_TOP) { + mService.getStableInsetsLocked(mTmpRect); + int topInset = mTmpRect.top; + return mBounds.bottom - topInset; + } else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) { + return mBounds.width() - mDockedStackMinimizeThickness; + } else { + return 0; + } + } + /** * Updates the adjustment depending on it's current state. */