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
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 01b55b7..880dd378 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 ac3b6d2..885048d 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 @@
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 @@
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 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 ba03a50..1131a65 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.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 @@
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 36c5419..4f7e14b 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 @@
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 5cc17a0..4487142 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.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.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 @@
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 @@
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
+ private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
private boolean mFlingAfterUpEvent;
@@ -320,6 +327,10 @@
}
};
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 @@
}
};
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 @@
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 @@
}
}
};
+ private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
private boolean mPulsing;
private boolean mScrollable;
private View mForcedScroll;
@@ -397,6 +420,7 @@
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 @@
};
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 @@
private final NotificationSectionsManager mSectionsManager;
private ForegroundServiceDungeonView mFgsSectionView;
+ private boolean mAnimateBottomOnLayout;
private float mLastSentAppear;
private float mLastSentExpandedHeight;
private boolean mWillExpand;
@@ -436,6 +461,7 @@
private boolean mKeyguardMediaControllorVisible;
private NotificationEntry mTopHeadsUpEntry;
+ private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
@@ -502,6 +528,7 @@
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 @@
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 @@
* @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 @@
}
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 @@
}
}
+ @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 @@
updateContentHeight();
clampScrollPosition();
requestChildrenUpdate();
- updateFirstAndLastExpandableView();
+ updateFirstAndLastBackgroundViews();
updateAlgorithmLayoutMinHeight();
updateOwnTranslationZ();
}
@@ -852,6 +1048,9 @@
private void onPreDrawDuringAnimation() {
mShelf.updateAppearance();
updateClippingToTopRoundedCorner();
+ if (!mNeedsAnimation && !mChildrenUpdateRequested) {
+ updateBackground();
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -2168,6 +2367,131 @@
}
}
+ @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 @@
}
@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 @@
}
@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 @@
}
//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 @@
}
@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 @@
setAnimationRunning(true);
mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
mAnimationEvents.clear();
+ updateBackground();
updateViewShadows();
updateClippingToTopRoundedCorner();
} else {
@@ -4004,6 +4348,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void setDimAmount(float dimAmount) {
mDimAmount = dimAmount;
+ updateBackgroundDimming();
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4072,6 +4417,7 @@
}
runAnimationFinishedRunnables();
setAnimationRunning(false);
+ updateBackground();
updateViewShadows();
updateClippingToTopRoundedCorner();
}
@@ -4200,6 +4546,7 @@
invalidateOutline();
}
updateAlgorithmHeightAndPadding();
+ updateBackgroundDimming();
requestChildrenUpdate();
updateOwnTranslationZ();
}
@@ -5080,6 +5427,7 @@
*/
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
+ updateContinuousBackgroundDrawing();
requestChildrenUpdate();
}
@@ -5117,6 +5465,7 @@
}
void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
+ mAnimateBottomOnLayout = animateBottomOnLayout;
}
public void setOnPulseHeightChangedListener(Runnable listener) {
@@ -5149,14 +5498,25 @@
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 @@
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 8a03309..c2d030b 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 @@
if (mView.getDismissAllInProgress()) {
return;
}
+ mView.onSwipeEnd();
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (row.isHeadsUp()) {
@@ -454,6 +455,7 @@
@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 @@
@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 @@
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
+ mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
+
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly