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

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

Fix janky wakeup animation

Previous wake-up animation was totally broken before, as multiple
animations were running with different interpolators and different
durations, creating a really jaring experience for the front-door
of the phone, which users see hundreds of times a day.

Instead, we use a single animator to drive everything, and pass in
both the interpolated and the linear value so the right value can
be used in whichever place.

Test: Wakeup/sleep phone with different numbers of notifications
Fixes: 110980608
Change-Id: If1758404a4c49fcd7dc9fa3c93bb18e42a586632
parent 5c75b5b6
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -31,6 +31,13 @@ import com.android.systemui.statusbar.stack.HeadsUpAppearInterpolator;
 */
public class Interpolators {
    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);

    /**
     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
     * goes from 1 to 0 instead of 0 to 1).
     */
    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
@@ -51,4 +58,11 @@ public class Interpolators {
     */
    public static final Interpolator TOUCH_RESPONSE =
            new PathInterpolator(0.3f, 0f, 0.1f, 1f);

    /**
     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
     * goes from 1 to 0 instead of 0 to 1).
     */
    public static final Interpolator TOUCH_RESPONSE_REVERSE =
            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
}
+38 −15
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;

import com.android.internal.logging.MetricsLogger;
@@ -107,17 +108,20 @@ public class NotificationPanelView extends PanelView implements
    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
            .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    private static final FloatProperty<NotificationPanelView> SET_DARK_AMOUNT_PROPERTY =
            new FloatProperty<NotificationPanelView>("mDarkAmount") {
            new FloatProperty<NotificationPanelView>("mInterpolatedDarkAmount") {

                @Override
                public void setValue(NotificationPanelView object, float value) {
                    object.setDarkAmount(value);
                    object.setDarkAmount(value, object.mDarkInterpolator.getInterpolation(value));
                }

                @Override
                public Float get(NotificationPanelView object) {
                    return object.mDarkAmount;
                    return object.mLinearDarkAmount;
                }
            };

    private Interpolator mDarkInterpolator;
    private final PowerManager mPowerManager;
    private final AccessibilityManager mAccessibilityManager;

@@ -237,7 +241,18 @@ public class NotificationPanelView extends PanelView implements
    private int mIndicationBottomPadding;
    private int mAmbientIndicationBottomPadding;
    private boolean mIsFullWidth;
    private float mDarkAmount;

    /**
     * Current dark amount that follows regular interpolation curve of animation.
     */
    private float mInterpolatedDarkAmount;

    /**
     * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
     * interpolation curve is different.
     */
    private float mLinearDarkAmount;

    private float mDarkAmountTarget;
    private boolean mPulsing;
    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
@@ -392,7 +407,7 @@ public class NotificationPanelView extends PanelView implements
                false);
        addView(mKeyguardBottomArea, index);
        initBottomArea();
        setDarkAmount(mDarkAmount);
        setDarkAmount(mLinearDarkAmount, mInterpolatedDarkAmount);

        setKeyguardStatusViewVisibility(mStatusBarState, false, false);
        setKeyguardBottomAreaVisibility(mStatusBarState, false);
@@ -506,7 +521,7 @@ public class NotificationPanelView extends PanelView implements
                    getExpandedFraction(),
                    totalHeight,
                    mKeyguardStatusView.getHeight(),
                    mDarkAmount,
                    mInterpolatedDarkAmount,
                    mStatusBar.isKeyguardCurrentlySecure(),
                    mPulsing,
                    mBouncerTop);
