diff options
| author | 2020-12-02 21:12:56 +0000 | |
|---|---|---|
| committer | 2020-12-03 11:37:18 +0000 | |
| commit | 53f5af9e9796d08468598d748c488ee604c7a013 (patch) | |
| tree | e09a45b657dbf3b6c32a1c4f744e53b97621c7dc | |
| parent | c9991d0a93c888efe20d39c6d4ea095aeb3ef69a (diff) | |
Revert "Remove background drawing behind notifications"
This reverts commit 7552019d8bf2033195b1046d9870a1b093574189.
Reason for revert: removing outline breaks lockscreen <=> aod notif animation
DIFF - R.color.notification_shade_background_color is not available
anymore, so get default notif panel background color this way:
Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor()
Fixes: 174665900
Change-Id: Ia939e6bc1cb80e8d44b5c00e1534ab36f2c641cc
6 files changed, 666 insertions, 16 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 01b55b70d5ad..880dd378e390 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -357,6 +357,9 @@ the notification is not swiped enough to dismiss it. --> <bool name="config_showNotificationGear">true</bool> + <!-- Whether or not a background should be drawn behind a notification. --> + <bool name="config_drawNotificationBackground">true</bool> + <!-- Whether or the notifications can be shown and dismissed with a drag. --> <bool name="config_enableNotificationShadeDrag">true</bool> 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 ac3b6d26fab6..885048df13f1 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 @@ -82,6 +82,9 @@ public class AmbientState { private ExpandableNotificationRow mTrackedHeadsUpRow; private float mAppearFraction; + /** Tracks the state from AlertingNotificationManager#hasNotifications() */ + private boolean mHasAlertEntries; + public AmbientState( Context context, @NonNull SectionProvider sectionProvider) { @@ -365,10 +368,21 @@ public class AmbientState { mPanelTracking = panelTracking; } + public boolean hasPulsingNotifications() { + return mPulsing && mHasAlertEntries; + } + public void setPulsing(boolean hasPulsing) { mPulsing = hasPulsing; } + /** + * @return if we're pulsing in general + */ + public boolean isPulsing() { + return mPulsing; + } + public boolean isPulsing(NotificationEntry entry) { return mPulsing && entry.isAlerting(); } @@ -527,4 +541,8 @@ public class AmbientState { public float getAppearFraction() { return mAppearFraction; } + + public void setHasAlertEntries(boolean hasAlertEntries) { + mHasAlertEntries = hasAlertEntries; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java index ba03a50b0ba5..1131a65abe93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java @@ -31,22 +31,175 @@ import com.android.systemui.statusbar.notification.ShadeViewRefactor; import com.android.systemui.statusbar.notification.row.ExpandableView; /** - * Represents the priority of a notification section and tracks first and last visible children. + * Represents the bounds of a section of the notification shade and handles animation when the + * bounds change. */ public class NotificationSection { private @PriorityBucket int mBucket; + private View mOwningView; + private Rect mBounds = new Rect(); + private Rect mCurrentBounds = new Rect(-1, -1, -1, -1); + private Rect mStartAnimationRect = new Rect(); + private Rect mEndAnimationRect = new Rect(); + private ObjectAnimator mTopAnimator = null; + private ObjectAnimator mBottomAnimator = null; private ExpandableView mFirstVisibleChild; private ExpandableView mLastVisibleChild; - NotificationSection(@PriorityBucket int bucket) { + NotificationSection(View owningView, @PriorityBucket int bucket) { + mOwningView = owningView; mBucket = bucket; } + public void cancelAnimators() { + if (mBottomAnimator != null) { + mBottomAnimator.cancel(); + } + if (mTopAnimator != null) { + mTopAnimator.cancel(); + } + } + + public Rect getCurrentBounds() { + return mCurrentBounds; + } + + public Rect getBounds() { + return mBounds; + } + + public boolean didBoundsChange() { + return !mCurrentBounds.equals(mBounds); + } + + public boolean areBoundsAnimating() { + return mBottomAnimator != null || mTopAnimator != null; + } + @PriorityBucket public int getBucket() { return mBucket; } + public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) { + // Left and right bounds are always applied immediately. + mCurrentBounds.left = mBounds.left; + mCurrentBounds.right = mBounds.right; + startBottomAnimation(animateBottom); + startTopAnimation(animateTop); + } + + + @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER) + private void startTopAnimation(boolean animate) { + int previousEndValue = mEndAnimationRect.top; + int newEndValue = mBounds.top; + ObjectAnimator previousAnimator = mTopAnimator; + if (previousAnimator != null && previousEndValue == newEndValue) { + return; + } + if (!animate) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + int previousStartValue = mStartAnimationRect.top; + PropertyValuesHolder[] values = previousAnimator.getValues(); + values[0].setIntValues(previousStartValue, newEndValue); + mStartAnimationRect.top = previousStartValue; + mEndAnimationRect.top = newEndValue; + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + setBackgroundTop(newEndValue); + return; + } + } + if (previousAnimator != null) { + previousAnimator.cancel(); + } + ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop", + mCurrentBounds.top, newEndValue); + Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; + animator.setInterpolator(interpolator); + animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mStartAnimationRect.top = -1; + mEndAnimationRect.top = -1; + mTopAnimator = null; + } + }); + animator.start(); + mStartAnimationRect.top = mCurrentBounds.top; + mEndAnimationRect.top = newEndValue; + mTopAnimator = animator; + } + + @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER) + private void startBottomAnimation(boolean animate) { + int previousStartValue = mStartAnimationRect.bottom; + int previousEndValue = mEndAnimationRect.bottom; + int newEndValue = mBounds.bottom; + ObjectAnimator previousAnimator = mBottomAnimator; + if (previousAnimator != null && previousEndValue == newEndValue) { + return; + } + if (!animate) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + values[0].setIntValues(previousStartValue, newEndValue); + mStartAnimationRect.bottom = previousStartValue; + mEndAnimationRect.bottom = newEndValue; + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + setBackgroundBottom(newEndValue); + return; + } + } + if (previousAnimator != null) { + previousAnimator.cancel(); + } + ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom", + mCurrentBounds.bottom, newEndValue); + Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; + animator.setInterpolator(interpolator); + animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mStartAnimationRect.bottom = -1; + mEndAnimationRect.bottom = -1; + mBottomAnimator = null; + } + }); + animator.start(); + mStartAnimationRect.bottom = mCurrentBounds.bottom; + mEndAnimationRect.bottom = newEndValue; + mBottomAnimator = animator; + } + + @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW) + private void setBackgroundTop(int top) { + mCurrentBounds.top = top; + mOwningView.invalidate(); + } + + @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW) + private void setBackgroundBottom(int bottom) { + mCurrentBounds.bottom = bottom; + mOwningView.invalidate(); + } + public ExpandableView getFirstVisibleChild() { return mFirstVisibleChild; } @@ -66,4 +219,93 @@ public class NotificationSection { mLastVisibleChild = child; return changed; } + + public void resetCurrentBounds() { + mCurrentBounds.set(mBounds); + } + + /** + * Returns true if {@code top} is equal to the top of this section (if not currently animating) + * or where the top of this section will be when animation completes. + */ + public boolean isTargetTop(int top) { + return (mTopAnimator == null && mCurrentBounds.top == top) + || (mTopAnimator != null && mEndAnimationRect.top == top); + } + + /** + * Returns true if {@code bottom} is equal to the bottom of this section (if not currently + * animating) or where the bottom of this section will be when animation completes. + */ + public boolean isTargetBottom(int bottom) { + return (mBottomAnimator == null && mCurrentBounds.bottom == bottom) + || (mBottomAnimator != null && mEndAnimationRect.bottom == bottom); + } + + /** + * Update the bounds of this section based on it's views + * + * @param minTopPosition the minimum position that the top needs to have + * @param minBottomPosition the minimum position that the bottom needs to have + * @return the position of the new bottom + */ + public int updateBounds(int minTopPosition, int minBottomPosition, + boolean shiftBackgroundWithFirst) { + int top = minTopPosition; + int bottom = minTopPosition; + ExpandableView firstView = getFirstVisibleChild(); + if (firstView != null) { + // Round Y up to avoid seeing the background during animation + int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); + // TODO: look into the already animating part + int newTop; + if (isTargetTop(finalTranslationY)) { + // we're ending up at the same location as we are now, let's just skip the + // animation + newTop = finalTranslationY; + } else { + newTop = (int) Math.ceil(firstView.getTranslationY()); + } + top = Math.max(newTop, top); + if (firstView.showingPulsing()) { + // If we're pulsing, the notification can actually go below! + bottom = Math.max(bottom, finalTranslationY + + ExpandableViewState.getFinalActualHeight(firstView)); + if (shiftBackgroundWithFirst) { + mBounds.left += Math.max(firstView.getTranslation(), 0); + mBounds.right += Math.min(firstView.getTranslation(), 0); + } + } + } + top = Math.max(minTopPosition, top); + ExpandableView lastView = getLastVisibleChild(); + if (lastView != null) { + float finalTranslationY = ViewState.getFinalTranslationY(lastView); + int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); + // Round Y down to avoid seeing the background during animation + int finalBottom = (int) Math.floor( + finalTranslationY + finalHeight - lastView.getClipBottomAmount()); + int newBottom; + if (isTargetBottom(finalBottom)) { + // we're ending up at the same location as we are now, lets just skip the animation + newBottom = finalBottom; + } else { + newBottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() + - lastView.getClipBottomAmount()); + // The background can never be lower than the end of the last view + minBottomPosition = (int) Math.min( + lastView.getTranslationY() + lastView.getActualHeight(), + minBottomPosition); + } + bottom = Math.max(bottom, Math.max(newBottom, minBottomPosition)); + } + bottom = Math.max(top, bottom); + mBounds.top = top; + mBounds.bottom = bottom; + return bottom; + } + + public boolean needsBackground() { + return mFirstVisibleChild != null && mBucket != BUCKET_MEDIA_CONTROLS; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 36c5419b7399..4f7e14ba4a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -126,7 +126,7 @@ class NotificationSectionsManager @Inject internal constructor( fun createSectionsForBuckets(): Array<NotificationSection> = sectionsFeatureManager.getNotificationBuckets() - .map { NotificationSection(it) } + .map { NotificationSection(parent, it) } .toTypedArray() /** 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 5cc17a08504f..4487142e8907 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 @@ -38,9 +38,12 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; @@ -69,6 +72,7 @@ import android.widget.OverScroller; import android.widget.ScrollView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardSliceView; @@ -153,6 +157,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private ExpandHelper mExpandHelper; private NotificationSwipeHelper mSwipeHelper; private int mCurrentStackHeight = Integer.MAX_VALUE; + private final Paint mBackgroundPaint = new Paint(); + private final boolean mShouldDrawNotificationBackground; private boolean mHighPriorityBeforeSpeedBump; private boolean mDismissRtl; @@ -251,6 +257,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; + private boolean mFadeNotificationsOnDismiss; private FooterDismissListener mFooterDismissListener; private boolean mFlingAfterUpEvent; @@ -320,6 +327,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } }; private NotificationSection[] mSections; + private boolean mAnimateNextBackgroundTop; + private boolean mAnimateNextBackgroundBottom; + private boolean mAnimateNextSectionBoundsChange; + private int mBgColor; private float mDimAmount; private ValueAnimator mDimAnimator; private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); @@ -330,14 +341,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } }; private ValueAnimator.AnimatorUpdateListener mDimUpdateListener - = animation -> setDimAmount((Float) animation.getAnimatedValue()); + = new ValueAnimator.AnimatorUpdateListener() { + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setDimAmount((Float) animation.getAnimatedValue()); + } + }; protected ViewGroup mQsContainer; private boolean mContinuousShadowUpdate; + private boolean mContinuousBackgroundUpdate; private ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> { updateViewShadows(); return true; }; + private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> { + updateBackground(); + return true; + }; private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> { float endY = view.getTranslationY() + view.getActualHeight(); float otherEndY = otherView.getTranslationY() + otherView.getActualHeight(); @@ -356,7 +378,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mAmbientState.isHiddenAtAll()) { float xProgress = mHideXInterpolator.getInterpolation( (1 - mLinearHideAmount) * mBackgroundXFactor); - outline.setRoundRect(mOutlineAnimationRect, + outline.setRoundRect(mBackgroundAnimationRect, MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius, xProgress)); outline.setAlpha(1.0f - mAmbientState.getHideAmount()); @@ -365,6 +387,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } }; + private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); private boolean mPulsing; private boolean mScrollable; private View mForcedScroll; @@ -397,6 +420,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mInHeadsUpPinnedMode; private boolean mHeadsUpAnimatingAway; private int mStatusBarState; + private int mCachedBackgroundColor; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; private Runnable mReflingAndAnimateScroll = () -> { if (ANCHOR_SCROLLING) { @@ -406,7 +430,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable }; private int mCornerRadius; private int mSidePaddings; - private final Rect mOutlineAnimationRect = new Rect(); + private final Rect mBackgroundAnimationRect = new Rect(); private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); private int mHeadsUpInset; private HeadsUpAppearanceController mHeadsUpAppearanceController; @@ -426,6 +450,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final NotificationSectionsManager mSectionsManager; private ForegroundServiceDungeonView mFgsSectionView; + private boolean mAnimateBottomOnLayout; private float mLastSentAppear; private float mLastSentExpandedHeight; private boolean mWillExpand; @@ -436,6 +461,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mKeyguardMediaControllorVisible; private NotificationEntry mTopHeadsUpEntry; + private long mNumHeadsUp; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = @@ -502,6 +528,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSections = mSectionsManager.createSectionsForBuckets(); mAmbientState = new AmbientState(context, mSectionsManager); + mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor(); int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback, @@ -510,9 +537,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mExpandHelper.setScrollAdapter(mScrollAdapter); mStackScrollAlgorithm = createStackScrollAlgorithm(context); + mShouldDrawNotificationBackground = + res.getBoolean(R.bool.config_drawNotificationBackground); setOutlineProvider(mOutlineProvider); - setWillNotDraw(!DEBUG); + boolean willDraw = mShouldDrawNotificationBackground || DEBUG; + setWillNotDraw(!willDraw); + mBackgroundPaint.setAntiAlias(true); if (DEBUG) { mDebugPaint = new Paint(); mDebugPaint.setColor(0xffff0000); @@ -557,7 +588,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return the height at which we will wake up when pulsing */ public float getWakeUpHeight() { - ExpandableView firstChild = getFirstExpandableView(); + ExpandableView firstChild = getFirstChildWithBackground(); if (firstChild != null) { if (mKeyguardBypassEnabledProvider.getBypassEnabled()) { return firstChild.getHeadsUpHeightWithoutHeader(); @@ -608,11 +639,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void updateBgColor() { + mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor(); + updateBackgroundDimming(); mShelf.onUiModeChanged(); } @ShadeViewRefactor(RefactorComponent.DECORATOR) protected void onDraw(Canvas canvas) { + if (mShouldDrawNotificationBackground + && (mSections[0].getCurrentBounds().top + < mSections[mSections.length - 1].getCurrentBounds().bottom + || mAmbientState.isDozing())) { + drawBackground(canvas); + } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) { + drawHeadsUpBackground(canvas); + } + if (DEBUG) { int y = mTopPadding; canvas.drawLine(0, y, getWidth(), y, mDebugPaint); @@ -647,6 +689,160 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } + @ShadeViewRefactor(RefactorComponent.DECORATOR) + private void drawBackground(Canvas canvas) { + int lockScreenLeft = mSidePaddings; + int lockScreenRight = getWidth() - mSidePaddings; + int lockScreenTop = mSections[0].getCurrentBounds().top; + int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom; + int hiddenLeft = getWidth() / 2; + int hiddenTop = mTopPadding; + + float yProgress = 1 - mInterpolatedHideAmount; + float xProgress = mHideXInterpolator.getInterpolation( + (1 - mLinearHideAmount) * mBackgroundXFactor); + + int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress); + int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress); + int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress); + int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress); + mBackgroundAnimationRect.set( + left, + top, + right, + bottom); + + int backgroundTopAnimationOffset = top - lockScreenTop; + // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD + boolean anySectionHasVisibleChild = false; + for (NotificationSection section : mSections) { + if (section.needsBackground()) { + anySectionHasVisibleChild = true; + break; + } + } + boolean shouldDrawBackground; + if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) { + shouldDrawBackground = isPulseExpanding(); + } else { + shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild; + } + if (shouldDrawBackground) { + drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset); + } + + updateClipping(); + } + + /** + * Draws round rects for each background section. + * + * We want to draw a round rect for each background section as defined by {@link #mSections}. + * However, if two sections are directly adjacent with no gap between them (e.g. on the + * lockscreen where the shelf can appear directly below the high priority section, or while + * scrolling the shade so that the top of the shelf is right at the bottom of the high priority + * section), we don't want to round the adjacent corners. + * + * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we + * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect. + * This method tracks the top of each rect we need to draw, then iterates through the visible + * sections. If a section is not adjacent to the previous section, we draw the previous rect + * behind the sections we've accumulated up to that point, then start a new rect at the top of + * the current section. When we're done iterating we will always have one rect left to draw. + */ + private void drawBackgroundRects(Canvas canvas, int left, int right, int top, + int animationYOffset) { + int backgroundRectTop = top; + int lastSectionBottom = + mSections[0].getCurrentBounds().bottom + animationYOffset; + int currentLeft = left; + int currentRight = right; + boolean first = true; + for (NotificationSection section : mSections) { + if (!section.needsBackground()) { + continue; + } + int sectionTop = section.getCurrentBounds().top + animationYOffset; + int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right); + int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft); + // If sections are directly adjacent to each other, we don't want to draw them + // as separate roundrects, as the rounded corners right next to each other look + // bad. + if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX + || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) { + canvas.drawRoundRect(currentLeft, + backgroundRectTop, + currentRight, + lastSectionBottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + backgroundRectTop = sectionTop; + } + currentLeft = ownLeft; + currentRight = ownRight; + lastSectionBottom = + section.getCurrentBounds().bottom + animationYOffset; + first = false; + } + canvas.drawRoundRect(currentLeft, + backgroundRectTop, + currentRight, + lastSectionBottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + } + + private void drawHeadsUpBackground(Canvas canvas) { + int left = mSidePaddings; + int right = getWidth() - mSidePaddings; + + float top = getHeight(); + float bottom = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() != View.GONE + && child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0 + && row.getProvider().shouldShowGutsOnSnapOpen()) { + top = Math.min(top, row.getTranslationY()); + bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight()); + } + } + } + + if (top < bottom) { + canvas.drawRoundRect( + left, top, right, bottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + } + } + + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + void updateBackgroundDimming() { + // No need to update the background color if it's not being drawn. + if (!mShouldDrawNotificationBackground) { + return; + } + final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1; + if (newFlowHideShelf) { + mBackgroundPaint.setColor(Color.TRANSPARENT); + invalidate(); + return; + } + // Interpolate between semi-transparent notification panel background color + // and white AOD separator. + float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */, + mLinearHideAmount); + int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation); + + if (mCachedBackgroundColor != color) { + mCachedBackgroundColor = color; + mBackgroundPaint.setColor(color); + invalidate(); + } + } + private void reinitView() { initView(getContext(), mKeyguardBypassEnabledProvider, mSwipeHelper); } @@ -781,7 +977,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateContentHeight(); clampScrollPosition(); requestChildrenUpdate(); - updateFirstAndLastExpandableView(); + updateFirstAndLastBackgroundViews(); updateAlgorithmLayoutMinHeight(); updateOwnTranslationZ(); } @@ -852,6 +1048,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void onPreDrawDuringAnimation() { mShelf.updateAppearance(); updateClippingToTopRoundedCorner(); + if (!mNeedsAnimation && !mChildrenUpdateRequested) { + updateBackground(); + } } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -2168,6 +2367,131 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + private void updateBackground() { + // No need to update the background color if it's not being drawn. + if (!mShouldDrawNotificationBackground) { + return; + } + + updateBackgroundBounds(); + if (didSectionBoundsChange()) { + boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop + || mAnimateNextBackgroundBottom || areSectionBoundsAnimating(); + if (!isExpanded()) { + abortBackgroundAnimators(); + animate = false; + } + if (animate) { + startBackgroundAnimation(); + } else { + for (NotificationSection section : mSections) { + section.resetCurrentBounds(); + } + invalidate(); + } + } else { + abortBackgroundAnimators(); + } + mAnimateNextBackgroundTop = false; + mAnimateNextBackgroundBottom = false; + mAnimateNextSectionBoundsChange = false; + } + + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) + private void abortBackgroundAnimators() { + for (NotificationSection section : mSections) { + section.cancelAnimators(); + } + } + + private boolean didSectionBoundsChange() { + for (NotificationSection section : mSections) { + if (section.didBoundsChange()) { + return true; + } + } + return false; + } + + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) + private boolean areSectionBoundsAnimating() { + for (NotificationSection section : mSections) { + if (section.areBoundsAnimating()) { + return true; + } + } + return false; + } + + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) + private void startBackgroundAnimation() { + // TODO(kprevas): do we still need separate fields for top/bottom? + // or can each section manage its own animation state? + NotificationSection firstVisibleSection = getFirstVisibleSection(); + NotificationSection lastVisibleSection = getLastVisibleSection(); + for (NotificationSection section : mSections) { + section.startBackgroundAnimation( + section == firstVisibleSection + ? mAnimateNextBackgroundTop + : mAnimateNextSectionBoundsChange, + section == lastVisibleSection + ? mAnimateNextBackgroundBottom + : mAnimateNextSectionBoundsChange); + } + } + + /** + * Update the background bounds to the new desired bounds + */ + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + private void updateBackgroundBounds() { + int left = mSidePaddings; + int right = getWidth() - mSidePaddings; + for (NotificationSection section : mSections) { + section.getBounds().left = left; + section.getBounds().right = right; + } + + if (!mIsExpanded) { + for (NotificationSection section : mSections) { + section.getBounds().top = 0; + section.getBounds().bottom = 0; + } + return; + } + int minTopPosition; + NotificationSection lastSection = getLastVisibleSection(); + boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD; + if (!onKeyguard) { + minTopPosition = (int) (mTopPadding + mStackTranslation); + } else if (lastSection == null) { + minTopPosition = mTopPadding; + } else { + // The first sections could be empty while there could still be elements in later + // sections. The position of these first few sections is determined by the position of + // the first visible section. + NotificationSection firstVisibleSection = getFirstVisibleSection(); + firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */, + false /* shiftPulsingWithFirst */); + minTopPosition = firstVisibleSection.getBounds().top; + } + boolean shiftPulsingWithFirst = mNumHeadsUp <= 1 + && (mAmbientState.isDozing() + || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard)); + for (NotificationSection section : mSections) { + int minBottomPosition = minTopPosition; + if (section == lastSection) { + // We need to make sure the section goes all the way to the shelf + minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf) + + mShelf.getIntrinsicHeight()); + } + minTopPosition = section.updateBounds(minTopPosition, minBottomPosition, + shiftPulsingWithFirst); + shiftPulsingWithFirst = false; + } + } + private NotificationSection getFirstVisibleSection() { for (NotificationSection section : mSections) { if (section.getFirstVisibleChild() != null) { @@ -2188,7 +2512,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @ShadeViewRefactor(RefactorComponent.COORDINATOR) - private ExpandableView getLastExpandableView() { + private ExpandableView getLastChildWithBackground() { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { ExpandableView child = (ExpandableView) getChildAt(i); @@ -2201,7 +2525,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @ShadeViewRefactor(RefactorComponent.COORDINATOR) - private ExpandableView getFirstExpandableView() { + private ExpandableView getFirstChildWithBackground() { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { ExpandableView child = (ExpandableView) getChildAt(i); @@ -2214,7 +2538,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } //TODO: We shouldn't have to generate this list every time - private List<ExpandableView> getExpandableViewList() { + private List<ExpandableView> getChildrenWithBackground() { ArrayList<ExpandableView> children = new ArrayList<>(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -2752,13 +3076,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) - private void updateFirstAndLastExpandableView() { - ExpandableView lastChild = getLastExpandableView(); - mSectionsManager.updateFirstAndLastViewsForAllSections( - mSections, getExpandableViewList()); + private void updateFirstAndLastBackgroundViews() { + NotificationSection firstSection = getFirstVisibleSection(); + NotificationSection lastSection = getLastVisibleSection(); + ExpandableView previousFirstChild = + firstSection == null ? null : firstSection.getFirstVisibleChild(); + ExpandableView previousLastChild = + lastSection == null ? null : lastSection.getLastVisibleChild(); + + ExpandableView firstChild = getFirstChildWithBackground(); + ExpandableView lastChild = getLastChildWithBackground(); + boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections( + mSections, getChildrenWithBackground()); + + if (mAnimationsEnabled && mIsExpanded) { + mAnimateNextBackgroundTop = firstChild != previousFirstChild; + mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout; + mAnimateNextSectionBoundsChange = sectionViewsChanged; + } else { + mAnimateNextBackgroundTop = false; + mAnimateNextBackgroundBottom = false; + mAnimateNextSectionBoundsChange = false; + } mAmbientState.setLastVisibleBackgroundChild(lastChild); // TODO: Refactor SectionManager and put the RoundnessManager there. mController.getNoticationRoundessManager().updateRoundedChildren(mSections); + mAnimateBottomOnLayout = false; invalidate(); } @@ -2920,6 +3263,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setAnimationRunning(true); mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay); mAnimationEvents.clear(); + updateBackground(); updateViewShadows(); updateClippingToTopRoundedCorner(); } else { @@ -4004,6 +4348,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void setDimAmount(float dimAmount) { mDimAmount = dimAmount; + updateBackgroundDimming(); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4072,6 +4417,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } runAnimationFinishedRunnables(); setAnimationRunning(false); + updateBackground(); updateViewShadows(); updateClippingToTopRoundedCorner(); } @@ -4200,6 +4546,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable invalidateOutline(); } updateAlgorithmHeightAndPadding(); + updateBackgroundDimming(); requestChildrenUpdate(); updateOwnTranslationZ(); } @@ -5080,6 +5427,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setDozeAmount(float dozeAmount) { mAmbientState.setDozeAmount(dozeAmount); + updateContinuousBackgroundDrawing(); requestChildrenUpdate(); } @@ -5117,6 +5465,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void setAnimateBottomOnLayout(boolean animateBottomOnLayout) { + mAnimateBottomOnLayout = animateBottomOnLayout; } public void setOnPulseHeightChangedListener(Runnable listener) { @@ -5149,14 +5498,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable void onSwipeBegin() { requestDisallowInterceptTouchEvent(true); + updateFirstAndLastBackgroundViews(); updateContinuousShadowDrawing(); + updateContinuousBackgroundDrawing(); requestChildrenUpdate(); } + void onSwipeEnd() { + updateFirstAndLastBackgroundViews(); + } + void setTopHeadsUpEntry(NotificationEntry topEntry) { mTopHeadsUpEntry = topEntry; } + void setNumHeadsUp(long numHeadsUp) { + mNumHeadsUp = numHeadsUp; + mAmbientState.setHasAlertEntries(numHeadsUp > 0); + } + public boolean getIsExpanded() { return mIsExpanded; } @@ -5271,6 +5631,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSectionsManager.updateSectionBoundaries(reason); } + boolean isSilkDismissEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* enabled by default */) == 1; + } + + void updateContinuousBackgroundDrawing() { + if (isSilkDismissEnabled()) { + return; + } + boolean continuousBackground = !mAmbientState.isFullyAwake() + && mSwipeHelper.isSwiping(); + if (continuousBackground != mContinuousBackgroundUpdate) { + mContinuousBackgroundUpdate = continuousBackground; + if (continuousBackground) { + getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater); + } else { + getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater); + } + } + } + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void updateContinuousShadowDrawing() { boolean continuousShadowUpdate = mAnimationRunning 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 8a0330912502..c2d030b132db 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 @@ -395,6 +395,7 @@ public class NotificationStackScrollLayoutController { if (mView.getDismissAllInProgress()) { return; } + mView.onSwipeEnd(); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (row.isHeadsUp()) { @@ -454,6 +455,7 @@ public class NotificationStackScrollLayoutController { @Override public void onChildSnappedBack(View animView, float targetLeft) { + mView.onSwipeEnd(); if (animView instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) animView; if (row.isPinned() && !canChildBeDismissed(row) @@ -517,7 +519,9 @@ public class NotificationStackScrollLayoutController { @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { + long numEntries = mHeadsUpManager.getAllEntries().count(); NotificationEntry topEntry = mHeadsUpManager.getTopEntry(); + mView.setNumHeadsUp(numEntries); mView.setTopHeadsUpEntry(topEntry); mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */); } @@ -661,6 +665,8 @@ public class NotificationStackScrollLayoutController { mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed); mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener); + mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming); + mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly |