Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f97ed927 authored by Jorim Jaggi's avatar Jorim Jaggi
Browse files

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
parent 14b2deac
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -216,6 +216,9 @@ public class AppTransition implements Dump {
    private final ArrayList<AppTransitionListener> 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) {
+77 −6
Original line number Diff line number Diff line
@@ -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;
+20 −0
Original line number Diff line number Diff line
@@ -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.
     */