@@ -1917,7 +1932,7 @@ public class NotificationPanelView extends PanelView implements
        if (view == null && mQsExpanded) {
            return;
        }
        if (needsAnimation && mDarkAmount == 0) {
        if (needsAnimation && mInterpolatedDarkAmount == 0) {
            mAnimateNextPositionUpdate = true;
        }
        ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
@@ -2727,20 +2742,28 @@ public class NotificationPanelView extends PanelView implements
        }
        mDarkAmountTarget = darkAmount;
        if (animate) {
            if (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f) {
                mDarkInterpolator = dozing
                        ? Interpolators.FAST_OUT_SLOW_IN
                        : Interpolators.TOUCH_RESPONSE_REVERSE;
            }
            mNotificationStackScroller.notifyDarkAnimationStart(dozing);
            mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, darkAmount);
            mDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
            mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
            mDarkAnimator.setInterpolator(Interpolators.LINEAR);
            mDarkAnimator.setDuration(mNotificationStackScroller.getDarkAnimationDuration(dozing));
            mDarkAnimator.start();
        } else {
            setDarkAmount(darkAmount);
            setDarkAmount(darkAmount, darkAmount);
        }
    }

    private void setDarkAmount(float amount) {
        mDarkAmount = amount;
        mKeyguardStatusView.setDarkAmount(mDarkAmount);
        mKeyguardBottomArea.setDarkAmount(mDarkAmount);
    private void setDarkAmount(float linearAmount, float amount) {
        mInterpolatedDarkAmount = amount;
        mLinearDarkAmount = linearAmount;
        mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
        mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
        positionClockAndNotifications();
        mNotificationStackScroller.setDarkAmount(linearAmount, mInterpolatedDarkAmount);
    }

    public void setPulsing(boolean pulsing) {
@@ -2765,7 +2788,7 @@ public class NotificationPanelView extends PanelView implements
    public void dozeTimeTick() {
        mKeyguardStatusView.dozeTimeTick();
        mKeyguardBottomArea.dozeTimeTick();
        if (mDarkAmount > 0) {
        if (mInterpolatedDarkAmount > 0) {
            positionClockAndNotifications();
        }
    }
+51 −62
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -46,11 +45,9 @@ import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -375,25 +372,22 @@ public class NotificationStackScrollLayout extends ViewGroup
    private boolean mScrollable;
    private View mForcedScroll;
    private View mNeedingPulseAnimation;
    private float mDarkAmount = 0f;

    /**
     * @see #setDarkAmount(float, float)
     */
    private float mInterpolatedDarkAmount = 0f;

    /**
     * @see #setDarkAmount(float, float)
     */
    private float mLinearDarkAmount = 0f;

    /**
     * How fast the background scales in the X direction as a factor of the Y expansion.
     */
    private float mBackgroundXFactor = 1f;
    private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT =
            new FloatProperty<NotificationStackScrollLayout>("darkAmount") {
                @Override
                public void setValue(NotificationStackScrollLayout object, float value) {
                    object.setDarkAmount(value);
                }

                @Override
                public Float get(NotificationStackScrollLayout object) {
                    return object.getDarkAmount();
                }
            };
    private ObjectAnimator mDarkAmountAnimator;
    private boolean mUsingLightTheme;
    private boolean mQsExpanded;
    private boolean mForwardScrollable;
@@ -424,6 +418,8 @@ public class NotificationStackScrollLayout extends ViewGroup
    private NotificationIconAreaController mIconAreaController;
    private float mVerticalPanelTranslation;

    private Interpolator mDarkXInterpolator = Interpolators.FAST_OUT_SLOW_IN;

    public NotificationStackScrollLayout(Context context) {
        this(context, null);
    }
@@ -558,16 +554,16 @@ public class NotificationStackScrollLayout extends ViewGroup
                canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint);
            }
        } else {
            float inverseDark = 1 - mDarkAmount;
            float yProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(inverseDark);
            float xProgress = Interpolators.FAST_OUT_SLOW_IN
                    .getInterpolation(inverseDark * mBackgroundXFactor);
            float yProgress = 1 - mInterpolatedDarkAmount;
            float xProgress = mDarkXInterpolator.getInterpolation(
                    (1 - mLinearDarkAmount) * mBackgroundXFactor);

            mBackgroundAnimationRect.set(
                    (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress),
                    (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress),
                    (int) MathUtils.lerp(darkRight, lockScreenRight, xProgress),
                    (int) MathUtils.lerp(darkBottom, lockScreenBottom, yProgress));

            if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) {
                canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top,
                        mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom,
@@ -585,14 +581,15 @@ public class NotificationStackScrollLayout extends ViewGroup

        float alpha =
                BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
        alpha *= 1f - mDarkAmount;
        alpha *= 1f - mInterpolatedDarkAmount;
        // We need to manually blend in the background color.
        int scrimColor = mScrimController.getBackgroundColor();
        int awakeColor = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);

        // Interpolate between semi-transparent notification panel background color
        // and white AOD separator.
        float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(mDarkAmount);
        float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(
                mInterpolatedDarkAmount);
        int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);

        if (mCachedBackgroundColor != color) {
@@ -736,7 +733,8 @@ public class NotificationStackScrollLayout extends ViewGroup
    }

    private void updateAlgorithmHeightAndPadding() {
        mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding, mDarkAmount);
        mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding,
                mInterpolatedDarkAmount);
        mAmbientState.setLayoutHeight(getLayoutHeight());
        updateAlgorithmLayoutMinHeight();
        mAmbientState.setTopPadding(mTopPadding);
