From a21cbcdf0381d81fc76111004b43b65213426274 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Fri, 11 Jun 2021 10:11:50 +0200 Subject: [PATCH 1/2] When swiping notifications it doesn't clip early anymore Previously notifications started to clip before reaching the edge. We're now changinging the flags to dismiss with translation instead of clipping the contents. This is change 1/2 as the notification container should clip the notification now, which is especially noticeable in split shade. Bug: 181887261 Test: dismiss notifications, visually observe no clipping Change-Id: I003b1486ce64dd127a90ca2d9f982230eba79f60 --- packages/SystemUI/res/values/config.xml | 4 - .../src/com/android/systemui/SwipeHelper.java | 13 +++- .../notification/NotificationClicker.java | 4 +- .../row/ExpandableNotificationRow.java | 62 +++++++++++----- .../row/ExpandableOutlineView.java | 36 +++++++-- .../stack/NotificationStackScrollLayout.java | 73 ++++++++++++++++--- ...tificationStackScrollLayoutController.java | 5 ++ .../stack/NotificationSwipeHelper.java | 12 +++ .../stack/StackStateAnimator.java | 2 +- .../row/ExpandableNotificationRowTest.java | 1 + 10 files changed, 166 insertions(+), 46 deletions(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b4deaa0af543f..82ce881b2fbc1 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -396,10 +396,6 @@ false - - true - false diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index b0f4da251208e..affad7a57d868 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -391,9 +391,9 @@ public class SwipeHelper implements Gefingerpoken { boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) || (getTranslation(animView) < 0 && !isDismissAll); if (animateLeft || animateLeftForRtl || animateUpForMenu) { - newPos = -getSize(animView); + newPos = -getTotalTranslationLength(animView); } else { - newPos = getSize(animView); + newPos = getTotalTranslationLength(animView); } long duration; if (fixedDuration == 0) { @@ -469,6 +469,15 @@ public class SwipeHelper implements Gefingerpoken { anim.start(); } + /** + * Get the total translation length where we want to swipe to when dismissing the view. By + * default this is the size of the view, but can also be larger. + * @param animView the view to ask about + */ + protected float getTotalTranslationLength(View animView) { + return getSize(animView); + } + /** * Called to update the dismiss animation. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index aef01e9bd8114..0fb1c54bb1500 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -71,11 +71,11 @@ public final class NotificationClicker implements View.OnClickListener { // Check if the notification is displaying the menu, if so slide notification back if (isMenuVisible(row)) { mLogger.logMenuVisible(entry); - row.animateTranslateNotification(0); + row.animateResetTranslation(); return; } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { mLogger.logParentMenuVisible(entry); - row.getNotificationParent().animateTranslateNotification(0); + row.getNotificationParent().animateResetTranslation(); return; } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { // We never want to open the app directly if the user clicks in between diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index d5acf2d630257..ba28dc59def4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -874,7 +874,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected boolean handleSlideBack() { if (mMenuRow != null && mMenuRow.isMenuVisible()) { - animateTranslateNotification(0 /* targetLeft */); + animateResetTranslation(); return true; } return false; @@ -1711,21 +1711,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); mChildrenContainer.onNotificationUpdated(); - if (mShouldTranslateContents) { - mTranslateableViews.add(mChildrenContainer); - } + mTranslateableViews.add(mChildrenContainer); }); - if (mShouldTranslateContents) { - // Add the views that we translate to reveal the menu - mTranslateableViews = new ArrayList<>(); - for (int i = 0; i < getChildCount(); i++) { - mTranslateableViews.add(getChildAt(i)); - } - // Remove views that don't translate - mTranslateableViews.remove(mChildrenContainerStub); - mTranslateableViews.remove(mGutsStub); + // Add the views that we translate to reveal the menu + mTranslateableViews = new ArrayList<>(); + for (int i = 0; i < getChildCount(); i++) { + mTranslateableViews.add(getChildAt(i)); } + // Remove views that don't translate + mTranslateableViews.remove(mChildrenContainerStub); + mTranslateableViews.remove(mGutsStub); } private void doLongClickCallback() { @@ -1803,7 +1799,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mTranslateAnim.cancel(); } - if (!mShouldTranslateContents) { + if (mDismissUsingRowTranslationX) { setTranslationX(0); } else if (mTranslateableViews != null) { for (int i = 0; i < mTranslateableViews.size(); i++) { @@ -1865,23 +1861,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getActiveRemoteInputText(); } - public void animateTranslateNotification(final float leftTarget) { + /** + * Reset the translation with an animation. + */ + public void animateResetTranslation() { if (mTranslateAnim != null) { mTranslateAnim.cancel(); } - mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); + mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */); if (mTranslateAnim != null) { mTranslateAnim.start(); } } + /** + * Set the dismiss behavior of the view. + * @param usingRowTranslationX {@code true} if the view should translate using regular + * translationX, otherwise the contents will be + * translated. + */ + @Override + public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { + if (usingRowTranslationX != mDismissUsingRowTranslationX) { + // In case we were already transitioning, let's switch over! + float previousTranslation = getTranslation(); + if (previousTranslation != 0) { + setTranslation(0); + } + super.setDismissUsingRowTranslationX(usingRowTranslationX); + if (previousTranslation != 0) { + setTranslation(previousTranslation); + } + } + } + @Override public void setTranslation(float translationX) { invalidate(); if (isBlockingHelperShowingAndTranslationFinished()) { mGuts.setTranslationX(translationX); return; - } else if (!mShouldTranslateContents) { + } else if (mDismissUsingRowTranslationX) { setTranslationX(translationX); } else if (mTranslateableViews != null) { // Translate the group of views @@ -1905,7 +1925,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public float getTranslation() { - if (!mShouldTranslateContents) { + if (mDismissUsingRowTranslationX) { return getTranslationX(); } @@ -2896,7 +2916,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView float y = event.getY(); NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader(); - if (header != null && header.isInTouchRect(x - getTranslation(), y)) { + // the extra translation only needs to be added, if we're translating the notification + // contents, otherwise the motionEvent is already at the right place due to the + // touch event system. + float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0; + if (header != null && header.isInTouchRect(x - translation, y)) { return true; } if ((!mIsSummaryWithChildren || shouldShowPublic()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index a7035229bf5f9..d58fe3b3c4a3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -71,10 +71,10 @@ public abstract class ExpandableOutlineView extends ExpandableView { private int mBackgroundTop; /** - * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when + * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself. */ - protected boolean mShouldTranslateContents; + protected boolean mDismissUsingRowTranslationX = true; private float[] mTmpCornerRadii = new float[8]; private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @@ -82,7 +82,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { public void getOutline(View view, Outline outline) { if (!mCustomOutline && getCurrentTopRoundness() == 0.0f && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) { - int translation = mShouldTranslateContents ? (int) getTranslation() : 0; + // Only when translating just the contents, does the outline need to be shifted. + int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0; int left = Math.max(translation, 0); int top = mClipTopAmount + mBackgroundTop; int right = getWidth() + Math.min(translation, 0); @@ -107,7 +108,9 @@ public abstract class ExpandableOutlineView extends ExpandableView { float topRoundness = mAlwaysRoundBothCorners ? mOutlineRadius : getCurrentBackgroundRadiusTop(); if (!mCustomOutline) { - int translation = mShouldTranslateContents && !ignoreTranslation + // The outline just needs to be shifted if we're translating the contents. Otherwise + // it's already in the right place. + int translation = !mDismissUsingRowTranslationX && !ignoreTranslation ? (int) getTranslation() : 0; int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); left = Math.max(translation, 0) - halfExtraWidth; @@ -196,13 +199,14 @@ public abstract class ExpandableOutlineView extends ExpandableView { } protected boolean isClippingNeeded() { - return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ; + // When translating the contents instead of the overall view, we need to make sure we clip + // rounded to the contents. + boolean forTranslation = getTranslation() != 0 && !mDismissUsingRowTranslationX; + return mAlwaysRoundBothCorners || mCustomOutline || forTranslation; } private void initDimens() { Resources res = getResources(); - mShouldTranslateContents = - res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe); mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius); mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); if (!mAlwaysRoundBothCorners) { @@ -342,9 +346,25 @@ public abstract class ExpandableOutlineView extends ExpandableView { } } + /** + * Set the dismiss behavior of the view. + * @param usingRowTranslationX {@code true} if the view should translate using regular + * translationX, otherwise the contents will be + * translated. + */ + public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { + mDismissUsingRowTranslationX = usingRowTranslationX; + } + @Override public int getOutlineTranslation() { - return mCustomOutline ? mOutlineRect.left : (int) getTranslation(); + if (mCustomOutline) { + return mOutlineRect.left; + } + if (mDismissUsingRowTranslationX) { + return 0; + } + return (int) getTranslation(); } public void updateOutline() { 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 955aac3775fcb..4bfa03b6982a2 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 @@ -475,6 +475,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Are we launching a notification right now */ private boolean mLaunchingNotification; + + /** + * Do notifications dismiss with normal transitioning + */ + private boolean mDismissUsingRowTranslationX = true; private NotificationEntry mTopHeadsUpEntry; private long mNumHeadsUp; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; @@ -528,7 +533,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSectionsManager = notificationSectionsManager; mFeatureFlags = featureFlags; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res); + updateSplitNotificationShade(); mSectionsManager.initialize(this, LayoutInflater.from(context)); mSections = mSectionsManager.createSectionsForBuckets(); @@ -1600,8 +1605,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Resources res = getResources(); - mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res); - updateUseRoundedRectClipping(); + updateSplitNotificationShade(); mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); float densityScale = res.getDisplayMetrics().density; mSwipeHelper.setDensityScale(densityScale); @@ -2528,8 +2532,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateScrollStateForRemovedChild(child); boolean animationGenerated = generateRemoveAnimation(child); if (animationGenerated) { - if (!mSwipedOutViews.contains(child) - || Math.abs(child.getTranslation()) != child.getWidth()) { + if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { container.addTransientView(child, 0); child.setTransientContainer(container); } @@ -2541,6 +2544,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable focusNextViewIfFocused(child); } + /** + * Has this view been fully swiped out such that it's not visible anymore. + */ + public boolean isFullySwipedOut(ExpandableView child) { + return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child)); + } + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void focusNextViewIfFocused(View view) { if (view instanceof ExpandableNotificationRow) { @@ -2759,7 +2769,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateAnimationState(child); updateChronometerForChild(child); if (child instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl); + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.setDismissRtl(mDismissRtl); + row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); + } } @@ -3033,7 +3046,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable removedTranslation = row.getTranslationWhenRemoved(); ignoreChildren = false; } - childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth(); + childWasSwipedOut |= isFullySwipedOut(row); } else if (child instanceof MediaHeaderView) { childWasSwipedOut = true; } @@ -3041,11 +3054,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable Rect clipBounds = child.getClipBounds(); childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; - if (childWasSwipedOut && child instanceof ExpandableView) { + if (childWasSwipedOut) { // Clean up any potential transient views if the child has already been swiped // out, as we won't be animating it further (due to its height already being // clipped to 0. - ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer(); + ViewGroup transientContainer = child.getTransientContainer(); if (transientContainer != null) { transientContainer.removeTransientView(child); } @@ -4051,7 +4064,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable expandableView.setFakeShadowIntensity( diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, previous.getOutlineAlpha(), (int) yLocation, - previous.getOutlineTranslation()); + (int) (previous.getOutlineTranslation() + previous.getTranslation())); } previous = expandableView; } @@ -4636,6 +4649,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mStatusBarState = statusBarState; mAmbientState.setStatusBarState(statusBarState); updateSpeedBumpIndex(); + updateDismissBehavior(); } void onStatePostChange(boolean fromShadeLocked) { @@ -5224,6 +5238,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } + private void updateSplitNotificationShade() { + boolean split = shouldUseSplitNotificationShade(mFeatureFlags, getResources()); + if (split != mShouldUseSplitNotificationShade) { + mShouldUseSplitNotificationShade = split; + updateDismissBehavior(); + updateUseRoundedRectClipping(); + } + } + + private void updateDismissBehavior() { + // On the split keyguard, dismissing with clipping without a visual boundary looks odd, + // so let's use the content dismiss behavior instead. + boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade + || mStatusBarState != StatusBarState.KEYGUARD; + if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) { + mDismissUsingRowTranslationX = dismissUsingRowTranslationX; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissUsingRowTranslationX( + dismissUsingRowTranslationX); + } + } + } + } + /** * Set if we're launching a notification right now. */ @@ -5258,6 +5298,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable super.dispatchDraw(canvas); } + /** + * Calculate the total translation needed when dismissing. + */ + public float getTotalTranslationLength(View animView) { + if (!mDismissUsingRowTranslationX) { + return animView.getMeasuredWidth(); + } + float notificationWidth = animView.getMeasuredWidth(); + int containerWidth = getMeasuredWidth(); + float padding = (containerWidth - notificationWidth) / 2.0f; + return containerWidth - padding; + } + /** * 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 1e42a2c5ddf34..0f8fc14f9d36e 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 @@ -383,6 +383,11 @@ public class NotificationStackScrollLayoutController { false /* resetMenu */); } + @Override + public float getTotalTranslationLength(View animView) { + return mView.getTotalTranslationLength(animView); + } + @Override public void onSnooze(StatusBarNotification sbn, NotificationSwipeActionHelper.SnoozeOption snoozeOption) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index f4c4d440b0634..664776975b240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -324,6 +324,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc } } + @Override + protected float getTotalTranslationLength(View animView) { + return mCallback.getTotalTranslationLength(animView); + } + @Override public void setTranslation(View v, float translate) { if (v instanceof SwipeableView) { @@ -466,6 +471,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption); void onDismiss(); + + /** + * Get the total translation length where we want to swipe to when dismissing the view. By + * default this is the size of the view, but can also be larger. + * @param animView the view to ask about + */ + float getTotalTranslationLength(View animView); } static class Builder { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 4fd2064b394d1..ee12b4b2d728c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -392,7 +392,7 @@ public class StackStateAnimator { 0, () -> removeTransientView(changingView), null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (Math.abs(changingView.getTranslation()) == changingView.getWidth() + if (mHostLayout.isFullySwipedOut(changingView) && changingView.getTransientContainer() != null) { changingView.getTransientContainer().removeTransientView(changingView); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 2e0827f24bf84..fa25c3f1e0053 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -289,6 +289,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testIconScrollXAfterTranslationAndReset() throws Exception { + mGroupRow.setDismissUsingRowTranslationX(false); mGroupRow.setTranslation(50); assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); From 982d5430b8469bf45bbaa5d6009d04e08cf2aac4 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Mon, 14 Jun 2021 14:09:06 +0200 Subject: [PATCH 2/2] Animating when the qs boundary changes due to notification interactions When dismissing notifications, the logic how the scrolling was adapted wasn't updated yet, leading to accidental scrolls after dismisses. Additionally, we're now animating these changes properly when the boundary changes for these events. Fixes: 188439802 Test: have a couple of notifications, scroll, dismiss the first notification, observe nice animation Change-Id: Ic4faec910a7ca82fd283ab2053ddb4222668a161 --- .../stack/NotificationStackScrollLayout.java | 71 ++++++++++++++++--- ...tificationStackScrollLayoutController.java | 2 +- .../NotificationPanelViewController.java | 10 +-- 3 files changed, 70 insertions(+), 13 deletions(-) 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 4bfa03b6982a2..d79c57565dcdc 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 @@ -425,13 +425,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final Rect mBackgroundAnimationRect = new Rect(); private ArrayList> mExpandedHeightListeners = new ArrayList<>(); private int mHeadsUpInset; + + /** + * The position of the scroll boundary relative to this view. This is where the notifications + * stop scrolling and will start to clip instead. + */ + private int mQsScrollBoundaryPosition; private HeadsUpAppearanceController mHeadsUpAppearanceController; private final Rect mTmpRect = new Rect(); private DismissListener mDismissListener; private DismissAllAnimationListener mDismissAllAnimationListener; private NotificationRemoteInputManager mRemoteInputManager; private ShadeController mShadeController; - private Runnable mOnStackYChanged; + private Consumer mOnStackYChanged; protected boolean mClearAllEnabled; @@ -471,6 +477,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mRoundedRectClippingRight; private float[] mBgCornerRadii = new float[8]; + /** + * Whether stackY should be animated in case the view is getting shorter than the scroll + * position and this scrolling will lead to the top scroll inset getting smaller. + */ + private boolean mAnimateStackYForContentHeightChange = false; + /** * Are we launching a notification right now */ @@ -889,6 +901,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mCornerRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); + mQsScrollBoundaryPosition = res.getDimensionPixelSize( + com.android.internal.R.dimen.quick_qs_offset_height); } void updateCornerRadius() { @@ -988,6 +1002,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateFirstAndLastBackgroundViews(); updateAlgorithmLayoutMinHeight(); updateOwnTranslationZ(); + + // Once the layout has finished, we don't need to animate any scrolling clampings anymore. + mAnimateStackYForContentHeightChange = false; } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -1122,7 +1139,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void clampScrollPosition() { int scrollRange = getScrollRange(); if (scrollRange < mOwnScrollY) { - setOwnScrollY(scrollRange); + boolean animateStackY = false; + if (scrollRange < getScrollAmountToScrollBoundary() + && mAnimateStackYForContentHeightChange) { + // if the scroll boundary updates the position of the stack, + animateStackY = true; + } + setOwnScrollY(scrollRange, animateStackY); } } @@ -1151,6 +1174,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Apply expansion fraction to the y position and height of the notifications panel. */ private void updateStackPosition() { + updateStackPosition(false /* listenerNeedsAnimation */); + } + + /** + * Apply expansion fraction to the y position and height of the notifications panel. + * @param listenerNeedsAnimation does the listener need to animate? + */ + private void updateStackPosition(boolean listenerNeedsAnimation) { // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + mAmbientState.getOverExpansion(); @@ -1158,7 +1189,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); if (mOnStackYChanged != null) { - mOnStackYChanged.run(); + mOnStackYChanged.accept(listenerNeedsAnimation); } if (mQsExpansionFraction <= 0) { final float stackEndHeight = Math.max(0f, @@ -1170,7 +1201,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - void setOnStackYChanged(Runnable onStackYChanged) { + /** + * Add a listener when the StackY changes. The argument signifies whether an animation is + * needed. + */ + void setOnStackYChanged(Consumer onStackYChanged) { mOnStackYChanged = onStackYChanged; } @@ -2670,17 +2705,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final int startingPosition = getPositionInLinearLayout(removedChild); final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements; final int endPosition = startingPosition + childHeight; - if (endPosition <= mOwnScrollY) { + final int scrollBoundaryStart = getScrollAmountToScrollBoundary(); + mAnimateStackYForContentHeightChange = true; + // This is reset onLayout + if (endPosition <= mOwnScrollY - scrollBoundaryStart) { // This child is fully scrolled of the top, so we have to deduct its height from the // scrollPosition setOwnScrollY(mOwnScrollY - childHeight); - } else if (startingPosition < mOwnScrollY) { + } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) { // This child is currently being scrolled into, set the scroll position to the // start of this child - setOwnScrollY(startingPosition); + setOwnScrollY(startingPosition + scrollBoundaryStart); } } + /** + * @return the amount of scrolling needed to start clipping notifications. + */ + private int getScrollAmountToScrollBoundary() { + return mTopPadding - mQsScrollBoundaryPosition; + } + @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getIntrinsicHeight(View view) { if (view instanceof ExpandableView) { @@ -3832,6 +3877,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void onChildHeightChanged(ExpandableView view, boolean needsAnimation) { + boolean previouslyNeededAnimation = mAnimateStackYForContentHeightChange; + if (needsAnimation) { + mAnimateStackYForContentHeightChange = true; + } updateContentHeight(); updateScrollPositionOnExpandInBottom(view); clampScrollPosition(); @@ -3852,6 +3901,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestAnimationOnViewResize(row); } requestChildrenUpdate(); + mAnimateStackYForContentHeightChange = previouslyNeededAnimation; } void onChildHeightReset(ExpandableView view) { @@ -4577,13 +4627,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setOwnScrollY(int ownScrollY) { + setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */); + } + + @ShadeViewRefactor(RefactorComponent.COORDINATOR) + private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { if (ownScrollY != mOwnScrollY) { // We still want to call the normal scrolled changed for accessibility reasons onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); mOwnScrollY = ownScrollY; mAmbientState.setScrollY(mOwnScrollY); updateOnScrollChange(); - updateStackPosition(); + updateStackPosition(animateStackYChangeListener); } } 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 0f8fc14f9d36e..fb4f5592e97f0 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 @@ -1023,7 +1023,7 @@ public class NotificationStackScrollLayoutController { mView.setQsExpansionFraction(expansionFraction); } - public void setOnStackYChanged(Runnable onStackYChanged) { + public void setOnStackYChanged(Consumer onStackYChanged) { mView.setOnStackYChanged(onStackYChanged); } 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 eef519fef16c8..35d1526c4c593 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -830,6 +830,7 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mOnOverscrollTopChangedListener); mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); + mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged); mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); @@ -2198,15 +2199,17 @@ public class NotificationPanelViewController extends PanelViewController { mDepthController.setQsPanelExpansion(qsExpansionFraction); } - private Runnable mOnStackYChanged = () -> { + private void onStackYChanged(boolean shouldAnimate) { if (mQs != null) { + if (shouldAnimate) { + mAnimateNextNotificationBounds = true; + mNotificationBoundsAnimationDelay = 0; + } setQSClippingBounds(); } }; 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 updateQSExpansionEnabledAmbient(); } @@ -3335,7 +3338,6 @@ public class NotificationPanelViewController extends PanelViewController { // The expandedHeight is always the full panel Height when bypassing expandedHeight = getMaxPanelHeightNonBypass(); } - mNotificationStackScrollLayoutController.setOnStackYChanged(mOnStackYChanged); mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight); updateKeyguardBottomAreaAlpha(); updateBigClockAlpha();