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 b5963ef443e79..a12179cdb0f86 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 @@ -78,6 +78,7 @@ public class AmbientState { private ExpandableNotificationRow mTrackedHeadsUpRow; private float mAppearFraction; private boolean mIsShadeOpening; + private float mSectionPadding; /** Tracks the state from AlertingNotificationManager#hasNotifications() */ private boolean mHasAlertEntries; @@ -105,6 +106,14 @@ public class AmbientState { return mIsShadeOpening; } + void setSectionPadding(float padding) { + mSectionPadding = padding; + } + + float getSectionPadding() { + return mSectionPadding; + } + private static int getZDistanceBetweenElements(Context context) { return Math.max(1, context.getResources() .getDimensionPixelSize(R.dimen.z_distance_between_notifications)); 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 6abbc6bc2c368..f07d8740c3e90 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 @@ -554,6 +554,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.setIsShadeOpening(isOpening); } + void setSectionPadding(float margin) { + mAmbientState.setSectionPadding(margin); + requestChildrenUpdate(); + } + @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onFinishInflate() { 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 5126f5557b7ae..879dad8dbb923 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 @@ -275,6 +275,10 @@ public class NotificationStackScrollLayoutController { mView.setIsShadeOpening(isOpening); } + public void setSectionPadding(float padding) { + mView.setSectionPadding(padding); + } + private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { @Override public void onMenuClicked( 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 2c206b1c3f453..e6efba7ca28bb 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 @@ -341,7 +341,8 @@ public class StackScrollAlgorithm { boolean isEmptyShadeView = child instanceof EmptyShadeView; childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; - float inset = ambientState.getTopPadding() + ambientState.getStackTranslation(); + float inset = ambientState.getTopPadding() + ambientState.getStackTranslation() + + ambientState.getSectionPadding(); if (i <= algorithmState.getIndexOfExpandingNotification()) { inset += ambientState.getExpandAnimationTopChange(); } @@ -563,6 +564,10 @@ public class StackScrollAlgorithm { childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart); } childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart); + if (child instanceof SectionHeaderView) { + // Add padding before sections for overscroll effect. + childViewState.yTranslation += ambientState.getSectionPadding(); + } if (childViewState.yTranslation >= shelfStart) { childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild(); childViewState.inShelf = true; 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 3db6c80cf4ff8..3e0978435e28a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -477,6 +477,7 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mShowingKeyguardHeadsUp; private boolean mAllowExpandForSmallExpansion; private Runnable mExpandAfterLayoutRunnable; + private float mSectionPadding; /** * Is this a collapse that started on the panel where we should allow the panel to intercept @@ -2415,6 +2416,16 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setIsShadeOpening(isOpening); } + @Override + public void setSectionPadding(float padding) { + if (padding == mSectionPadding) { + return; + } + mSectionPadding = padding; + mQsFrame.setTranslationY(padding); + mNotificationStackScrollLayoutController.setSectionPadding(padding); + } + @Override protected void setOverExpansion(float overExpansion, boolean isPixels) { if (mConflictingQsExpansionGesture || mQsExpandImmediate) { @@ -2501,19 +2512,6 @@ public class NotificationPanelViewController extends PanelViewController { } } - @Override - protected boolean shouldExpandToTopOfClearAll(float targetHeight) { - boolean perform = super.shouldExpandToTopOfClearAll(targetHeight); - if (!perform) { - return false; - } - // Let's make sure we're not appearing but the animation will end below the appear. - // Otherwise quick settings would jump at the end of the animation. - float fraction = mNotificationStackScrollLayoutController - .calculateAppearFraction(targetHeight); - return fraction >= 1.0f; - } - @Override protected boolean shouldUseDismissingAnimation() { return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen() @@ -2532,11 +2530,6 @@ public class NotificationPanelViewController extends PanelViewController { return mNotificationStackScrollLayoutController.isFooterViewContentVisible(); } - @Override - protected int getClearAllHeightWithPadding() { - return mNotificationStackScrollLayoutController.getFooterViewHeightWithPadding(); - } - @Override protected boolean isTrackingBlocked() { return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; 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 a1112dc37b50a..bee0cb1260314 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -57,6 +57,10 @@ public abstract class PanelBar extends FrameLayout { } } + protected boolean isShadeOpening() { + return mState == STATE_OPENING; + } + @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); 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 da82986fe6b19..3031b8a87ab24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -32,6 +32,7 @@ import android.content.res.Resources; import android.os.SystemClock; import android.os.VibrationEffect; import android.util.Log; +import android.util.MathUtils; import android.view.InputDevice; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -67,6 +68,13 @@ public abstract class PanelViewController { private static final int INITIAL_OPENING_PEEK_DURATION = 200; private static final int PEEK_ANIMATION_DURATION = 360; 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 float mFlingTarget; + private float mFlingVelocity; protected long mDownTime; protected boolean mTouchSlopExceededBeforeDown; private float mMinExpandHeight; @@ -569,14 +577,6 @@ public abstract class PanelViewController { protected void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { - // Hack to make the expand transition look nice when clear all button is visible - we make - // the animation only to the last notification, and then jump to the maximum panel height so - // clear all just fades in and the decelerating motion is towards the last notification. - final boolean clearAllExpandHack = expand && - shouldExpandToTopOfClearAll(getMaxPanelHeight() - getClearAllHeightWithPadding()); - if (clearAllExpandHack) { - target = getMaxPanelHeight() - getClearAllHeightWithPadding(); - } if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); notifyExpandingFinished(); @@ -584,13 +584,14 @@ public abstract class PanelViewController { } mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; ValueAnimator animator = createHeightAnimator(target); + mFlingTarget = target; if (expand) { if (expandBecauseOfFalsing && vel < 0) { vel = 0; } mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight()); if (vel == 0) { - animator.setDuration(350); + animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); } } else { if (shouldUseDismissingAnimation()) { @@ -615,6 +616,7 @@ public abstract class PanelViewController { animator.setDuration(mFixedDuration); } } + mFlingVelocity = vel; animator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @@ -625,38 +627,54 @@ public abstract class PanelViewController { @Override public void onAnimationEnd(Animator animation) { - if (clearAllExpandHack && !mCancelled) { - setExpandedHeightInternal(getMaxPanelHeight()); - } - setAnimator(null); - if (!mCancelled) { - InteractionJankMonitor.getInstance() - .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); - notifyExpandingFinished(); + if (expand && mFlingVelocity > 0) { + // After the shade is flinged open to an overscrolled state, spring back + // the shade by reducing section padding to 0. + springBack(); } else { - InteractionJankMonitor.getInstance() - .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + onFlingEnd(mCancelled); } - notifyBarPanelExpansionChanged(); } }); setAnimator(animator); animator.start(); } - /** - * When expanding, should we expand to the top of clear all and expand immediately? - * This will make sure that the animation will stop smoothly at the end of the last notification - * before the clear all affordance. - * - * @param targetHeight the height that we would animate to, right above clear all - * - * @return true if we can expand to the top of clear all - */ - protected boolean shouldExpandToTopOfClearAll(float targetHeight) { - return fullyExpandedClearAllVisible() - && mExpandedHeight < targetHeight - && !isClearAllVisible(); + private void springBack() { + ValueAnimator animator = ValueAnimator.ofFloat(MAX_OVERSCROLL, 0); + animator.addUpdateListener( + animation -> { + setSectionPadding((float) animation.getAnimatedValue()); + setExpandedHeightInternal(mFlingTarget); + }); + animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); + animator.setInterpolator(Interpolators.LINEAR); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + @Override + public void onAnimationEnd(Animator animation) { + onFlingEnd(mCancelled); + } + }); + setAnimator(animator); + animator.start(); + } + + private void onFlingEnd(boolean cancelled) { + setAnimator(null); + if (!cancelled) { + InteractionJankMonitor.getInstance() + .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + notifyExpandingFinished(); + } else { + InteractionJankMonitor.getInstance() + .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + notifyBarPanelExpansionChanged(); } protected abstract boolean shouldUseDismissingAnimation(); @@ -697,10 +715,28 @@ public abstract class PanelViewController { setExpandedHeight(currentMaxPanelHeight); } + private float getStackHeightFraction(float height) { + final float gestureFraction = height / getMaxPanelHeight(); + final float stackHeightFraction = Interpolators.ACCELERATE_DECELERATE + .getInterpolation(gestureFraction); + 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)); @@ -742,6 +778,8 @@ public abstract class PanelViewController { protected abstract void setIsShadeOpening(boolean isShadeOpening); + protected abstract void setSectionPadding(float padding); + protected abstract void setOverExpansion(float overExpansion, boolean isPixels); protected abstract void onHeightUpdated(float expandedHeight); @@ -1075,11 +1113,6 @@ public abstract class PanelViewController { protected abstract boolean isClearAllVisible(); - /** - * @return the height of the clear all button, in pixels including padding - */ - protected abstract int getClearAllHeightWithPadding(); - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; }