From 40cebe4468db1b42c88373c435e02da77891d3d8 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Thu, 3 Jun 2021 20:41:26 +0200 Subject: [PATCH] Improved the behavior of overscroll The overscroll logic could get stuck previously, which lead to a forehead visible on the lock screen. This improves the logic and animation to look much better. Fixes: 190029393 Test: expand shade, observe nice overshoot, no forehead on lockscreen Change-Id: I2c9d5313626d3037c7bd3c942820c391da99b459 --- .../shell/animation/FlingAnimationUtils.java | 7 + .../systemui/animation/Interpolators.java | 10 + packages/SystemUI/res/values/dimens.xml | 3 + .../notification/stack/AmbientState.java | 10 +- .../stack/NotificationStackScrollLayout.java | 30 ++- ...tificationStackScrollLayoutController.java | 23 ++- .../stack/StackScrollAlgorithm.java | 4 - .../NotificationPanelViewController.java | 100 ++++------ .../systemui/statusbar/phone/PanelBar.java | 2 +- .../statusbar/phone/PanelViewController.java | 182 ++++++++++++------ .../phone/NotificationPanelViewTest.java | 2 - 11 files changed, 220 insertions(+), 153 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java index 176c620fa1193..798250de89d0d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java @@ -311,6 +311,13 @@ public class FlingAnimationUtils { return mMinVelocityPxPerSecond; } + /** + * @return a velocity considered fast + */ + public float getHighVelocityPxPerSecond() { + return mHighVelocityPxPerSecond; + } + /** * An interpolator which interpolates two interpolators with an interpolator. */ diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java index 457e8e6e9a5fa..659b9fee86564 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java @@ -93,6 +93,16 @@ public class Interpolators { (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f)); } + /** + * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot + * starts immediately here, instead of first having a section of non-overshooting + * + * @param progress a progress value going from 0 to 1 + */ + public static float getOvershootInterpolation(float progress) { + return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress))); + } + /** * Interpolate alpha for notifications background scrim during shade expansion. * @param fraction Shade expansion fraction diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 05f2f6727a73f..ad2a1aceabcc5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -771,6 +771,9 @@ 75dp + + 16dp + 48dp diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index a18917789ba19..0c86262d9037f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -83,7 +83,7 @@ public class AmbientState { private ExpandableNotificationRow mTrackedHeadsUpRow; private float mAppearFraction; private boolean mIsShadeOpening; - private float mSectionPadding; + private float mOverExpansion; /** Distance of top of notifications panel from top of screen. */ private float mStackY = 0; @@ -182,12 +182,12 @@ public class AmbientState { return mIsShadeOpening; } - void setSectionPadding(float padding) { - mSectionPadding = padding; + void setOverExpansion(float overExpansion) { + mOverExpansion = overExpansion; } - float getSectionPadding() { - return mSectionPadding; + float getOverExpansion() { + return mOverExpansion; } private static int getZDistanceBetweenElements(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 64f228f41df9d..9390c81a847bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -119,6 +119,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; @@ -470,6 +471,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } }; + private Consumer mScrollListener; private final ScrollAdapter mScrollAdapter = new ScrollAdapter() { @Override public boolean isScrolledToTop() { @@ -552,8 +554,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - void setSectionPadding(float margin) { - mAmbientState.setSectionPadding(margin); + /** + * Set the overexpansion of the panel to be applied to the view. + */ + void setOverExpansion(float margin) { + mAmbientState.setOverExpansion(margin); + updateStackPosition(); requestChildrenUpdate(); } @@ -1136,7 +1142,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private void updateStackPosition() { // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD - float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition; + float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + + mAmbientState.getOverExpansion(); final float fraction = mAmbientState.getExpansionFraction(); final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); @@ -1144,7 +1151,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mOnStackYChanged.run(); } if (mQsExpansionFraction <= 0) { - final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mSidePaddings; final float stackEndHeight = Math.max(0f, getHeight() - getEmptyBottomMargin() - mTopPadding); mAmbientState.setStackEndHeight(stackEndHeight); @@ -1166,7 +1172,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void setExpandedHeight(float height) { final float shadeBottom = getHeight() - getEmptyBottomMargin(); - final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f); + final float expansionFraction = MathUtils.saturate(height / shadeBottom); mAmbientState.setExpansionFraction(expansionFraction); updateStackPosition(); @@ -2395,8 +2401,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable float topOverScroll = getCurrentOverScrollAmount(true); return mScrolledToTopOnFirstDown && !mExpandedInThisMotion - && topOverScroll > mMinTopOverScrollToEscape - && initialVelocity > 0; + && (initialVelocity > mMinimumVelocity + || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0)); } /** @@ -4552,6 +4558,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void updateOnScrollChange() { + if (mScrollListener != null) { + mScrollListener.accept(mOwnScrollY); + } updateForwardAndBackwardScrollability(); requestChildrenUpdate(); } @@ -5162,6 +5171,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestChildrenUpdate(); } + /** + * Set a listener to when scrolling changes. + */ + public void setOnScrollListener(Consumer listener) { + mScrollListener = listener; + } + /** * A listener that is notified when the empty space below the notifications is clicked on */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 4432f5463802e..a6bba93d98162 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -124,6 +124,7 @@ import com.android.systemui.tuner.TunerService; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; @@ -302,8 +303,11 @@ public class NotificationStackScrollLayoutController { } }; - public void setSectionPadding(float padding) { - mView.setSectionPadding(padding); + /** + * Set the overexpansion of the panel to be applied to the view. + */ + public void setOverExpansion(float overExpansion) { + mView.setOverExpansion(overExpansion); } private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { @@ -944,10 +948,6 @@ public class NotificationStackScrollLayoutController { mView.setOverScrollAmount(amount, onTop, animate); } - public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { - mView.setOverScrolledPixels(numPixels, onTop, animate); - } - public void resetScrollPosition() { mView.resetScrollPosition(); } @@ -1054,10 +1054,6 @@ public class NotificationStackScrollLayoutController { return mView.getCurrentOverScrollAmount(top); } - public float getCurrentOverScrolledPixels(boolean top) { - return mView.getCurrentOverScrolledPixels(top); - } - public float calculateAppearFraction(float height) { return mView.calculateAppearFraction(height); } @@ -1430,6 +1426,13 @@ public class NotificationStackScrollLayoutController { mView.setExtraTopInsetForFullShadeTransition(extraTopInset); } + /** + * Set a listener to when scrolling changes. + */ + public void setOnScrollListener(Consumer listener) { + mView.setOnScrollListener(listener); + } + /** * Enum for UiEvent logged from this class */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 63c5c80ff3814..a02ebbfa3521e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -403,10 +403,6 @@ public class StackScrollAlgorithm { } viewState.yTranslation = algorithmState.mCurrentYPosition; - if (view instanceof SectionHeaderView) { - // Add padding before sections for overscroll effect. - viewState.yTranslation += expansionFraction * ambientState.getSectionPadding(); - } if (view instanceof FooterView) { final boolean shadeClosed = !ambientState.isShadeExpanded(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index dc71c10ed87cf..336cbdc8a63dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -510,7 +510,6 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mShowingKeyguardHeadsUp; private boolean mAllowExpandForSmallExpansion; private Runnable mExpandAfterLayoutRunnable; - private float mSectionPadding; /** * The padding between the start of notifications and the qs boundary on the lockscreen. @@ -589,7 +588,6 @@ public class NotificationPanelViewController extends PanelViewController { private NotificationShelfController mNotificationShelfController; private int mScrimCornerRadius; private int mScreenCornerRadius; - private int mNotificationScrimPadding; private boolean mQSAnimatingHiddenFromCollapsed; private final Executor mUiExecutor; @@ -824,6 +822,7 @@ public class NotificationPanelViewController extends PanelViewController { mOnHeightChangedListener); mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mOnOverscrollTopChangedListener); + mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); @@ -908,8 +907,6 @@ public class NotificationPanelViewController extends PanelViewController { mScrimCornerRadius = mResources.getDimensionPixelSize( R.dimen.notification_scrim_corner_radius); mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources); - mNotificationScrimPadding = mResources.getDimensionPixelSize( - R.dimen.notification_side_paddings); mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( R.dimen.notification_side_paddings); } @@ -2190,6 +2187,22 @@ public class NotificationPanelViewController extends PanelViewController { } }; + private void onNotificationScrolled(int newScrollPosition) { + // Since this is an overscroller, sometimes the scrollY can be temporarily negative + // (when overscrollng on the top and flinging). Let's + updateQSExpansionEnabled(); + } + + @Override + public void setIsShadeOpening(boolean opening) { + mAmbientState.setIsShadeOpening(opening); + updateQSExpansionEnabled(); + } + + private void updateQSExpansionEnabled() { + setQsExpansionEnabled(mAmbientState.getScrollY() <= 0 && !mAmbientState.isShadeOpening()); + } + /** * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade * and QS state. @@ -2203,7 +2216,6 @@ public class NotificationPanelViewController extends PanelViewController { final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction()); final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0) && !mShouldUseSplitNotificationShade; - setQsExpansionEnabled(mAmbientState.getScrollY() == 0 && !mAmbientState.isShadeOpening()); if (!mShouldUseSplitNotificationShade) { if (mTransitioningToFullShadeProgress > 0.0f) { @@ -2315,8 +2327,6 @@ public class NotificationPanelViewController extends PanelViewController { qsBottomY = (int) MathUtils.lerp( qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction); } - // to account for shade overshooting animation, see setSectionPadding method - if (mSectionPadding > 0) qsBottomY += mSectionPadding; return qsBottomY; } } @@ -2632,7 +2642,7 @@ public class NotificationPanelViewController extends PanelViewController { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) { - int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount()); + int minHeight = mQsMinExpansionHeight; min = Math.max(min, minHeight); } int maxHeight; @@ -2644,8 +2654,8 @@ public class NotificationPanelViewController extends PanelViewController { } maxHeight = Math.max(min, maxHeight); if (maxHeight == 0 || isNaN(maxHeight)) { - Log.wtf(TAG, "maxPanelHeight is invalid. getOverExpansionAmount(): " - + getOverExpansionAmount() + ", calculatePanelHeightQsExpanded: " + Log.wtf(TAG, "maxPanelHeight is invalid. mOverExpansion: " + + mOverExpansion + ", calculatePanelHeightQsExpanded: " + calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: " + calculatePanelHeightShade() + ", mStatusBarMinHeight = " + mStatusBarMinHeight + ", mQsMinExpansionHeight = " + mQsMinExpansionHeight); @@ -2807,23 +2817,6 @@ public class NotificationPanelViewController extends PanelViewController { return alpha; } - @Override - protected float getOverExpansionAmount() { - float result = mNotificationStackScrollLayoutController - .getCurrentOverScrollAmount(true /* top */); - if (isNaN(result)) { - Log.wtf(TAG, "OverExpansionAmount is NaN!"); - } - - return result; - } - - @Override - protected float getOverExpansionPixels() { - return mNotificationStackScrollLayoutController - .getCurrentOverScrolledPixels(true /* top */); - } - /** * Hides the header when notifications are colliding with it. */ @@ -3018,31 +3011,15 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - public void setSectionPadding(float padding) { - if (padding == mSectionPadding) { + public void setOverExpansion(float overExpansion) { + if (overExpansion == mOverExpansion) { return; } - mSectionPadding = padding; - // TODO(b/172289889) update overscroll to spec - } - - @Override - protected void setOverExpansion(float overExpansion, boolean isPixels) { - if (mConflictingQsExpansionGesture || mQsExpandImmediate) { - return; - } - if (mBarState != KEYGUARD) { - mNotificationStackScrollLayoutController.setOnHeightChangedListener(null); - if (isPixels) { - mNotificationStackScrollLayoutController.setOverScrolledPixels( - overExpansion, true /* onTop */, false /* animate */); - } else { - mNotificationStackScrollLayoutController.setOverScrollAmount( - overExpansion, true /* onTop */, false /* animate */); - } - mNotificationStackScrollLayoutController - .setOnHeightChangedListener(mOnHeightChangedListener); - } + super.setOverExpansion(overExpansion); + // Translating the quick settings by half the overexpansion to center it in the background + // frame + mQsFrame.setTranslationY(overExpansion / 2f); + mNotificationStackScrollLayoutController.setOverExpansion(overExpansion); } @Override @@ -3066,7 +3043,7 @@ public class NotificationPanelViewController extends PanelViewController { mFalsingCollector.onTrackingStopped(); super.onTrackingStopped(expand); if (expand) { - mNotificationStackScrollLayoutController.setOverScrolledPixels(0.0f, true /* onTop */, + mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */, true /* animate */); } mNotificationStackScrollLayoutController.onPanelTrackingStopped(); @@ -3111,18 +3088,6 @@ public class NotificationPanelViewController extends PanelViewController { || !isTracking()); } - @Override - protected boolean fullyExpandedClearAllVisible() { - return mNotificationStackScrollLayoutController.isFooterViewNotGone() - && mNotificationStackScrollLayoutController.isScrolledToBottom() - && !mQsExpandImmediate; - } - - @Override - protected boolean isClearAllVisible() { - return mNotificationStackScrollLayoutController.isFooterViewContentVisible(); - } - @Override protected boolean isTrackingBlocked() { return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; @@ -3973,10 +3938,15 @@ public class NotificationPanelViewController extends PanelViewController { } mLastOverscroll = 0f; mQsExpansionFromOverscroll = false; + if (open) { + // During overscrolling, qsExpansion doesn't actually change that the qs is + // becoming expanded. Any layout could therefore reset the position again. Let's + // make sure we can expand + setOverScrolling(false); + } setQsExpansion(mQsExpansionHeight); flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> { - mStackScrollerOverscrolling = false; setOverScrolling(false); updateQsState(); }, false /* isClick */); @@ -4392,7 +4362,7 @@ public class NotificationPanelViewController extends PanelViewController { if (mQsMaxExpansionHeight != oldMaxHeight) { startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); } - } else if (!mQsExpanded) { + } else if (!mQsExpanded && mQsExpansionAnimator == null) { setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); } updateExpandedHeight(getExpandedHeight()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 64e2c1c5d268b..f1b6c7c3ad193 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -53,7 +53,7 @@ public abstract class PanelBar extends FrameLayout { if (DEBUG) LOG("go state: %d -> %d", mState, state); mState = state; if (mPanel != null) { - mPanel.getAmbientState().setIsShadeOpening(state == STATE_OPENING); + mPanel.setIsShadeOpening(state == STATE_OPENING); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 798e8953b1704..323a1128d3bbc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -68,12 +68,14 @@ public abstract class PanelViewController { public static final String TAG = PanelView.class.getSimpleName(); private static final int NO_FIXED_DURATION = -1; private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; - private static final long SHADE_OPEN_SPRING_BACK_DURATION = 200L; - private static final float MIN_OVERSCROLL = -50; - private static final float MAX_OVERSCROLL = 30; + private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; + + /** + * The factor of the usual high velocity that is needed in order to reach the maximum overshoot + * when flinging. A low value will make it that most flings will reach the maximum overshoot. + */ + private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f; - private float mFlingTarget; - private float mFlingVelocity; protected long mDownTime; protected boolean mTouchSlopExceededBeforeDown; private float mMinExpandHeight; @@ -83,6 +85,22 @@ public abstract class PanelViewController { protected boolean mIsLaunchAnimationRunning; private int mFixedDuration = NO_FIXED_DURATION; protected ArrayList mExpansionListeners = new ArrayList<>(); + protected float mOverExpansion; + + /** + * The overshoot amount when the panel flings open + */ + private float mPanelFlingOvershootAmount; + + /** + * The amount of pixels that we have overexpanded the last time with a gesture + */ + private float mLastGesturedOverExpansion = -1; + + /** + * Is the current animator the spring back animation? + */ + private boolean mIsSpringBackAnimation; private void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); @@ -254,6 +272,7 @@ public abstract class PanelViewController { mTouchSlop = configuration.getScaledTouchSlop(); mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); + mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); mUnlockFalsingThreshold = mResources.getDimensionPixelSize( R.dimen.unlock_falsing_threshold); } @@ -529,21 +548,38 @@ public abstract class PanelViewController { protected void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { - if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { + if (target == mExpandedHeight && mOverExpansion == 0.0f) { + // We're at the target and didn't fling and there's no overshoot endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); mKeyguardStateController.notifyPanelFlingEnd(); notifyExpandingFinished(); return; } mIsFlinging = true; - mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; - ValueAnimator animator = createHeightAnimator(target); - mFlingTarget = target; + // we want to perform an overshoot animation when flinging open + final boolean addOverscroll = expand + && mStatusBarStateController.getState() != StatusBarState.KEYGUARD + && mOverExpansion == 0.0f + && vel >= 0; + final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); + float overshootAmount = 0.0f; + if (addOverscroll) { + // Let's overshoot depending on the amount of velocity + overshootAmount = MathUtils.lerp( + 0.2f, + 1.0f, + MathUtils.saturate(vel + / (mFlingAnimationUtils.getHighVelocityPxPerSecond() + * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT))); + overshootAmount += mOverExpansion / mPanelFlingOvershootAmount; + } + ValueAnimator animator = createHeightAnimator(target, overshootAmount); if (expand) { if (expandBecauseOfFalsing && vel < 0) { vel = 0; } - mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight()); + mFlingAnimationUtils.apply(animator, mExpandedHeight, + target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight()); if (vel == 0) { animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); } @@ -570,7 +606,6 @@ public abstract class PanelViewController { animator.setDuration(mFixedDuration); } } - mFlingVelocity = vel; animator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @@ -586,7 +621,7 @@ public abstract class PanelViewController { @Override public void onAnimationEnd(Animator animation) { - if (expand && mFlingVelocity > 0) { + if (shouldSpringBack && !mCancelled) { // After the shade is flinged open to an overscrolled state, spring back // the shade by reducing section padding to 0. springBack(); @@ -600,14 +635,19 @@ public abstract class PanelViewController { } private void springBack() { - ValueAnimator animator = ValueAnimator.ofFloat(MAX_OVERSCROLL, 0); + if (mOverExpansion == 0) { + onFlingEnd(false /* cancelled */); + return; + } + mIsSpringBackAnimation = true; + ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0); animator.addUpdateListener( animation -> { - setSectionPadding((float) animation.getAnimatedValue()); - setExpandedHeightInternal(mFlingTarget); + setOverExpansionInternal((float) animation.getAnimatedValue(), + false /* isFromGesture */); }); animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); - animator.setInterpolator(Interpolators.LINEAR); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); animator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @Override @@ -616,6 +656,7 @@ public abstract class PanelViewController { } @Override public void onAnimationEnd(Animator animation) { + mIsSpringBackAnimation = false; onFlingEnd(mCancelled); } }); @@ -625,6 +666,8 @@ public abstract class PanelViewController { private void onFlingEnd(boolean cancelled) { mIsFlinging = false; + // No overshoot when the animation ends + setOverExpansionInternal(0, false /* isFromGesture */); setAnimator(null); mKeyguardStateController.notifyPanelFlingEnd(); if (!cancelled) { @@ -644,7 +687,7 @@ public abstract class PanelViewController { public void setExpandedHeight(float height) { if (DEBUG) logf("setExpandedHeight(%.1f)", height); - setExpandedHeightInternal(height + getOverExpansionPixels()); + setExpandedHeightInternal(height); } protected void requestPanelHeightUpdate() { @@ -662,7 +705,7 @@ public abstract class PanelViewController { return; } - if (mHeightAnimator != null) { + if (mHeightAnimator != null && !mIsSpringBackAnimation) { mPanelUpdateWhenAnimatorEnds = true; return; } @@ -677,38 +720,24 @@ public abstract class PanelViewController { return stackHeightFraction; } - // When the shade is flinged open, add space before sections for overscroll effect. - private void maybeOverScrollForShadeFlingOpen(float height) { - if (!mBar.isShadeOpening() || mFlingVelocity <= 0) { - return; - } - final float padding = MathUtils.lerp( - MIN_OVERSCROLL, MAX_OVERSCROLL, getStackHeightFraction(height)); - setSectionPadding(padding); - } - public void setExpandedHeightInternal(float h) { if (isNaN(h)) { Log.wtf(TAG, "ExpandedHeight set to NaN"); } - maybeOverScrollForShadeFlingOpen(h); if (mExpandLatencyTracking && h != 0f) { DejankUtils.postAfterTraversal( () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); mExpandLatencyTracking = false; } - float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); + float maxPanelHeight = getMaxPanelHeight(); if (mHeightAnimator == null) { - float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); - if (getOverExpansionPixels() != overExpansionPixels && mTracking) { - setOverExpansion(overExpansionPixels, true /* isPixels */); + if (mTracking) { + float overExpansionPixels = Math.max(0, h - maxPanelHeight); + setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); } - mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); + mExpandedHeight = Math.min(h, maxPanelHeight); } else { mExpandedHeight = h; - if (mOverExpandedBeforeFling) { - setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); - } } // If we are closing the panel and we are almost there due to a slow decelerating @@ -720,7 +749,7 @@ public abstract class PanelViewController { } } mExpandedFraction = Math.min(1f, - fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion); + maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); onHeightUpdated(mExpandedHeight); notifyBarPanelExpansionChanged(); } @@ -731,16 +760,31 @@ public abstract class PanelViewController { */ protected abstract boolean isTrackingBlocked(); - protected abstract void setSectionPadding(float padding); + protected void setOverExpansion(float overExpansion) { + mOverExpansion = overExpansion; + } - protected abstract void setOverExpansion(float overExpansion, boolean isPixels); + /** + * Set the current overexpansion + * + * @param overExpansion the amount of overexpansion to apply + * @param isFromGesture is this amount from a gesture and needs to be rubberBanded? + */ + private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) { + if (!isFromGesture) { + mLastGesturedOverExpansion = -1; + setOverExpansion(overExpansion); + } else if (mLastGesturedOverExpansion != overExpansion) { + mLastGesturedOverExpansion = overExpansion; + final float heightForFullOvershoot = mView.getHeight() / 3.0f; + float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot); + newExpansion = Interpolators.getOvershootInterpolation(newExpansion); + setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f); + } + } protected abstract void onHeightUpdated(float expandedHeight); - protected abstract float getOverExpansionAmount(); - - protected abstract float getOverExpansionPixels(); - /** * This returns the maximum height of the panel. Children should override this if their * desired height is not the full height. @@ -977,9 +1021,32 @@ public abstract class PanelViewController { } private ValueAnimator createHeightAnimator(float targetHeight) { + return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */); + } + + /** + * Create an animator that can also overshoot + * + * @param targetHeight the target height + * @param overshootAmount the amount of overshoot desired + */ + private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) { + float startExpansion = mOverExpansion; ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); animator.addUpdateListener( - animation -> setExpandedHeightInternal((float) animation.getAnimatedValue())); + animation -> { + if (overshootAmount > 0.0f + // Also remove the overExpansion when collapsing + || (targetHeight == 0.0f && startExpansion != 0)) { + final float expansion = MathUtils.lerp( + startExpansion, + mPanelFlingOvershootAmount * overshootAmount, + Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + animator.getAnimatedFraction())); + setOverExpansionInternal(expansion, false /* isFromGesture */); + } + setExpandedHeightInternal((float) animation.getAnimatedValue()); + }); return animator; } @@ -989,7 +1056,7 @@ public abstract class PanelViewController { mExpandedFraction, mExpandedFraction > 0f || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp() || mTracking - || mHeightAnimator != null); + || mHeightAnimator != null && !mIsSpringBackAnimation); } for (int i = 0; i < mExpansionListeners.size(); i++) { mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking); @@ -1037,14 +1104,6 @@ public abstract class PanelViewController { public abstract void resetViews(boolean animate); - - /** - * @return whether "Clear all" button will be visible when the panel is fully expanded - */ - protected abstract boolean fullyExpandedClearAllVisible(); - - protected abstract boolean isClearAllVisible(); - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; } @@ -1080,6 +1139,11 @@ public abstract class PanelViewController { return new OnConfigurationChangedListener(); } + /** + * Set that the panel is currently opening and not fully opened or closed. + */ + public abstract void setIsShadeOpening(boolean opening); + public class TouchHandler implements View.OnTouchListener { public boolean onInterceptTouchEvent(MotionEvent event) { if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted @@ -1108,7 +1172,7 @@ public abstract class PanelViewController { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mStatusBar.userActivity(); - mAnimatingOnDown = mHeightAnimator != null; + mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; mMinExpandHeight = 0.0f; mDownTime = SystemClock.uptimeMillis(); if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { @@ -1227,10 +1291,10 @@ public abstract class PanelViewController { mCollapsedAndHeadsUpOnDown = isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); addMovement(event); - if (!mGestureWaitForTouchSlop || (mHeightAnimator != null - && !mHintAnimationRunning)) { - mTouchSlopExceeded = - (mHeightAnimator != null && !mHintAnimationRunning) + boolean regularHeightAnimationRunning = mHeightAnimator != null + && !mHintAnimationRunning && !mIsSpringBackAnimation; + if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) { + mTouchSlopExceeded = regularHeightAnimationRunning || mTouchSlopExceededBeforeDown; cancelHeightAnimator(); onTrackingStarted(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index ffb53a8b2e11c..4b5657afe02fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -638,8 +638,6 @@ public class NotificationPanelViewTest extends SysuiTestCase { public void testCancelSwipeWhileLocked_notifiesKeyguardState() { mStatusBarStateController.setState(KEYGUARD); - mNotificationPanelViewController.setOverExpansion(100f, true); - // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that // the fling occurred and did not dismiss the keyguard. mNotificationPanelViewController.flingToHeight(