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. */