@@ -961,7 +959,7 @@ public class NotificationStackScrollLayout extends ViewGroup
    }

    public void updateClipping() {
        boolean animatingClipping = mDarkAmount > 0 && mDarkAmount < 1;
        boolean animatingClipping = mInterpolatedDarkAmount > 0 && mInterpolatedDarkAmount < 1;
        boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
                && !mHeadsUpAnimatingAway;
        if (mIsClipped != clipped) {
@@ -2421,7 +2419,7 @@ public class NotificationStackScrollLayout extends ViewGroup
            return;
        }

        final boolean awake = mDarkAmount != 0 || mAmbientState.isDark();
        final boolean awake = mInterpolatedDarkAmount != 0 || mAmbientState.isDark();
        mScrimController.setExcludedBackgroundArea(
                mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null
                        : mCurrentBounds);
@@ -3414,7 +3412,6 @@ public class NotificationStackScrollLayout extends ViewGroup
                            .animateY(mShelf));
            ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
            mAnimationEvents.add(ev);
            startDarkAmountAnimation();
        }
        mDarkNeedsAnimation = false;
    }
@@ -3992,9 +3989,6 @@ public class NotificationStackScrollLayout extends ViewGroup
            mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
            mNeedsAnimation = true;
        } else {
            if (mDarkAmountAnimator != null) {
                mDarkAmountAnimator.cancel();
            }
            setDarkAmount(dark ? 1f : 0f);
            updateBackground();
        }
@@ -4005,7 +3999,7 @@ public class NotificationStackScrollLayout extends ViewGroup
    }

    private void updatePanelTranslation() {
        setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mDarkAmount);
        setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
    }

    public void setVerticalPanelTranslation(float verticalPanelTranslation) {
@@ -4024,9 +4018,22 @@ public class NotificationStackScrollLayout extends ViewGroup
    }

    private void setDarkAmount(float darkAmount) {
        mDarkAmount = darkAmount;
        setDarkAmount(darkAmount, darkAmount);
    }

    /**
     * Sets the current dark amount.
     *
     * @param linearDarkAmount The dark amount that follows linear interpoloation in the animation,
     *                         i.e. animates from 0 to 1 or vice-versa in a linear manner.
     * @param interpolatedDarkAmount The dark amount that follows the actual interpolation of the
     *                                animation curve.
     */
    public void setDarkAmount(float linearDarkAmount, float interpolatedDarkAmount) {
        mLinearDarkAmount = linearDarkAmount;
        mInterpolatedDarkAmount = interpolatedDarkAmount;
        boolean wasFullyDark = mAmbientState.isFullyDark();
        mAmbientState.setDarkAmount(darkAmount);
        mAmbientState.setDarkAmount(interpolatedDarkAmount);
        boolean nowFullyDark = mAmbientState.isFullyDark();
        if (nowFullyDark != wasFullyDark) {
            updateContentHeight();
@@ -4044,42 +4051,24 @@ public class NotificationStackScrollLayout extends ViewGroup
        requestChildrenUpdate();
    }

    public float getDarkAmount() {
        return mDarkAmount;
    public void notifyDarkAnimationStart(boolean dark) {
        // We only swap the scaling factor if we're fully dark or fully awake to avoid
        // interpolation issues when playing with the power button.
        if (mInterpolatedDarkAmount == 0 || mInterpolatedDarkAmount == 1) {
            mBackgroundXFactor = dark ? 1.8f : 1.5f;
            mDarkXInterpolator = dark
                    ? Interpolators.FAST_OUT_SLOW_IN_REVERSE
                    : Interpolators.FAST_OUT_SLOW_IN;
        }

    /**
     * Cancel any previous dark animations - to avoid race conditions - and creates a new one.
     * This function also sets {@code mBackgroundXFactor} based on the current {@code mDarkAmount}.
     */
    private void startDarkAmountAnimation() {
        boolean dark = mAmbientState.isDark();
        if (mDarkAmountAnimator != null) {
            mDarkAmountAnimator.cancel();
    }

    public long getDarkAnimationDuration(boolean dark) {
        long duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
        // Longer animation when sleeping with more than 1 notification
        if (dark && getNotGoneChildCount() > 2) {
            duration *= 1.2f;
        }

        mDarkAmountAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount,
                dark ? 1f : 0);
        // We only swap the scaling factor if we're fully dark or fully awake to avoid
        // interpolation issues when playing with the power button.
        if (mDarkAmount == 0 || mDarkAmount == 1) {
            mBackgroundXFactor = dark ? 2.5f : 1.5f;
        }
        mDarkAmountAnimator.setDuration(duration);
        mDarkAmountAnimator.setInterpolator(Interpolators.LINEAR);
        mDarkAmountAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mDarkAmountAnimator = null;
            }
        });
        mDarkAmountAnimator.start();
        return duration;
    }

    private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {