diff options
| author | 2022-10-24 18:51:07 +0000 | |
|---|---|---|
| committer | 2022-10-24 18:51:07 +0000 | |
| commit | 46b33d3f81a3af6dd3a6876476dcfa41250eea73 (patch) | |
| tree | 296a38260784df579f5f031a46f6e18805f54a71 | |
| parent | 6117424fe383eda3c6e0f66b2404735245729d52 (diff) | |
| parent | ecf8e840eb410839301feffa497a3b88c003ff50 (diff) | |
Merge "Add support for rounded notification in groups" into tm-qpr-dev
20 files changed, 1081 insertions, 403 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e5b7ff930910..9ef3f5d97d47 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -63,7 +63,8 @@ object Flags { @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true) val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true) val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true) - // next id: 116 + @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, true) + // next id: 117 // 200 - keyguard/lockscreen // ** Flag retired ** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index f96198450ed6..87ef92a28d5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -40,6 +40,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -110,8 +111,8 @@ public class NotificationShelf extends ActivatableNotificationView implements setClipChildren(false); setClipToPadding(false); mShelfIcons.setIsStaticLayout(false); - setBottomRoundness(1.0f, false /* animate */); - setTopRoundness(1f, false /* animate */); + requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue); + requestTopRoundness(1f, false, SourceType.DefaultValue); // Setting this to first in section to get the clipping to the top roundness correct. This // value determines the way we are clipping to the top roundness of the overall shade @@ -413,7 +414,7 @@ public class NotificationShelf extends ActivatableNotificationView implements if (iconState != null && iconState.clampedAppearAmount == 1.0f) { // only if the first icon is fully in the shelf we want to clip to it! backgroundTop = (int) (child.getTranslationY() - getTranslationY()); - firstElementRoundness = expandableRow.getCurrentTopRoundness(); + firstElementRoundness = expandableRow.getTopRoundness(); } } @@ -507,28 +508,36 @@ public class NotificationShelf extends ActivatableNotificationView implements // Round bottom corners within animation bounds final float changeFraction = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); - anv.setBottomRoundness(anv.isLastInSection() ? 1f : changeFraction, - false /* animate */); + anv.requestBottomRoundness( + anv.isLastInSection() ? 1f : changeFraction, + /* animate = */ false, + SourceType.OnScroll); } else if (viewEnd < cornerAnimationTop) { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. - anv.setBottomRoundness(anv.isLastInSection() ? 1f : smallCornerRadius, - false /* animate */); + anv.requestBottomRoundness( + anv.isLastInSection() ? 1f : smallCornerRadius, + /* animate = */ false, + SourceType.OnScroll); } if (viewStart >= cornerAnimationTop) { // Round top corners within animation bounds final float changeFraction = MathUtils.saturate( (viewStart - cornerAnimationTop) / cornerAnimationDistance); - anv.setTopRoundness(anv.isFirstInSection() ? 1f : changeFraction, - false /* animate */); + anv.requestTopRoundness( + anv.isFirstInSection() ? 1f : changeFraction, + false, + SourceType.OnScroll); } else if (viewStart < cornerAnimationTop) { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. - anv.setTopRoundness(anv.isFirstInSection() ? 1f : smallCornerRadius, - false /* animate */); + anv.requestTopRoundness( + anv.isFirstInSection() ? 1f : smallCornerRadius, + false, + SourceType.OnScroll); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 553826dda919..0d35fdce953e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -70,8 +70,8 @@ class NotificationLaunchAnimatorController( val height = max(0, notification.actualHeight - notification.clipBottomAmount) val location = notification.locationOnScreen - val clipStartLocation = notificationListContainer.getTopClippingStartLocation() - val roundedTopClipping = Math.max(clipStartLocation - location[1], 0) + val clipStartLocation = notificationListContainer.topClippingStartLocation + val roundedTopClipping = (clipStartLocation - location[1]).coerceAtLeast(0) val windowTop = location[1] + roundedTopClipping val topCornerRadius = if (roundedTopClipping > 0) { // Because the rounded Rect clipping is complex, we start the top rounding at @@ -80,7 +80,7 @@ class NotificationLaunchAnimatorController( // if we'd like to have this perfect, but this is close enough. 0f } else { - notification.currentBackgroundRadiusTop + notification.topCornerRadius } val params = LaunchAnimationParameters( top = windowTop, @@ -88,7 +88,7 @@ class NotificationLaunchAnimatorController( left = location[0], right = location[0] + notification.width, topCornerRadius = topCornerRadius, - bottomCornerRadius = notification.currentBackgroundRadiusBottom + bottomCornerRadius = notification.bottomCornerRadius ) params.startTranslationZ = notification.translationZ @@ -97,8 +97,8 @@ class NotificationLaunchAnimatorController( params.startClipTopAmount = notification.clipTopAmount if (notification.isChildInGroup) { params.startNotificationTop += notification.notificationParent.translationY - val parentRoundedClip = Math.max( - clipStartLocation - notification.notificationParent.locationOnScreen[1], 0) + val locationOnScreen = notification.notificationParent.locationOnScreen[1] + val parentRoundedClip = (clipStartLocation - locationOnScreen).coerceAtLeast(0) params.parentStartRoundedTopClipping = parentRoundedClip val parentClip = notification.notificationParent.clipTopAmount diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt new file mode 100644 index 000000000000..ed7f648081c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -0,0 +1,284 @@ +package com.android.systemui.statusbar.notification + +import android.util.FloatProperty +import android.view.View +import androidx.annotation.FloatRange +import com.android.systemui.R +import com.android.systemui.statusbar.notification.stack.AnimationProperties +import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import kotlin.math.abs + +/** + * Interface that allows to request/retrieve top and bottom roundness (a value between 0f and 1f). + * + * To request a roundness value, an [SourceType] must be specified. In case more origins require + * different roundness, for the same property, the maximum value will always be chosen. + * + * It also returns the current radius for all corners ([updatedRadii]). + */ +interface Roundable { + /** Properties required for a Roundable */ + val roundableState: RoundableState + + /** Current top roundness */ + @get:FloatRange(from = 0.0, to = 1.0) + @JvmDefault + val topRoundness: Float + get() = roundableState.topRoundness + + /** Current bottom roundness */ + @get:FloatRange(from = 0.0, to = 1.0) + @JvmDefault + val bottomRoundness: Float + get() = roundableState.bottomRoundness + + /** Max radius in pixel */ + @JvmDefault + val maxRadius: Float + get() = roundableState.maxRadius + + /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */ + @JvmDefault + val topCornerRadius: Float + get() = topRoundness * maxRadius + + /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */ + @JvmDefault + val bottomCornerRadius: Float + get() = bottomRoundness * maxRadius + + /** Get and update the current radii */ + @JvmDefault + val updatedRadii: FloatArray + get() = + roundableState.radiiBuffer.also { radii -> + updateRadii( + topCornerRadius = topCornerRadius, + bottomCornerRadius = bottomCornerRadius, + radii = radii, + ) + } + + /** + * Request the top roundness [value] for a specific [sourceType]. + * + * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more + * origins require different roundness, for the same property, the maximum value will always be + * chosen. + * + * @param value a value between 0f and 1f. + * @param animate true if it should animate to that value. + * @param sourceType the source from which the request for roundness comes. + * @return Whether the roundness was changed. + */ + @JvmDefault + fun requestTopRoundness( + @FloatRange(from = 0.0, to = 1.0) value: Float, + animate: Boolean, + sourceType: SourceType, + ): Boolean { + val roundnessMap = roundableState.topRoundnessMap + val lastValue = roundnessMap.values.maxOrNull() ?: 0f + if (value == 0f) { + // we should only take the largest value, and since the smallest value is 0f, we can + // remove this value from the list. In the worst case, the list is empty and the + // default value is 0f. + roundnessMap.remove(sourceType) + } else { + roundnessMap[sourceType] = value + } + val newValue = roundnessMap.values.maxOrNull() ?: 0f + + if (lastValue != newValue) { + val wasAnimating = roundableState.isTopAnimating() + + // Fail safe: + // when we've been animating previously and we're now getting an update in the + // other direction, make sure to animate it too, otherwise, the localized updating + // may make the start larger than 1.0. + val shouldAnimate = wasAnimating && abs(newValue - lastValue) > 0.5f + + roundableState.setTopRoundness(value = newValue, animated = shouldAnimate || animate) + return true + } + return false + } + + /** + * Request the bottom roundness [value] for a specific [sourceType]. + * + * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more + * origins require different roundness, for the same property, the maximum value will always be + * chosen. + * + * @param value value between 0f and 1f. + * @param animate true if it should animate to that value. + * @param sourceType the source from which the request for roundness comes. + * @return Whether the roundness was changed. + */ + @JvmDefault + fun requestBottomRoundness( + @FloatRange(from = 0.0, to = 1.0) value: Float, + animate: Boolean, + sourceType: SourceType, + ): Boolean { + val roundnessMap = roundableState.bottomRoundnessMap + val lastValue = roundnessMap.values.maxOrNull() ?: 0f + if (value == 0f) { + // we should only take the largest value, and since the smallest value is 0f, we can + // remove this value from the list. In the worst case, the list is empty and the + // default value is 0f. + roundnessMap.remove(sourceType) + } else { + roundnessMap[sourceType] = value + } + val newValue = roundnessMap.values.maxOrNull() ?: 0f + + if (lastValue != newValue) { + val wasAnimating = roundableState.isBottomAnimating() + + // Fail safe: + // when we've been animating previously and we're now getting an update in the + // other direction, make sure to animate it too, otherwise, the localized updating + // may make the start larger than 1.0. + val shouldAnimate = wasAnimating && abs(newValue - lastValue) > 0.5f + + roundableState.setBottomRoundness(value = newValue, animated = shouldAnimate || animate) + return true + } + return false + } + + /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */ + @JvmDefault + fun applyRoundness() { + roundableState.targetView.invalidate() + } + + /** @return true if top or bottom roundness is not zero. */ + @JvmDefault + fun hasRoundedCorner(): Boolean { + return topRoundness != 0f || bottomRoundness != 0f + } + + /** + * Update an Array of 8 values, 4 pairs of [X,Y] radii. As expected by param radii of + * [android.graphics.Path.addRoundRect]. + * + * This method reuses the previous [radii] for performance reasons. + */ + @JvmDefault + fun updateRadii( + topCornerRadius: Float, + bottomCornerRadius: Float, + radii: FloatArray, + ) { + if (radii.size != 8) error("Unexpected radiiBuffer size ${radii.size}") + + if (radii[0] != topCornerRadius || radii[4] != bottomCornerRadius) { + (0..3).forEach { radii[it] = topCornerRadius } + (4..7).forEach { radii[it] = bottomCornerRadius } + } + } +} + +/** + * State object for a `Roundable` class. + * @param targetView Will handle the [AnimatableProperty] + * @param roundable Target of the radius animation + * @param maxRadius Max corner radius in pixels + */ +class RoundableState( + internal val targetView: View, + roundable: Roundable, + internal val maxRadius: Float, +) { + /** Animatable for top roundness */ + private val topAnimatable = topAnimatable(roundable) + + /** Animatable for bottom roundness */ + private val bottomAnimatable = bottomAnimatable(roundable) + + /** Current top roundness. Use [setTopRoundness] to update this value */ + @set:FloatRange(from = 0.0, to = 1.0) + internal var topRoundness = 0f + private set + + /** Current bottom roundness. Use [setBottomRoundness] to update this value */ + @set:FloatRange(from = 0.0, to = 1.0) + internal var bottomRoundness = 0f + private set + + /** Last requested top roundness associated by [SourceType] */ + internal val topRoundnessMap = mutableMapOf<SourceType, Float>() + + /** Last requested bottom roundness associated by [SourceType] */ + internal val bottomRoundnessMap = mutableMapOf<SourceType, Float>() + + /** Last cached radii */ + internal val radiiBuffer = FloatArray(8) + + /** Is top roundness animation in progress? */ + internal fun isTopAnimating() = PropertyAnimator.isAnimating(targetView, topAnimatable) + + /** Is bottom roundness animation in progress? */ + internal fun isBottomAnimating() = PropertyAnimator.isAnimating(targetView, bottomAnimatable) + + /** Set the current top roundness */ + internal fun setTopRoundness( + value: Float, + animated: Boolean = targetView.isShown, + ) { + PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated) + } + + /** Set the current bottom roundness */ + internal fun setBottomRoundness( + value: Float, + animated: Boolean = targetView.isShown, + ) { + PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated) + } + + companion object { + private val DURATION: AnimationProperties = + AnimationProperties() + .setDuration(StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS.toLong()) + + private fun topAnimatable(roundable: Roundable): AnimatableProperty = + AnimatableProperty.from( + object : FloatProperty<View>("topRoundness") { + override fun get(view: View): Float = roundable.topRoundness + + override fun setValue(view: View, value: Float) { + roundable.roundableState.topRoundness = value + roundable.applyRoundness() + } + }, + R.id.top_roundess_animator_tag, + R.id.top_roundess_animator_end_tag, + R.id.top_roundess_animator_start_tag, + ) + + private fun bottomAnimatable(roundable: Roundable): AnimatableProperty = + AnimatableProperty.from( + object : FloatProperty<View>("bottomRoundness") { + override fun get(view: View): Float = roundable.bottomRoundness + + override fun setValue(view: View, value: Float) { + roundable.roundableState.bottomRoundness = value + roundable.applyRoundness() + } + }, + R.id.bottom_roundess_animator_tag, + R.id.bottom_roundess_animator_end_tag, + R.id.bottom_roundess_animator_start_tag, + ) + } +} + +enum class SourceType { + DefaultValue, + OnDismissAnimation, + OnScroll, +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 755e3e1a208e..d29298a2f637 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -613,22 +613,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected void resetAllContentAlphas() {} @Override - protected void applyRoundness() { + public void applyRoundness() { super.applyRoundness(); - applyBackgroundRoundness(getCurrentBackgroundRadiusTop(), - getCurrentBackgroundRadiusBottom()); + applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius()); } @Override - public float getCurrentBackgroundRadiusTop() { + public float getTopCornerRadius() { float fraction = getInterpolatedAppearAnimationFraction(); - return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction); + return MathUtils.lerp(0, super.getTopCornerRadius(), fraction); } @Override - public float getCurrentBackgroundRadiusBottom() { + public float getBottomCornerRadius() { float fraction = getInterpolatedAppearAnimationFraction(); - return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction); + return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction); } private void applyBackgroundRoundness(float topRadius, float bottomRadius) { 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 087dc71f6cf2..9e7717caf69c 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 @@ -93,6 +93,7 @@ import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -154,7 +155,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView void onLayout(); } - /** Listens for changes to the expansion state of this row. */ + /** + * Listens for changes to the expansion state of this row. + */ public interface OnExpansionChangedListener { void onExpansionChanged(boolean isExpanded); } @@ -183,22 +186,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mNotificationLaunchHeight; private boolean mMustStayOnScreen; - /** Does this row contain layouts that can adapt to row expansion */ + /** + * Does this row contain layouts that can adapt to row expansion + */ private boolean mExpandable; - /** Has the user actively changed the expansion state of this row */ + /** + * Has the user actively changed the expansion state of this row + */ private boolean mHasUserChangedExpansion; - /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ + /** + * If {@link #mHasUserChangedExpansion}, has the user expanded this row + */ private boolean mUserExpanded; - /** Whether the blocking helper is showing on this notification (even if dismissed) */ + /** + * Whether the blocking helper is showing on this notification (even if dismissed) + */ private boolean mIsBlockingHelperShowing; /** * Has this notification been expanded while it was pinned */ private boolean mExpandedWhenPinned; - /** Is the user touching this row */ + /** + * Is the user touching this row + */ private boolean mUserLocked; - /** Are we showing the "public" version */ + /** + * Are we showing the "public" version + */ private boolean mShowingPublic; private boolean mSensitive; private boolean mSensitiveHiddenInGeneral; @@ -351,11 +366,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mWasChildInGroupWhenRemoved; private NotificationInlineImageResolver mImageResolver; private NotificationMediaManager mMediaManager; - @Nullable private OnExpansionChangedListener mExpansionChangedListener; - @Nullable private Runnable mOnIntrinsicHeightReachedRunnable; + @Nullable + private OnExpansionChangedListener mExpansionChangedListener; + @Nullable + private Runnable mOnIntrinsicHeightReachedRunnable; private float mTopRoundnessDuringLaunchAnimation; private float mBottomRoundnessDuringLaunchAnimation; + private boolean mIsNotificationGroupCornerEnabled; /** * Returns whether the given {@code statusBarNotification} is a system notification. @@ -574,14 +592,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Called when the notification's ranking was changed (but nothing else changed). */ + /** + * Called when the notification's ranking was changed (but nothing else changed). + */ public void onNotificationRankingUpdated() { if (mMenuRow != null) { mMenuRow.onNotificationUpdated(mEntry.getSbn()); } } - /** Call when bubble state has changed and the button on the notification should be updated. */ + /** + * Call when bubble state has changed and the button on the notification should be updated. + */ public void updateBubbleButton() { for (NotificationContentView l : mLayouts) { l.updateBubbleButton(mEntry); @@ -620,6 +642,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * Sets a supplier that can determine whether the keyguard is secure or not. + * * @param secureStateProvider A function that returns true if keyguard is secure. */ public void setSecureStateProvider(BooleanSupplier secureStateProvider) { @@ -781,7 +804,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setUntruncatedChildCount(childCount); } - /** Called after children have been attached to set the expansion states */ + /** + * Called after children have been attached to set the expansion states + */ public void resetChildSystemExpandedStates() { if (isSummaryWithChildren()) { mChildrenContainer.updateExpansionStates(); @@ -791,7 +816,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * Add a child notification to this view. * - * @param row the row to add + * @param row the row to add * @param childIndex the index to add it at, if -1 it will be added at the end */ public void addChildNotification(ExpandableNotificationRow row, int childIndex) { @@ -809,10 +834,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } onAttachedChildrenCountChanged(); row.setIsChildInGroup(false, null); - row.setBottomRoundness(0.0f, false /* animate */); + row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue); } - /** Returns the child notification at [index], or null if no such child. */ + /** + * Returns the child notification at [index], or null if no such child. + */ @Nullable public ExpandableNotificationRow getChildNotificationAt(int index) { if (mChildrenContainer == null @@ -834,7 +861,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * @param isChildInGroup Is this notification now in a group - * @param parent the new parent notification + * @param parent the new parent notification */ public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) { if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) { @@ -898,7 +925,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren(); } - /** Updates states of all children. */ + /** + * Updates states of all children. + */ public void updateChildrenStates(AmbientState ambientState) { if (mIsSummaryWithChildren) { ExpandableViewState parentState = getViewState(); @@ -906,21 +935,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Applies children states. */ + /** + * Applies children states. + */ public void applyChildrenState() { if (mIsSummaryWithChildren) { mChildrenContainer.applyState(); } } - /** Prepares expansion changed. */ + /** + * Prepares expansion changed. + */ public void prepareExpansionChanged() { if (mIsSummaryWithChildren) { mChildrenContainer.prepareExpansionChanged(); } } - /** Starts child animations. */ + /** + * Starts child animations. + */ public void startChildAnimation(AnimationProperties properties) { if (mIsSummaryWithChildren) { mChildrenContainer.startAnimationToState(properties); @@ -984,7 +1019,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren) { return mChildrenContainer.getIntrinsicHeight(); } - if(mExpandedWhenPinned) { + if (mExpandedWhenPinned) { return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); } else if (atLeastMinHeight) { return Math.max(getCollapsedHeight(), getHeadsUpHeight()); @@ -1079,18 +1114,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateClickAndFocus(); } - /** The click listener for the bubble button. */ + /** + * The click listener for the bubble button. + */ public View.OnClickListener getBubbleClickListener() { return v -> { if (mBubblesManagerOptional.isPresent()) { mBubblesManagerOptional.get() - .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */); + .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */); } mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */); }; } - /** The click listener for the snooze button. */ + /** + * The click listener for the snooze button. + */ public View.OnClickListener getSnoozeClickListener(MenuItem item) { return v -> { // Dismiss a snoozed notification if one is still left behind @@ -1252,7 +1291,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setContentBackground(int customBackgroundColor, boolean animate, - NotificationContentView notificationContentView) { + NotificationContentView notificationContentView) { if (getShowingLayout() == notificationContentView) { setTintColor(customBackgroundColor, animate); } @@ -1637,7 +1676,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView setTargetPoint(null); } - /** Shows the given feedback icon, or hides the icon if null. */ + /** + * Shows the given feedback icon, or hides the icon if null. + */ public void setFeedbackIcon(@Nullable FeedbackIcon icon) { if (mIsSummaryWithChildren) { mChildrenContainer.setFeedbackIcon(icon); @@ -1646,7 +1687,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPublicLayout.setFeedbackIcon(icon); } - /** Sets the last time the notification being displayed audibly alerted the user. */ + /** + * Sets the last time the notification being displayed audibly alerted the user. + */ public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; @@ -1700,7 +1743,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView Trace.endSection(); } - /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */ + /** + * Generates and appends "(MessagingStyle)" type tag to passed string for tracing. + */ @NonNull private String appendTraceStyleTag(@NonNull String traceTag) { if (!Trace.isEnabled()) { @@ -1721,7 +1766,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView super.onFinishInflate(); mPublicLayout = findViewById(R.id.expandedPublic); mPrivateLayout = findViewById(R.id.expanded); - mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; + mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; for (NotificationContentView l : mLayouts) { l.setExpandClickListener(mExpandClickListener); @@ -1740,6 +1785,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setIsLowPriority(mIsLowPriority); mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); mChildrenContainer.onNotificationUpdated(); + mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled); mTranslateableViews.add(mChildrenContainer); }); @@ -1796,6 +1842,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * Perform a smart action which triggers a longpress (expose guts). * Based on the semanticAction passed, may update the state of the guts view. + * * @param semanticAction associated with this smart action click */ public void doSmartActionClick(int x, int y, int semanticAction) { @@ -1939,9 +1986,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * 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. + * translationX, otherwise the contents will be + * translated. */ @Override public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { @@ -1955,6 +2003,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (previousTranslation != 0) { setTranslation(previousTranslation); } + if (mChildrenContainer != null) { + List<ExpandableNotificationRow> notificationChildren = + mChildrenContainer.getAttachedChildren(); + for (int i = 0; i < notificationChildren.size(); i++) { + ExpandableNotificationRow child = notificationChildren.get(i); + child.setDismissUsingRowTranslationX(usingRowTranslationX); + } + } } } @@ -2009,7 +2065,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public Animator getTranslateViewAnimator(final float leftTarget, - AnimatorUpdateListener listener) { + AnimatorUpdateListener listener) { if (mTranslateAnim != null) { mTranslateAnim.cancel(); } @@ -2115,7 +2171,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING)); float startTop = params.getStartNotificationTop(); top = (int) Math.min(MathUtils.lerp(startTop, - params.getTop(), expandProgress), + params.getTop(), expandProgress), startTop); } else { top = params.getTop(); @@ -2151,29 +2207,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } setTranslationY(top); - mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / mOutlineRadius; - mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / mOutlineRadius; + final float maxRadius = getMaxRadius(); + mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius; + mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius; invalidateOutline(); mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight); } @Override - public float getCurrentTopRoundness() { + public float getTopRoundness() { if (mExpandAnimationRunning) { return mTopRoundnessDuringLaunchAnimation; } - return super.getCurrentTopRoundness(); + return super.getTopRoundness(); } @Override - public float getCurrentBottomRoundness() { + public float getBottomRoundness() { if (mExpandAnimationRunning) { return mBottomRoundnessDuringLaunchAnimation; } - return super.getCurrentBottomRoundness(); + return super.getBottomRoundness(); } public void setExpandAnimationRunning(boolean expandAnimationRunning) { @@ -2284,7 +2341,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * Set this notification to be expanded by the user * - * @param userExpanded whether the user wants this notification to be expanded + * @param userExpanded whether the user wants this notification to be expanded * @param allowChildExpansion whether a call to this method allows expanding children */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { @@ -2434,7 +2491,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * @return {@code true} if the notification can show it's heads up layout. This is mostly true - * except for legacy use cases. + * except for legacy use cases. */ public boolean canShowHeadsUp() { if (mOnKeyguard && !isDozing() && !isBypassEnabled()) { @@ -2625,7 +2682,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, - long duration) { + long duration) { if (getVisibility() == GONE) { // If we are GONE, the hideSensitive parameter will not be calculated and always be // false, which is incorrect, let's wait until a real call comes in later. @@ -2658,9 +2715,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void animateShowingPublic(long delay, long duration, boolean showingPublic) { View[] privateViews = mIsSummaryWithChildren - ? new View[] {mChildrenContainer} - : new View[] {mPrivateLayout}; - View[] publicViews = new View[] {mPublicLayout}; + ? new View[]{mChildrenContainer} + : new View[]{mPrivateLayout}; + View[] publicViews = new View[]{mPublicLayout}; View[] hiddenChildren = showingPublic ? privateViews : publicViews; View[] shownChildren = showingPublic ? publicViews : privateViews; for (final View hiddenView : hiddenChildren) { @@ -2693,8 +2750,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as - * otherwise some state might not be updated. To request about the general clearability - * see {@link NotificationEntry#isDismissable()}. + * otherwise some state might not be updated. To request about the general clearability + * see {@link NotificationEntry#isDismissable()}. */ public boolean canViewBeDismissed() { return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); @@ -2777,8 +2834,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - public long performRemoveAnimation(long duration, long delay, float translationDirection, - boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, + public long performRemoveAnimation( + long duration, + long delay, + float translationDirection, + boolean isHeadsUpAnimation, + float endLocation, + Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { Animator anim = getTranslateViewAnimator(0f, null /* listener */); @@ -2828,7 +2890,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Gets the last value set with {@link #setNotificationFaded(boolean)} */ + /** + * Gets the last value set with {@link #setNotificationFaded(boolean)} + */ @Override public boolean isNotificationFaded() { return mIsFaded; @@ -2843,7 +2907,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * notifications return false from {@link #hasOverlappingRendering()} and delegate the * layerType to child views which really need it in order to render correctly, such as icon * views or the conversation face pile. - * + * <p> * Another compounding factor for notifications is that we change clipping on each frame of the * animation, so the hardware layer isn't able to do any caching at the top level, but the * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we @@ -2869,7 +2933,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Private helper for iterating over the layouts and children containers to set faded state */ + /** + * Private helper for iterating over the layouts and children containers to set faded state + */ private void setNotificationFadedOnChildren(boolean faded) { delegateNotificationFaded(mChildrenContainer, faded); for (NotificationContentView layout : mLayouts) { @@ -2897,7 +2963,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the * row should require overlapping rendering to ensure that the overlapped view doesn't bleed * through when alpha fading. - * + * <p> * Note that this currently works for top-level notifications which squish their height down * while collapsing the shade, but does not work for children inside groups, because the * accordion affect does not apply to those views, so super.hasOverlappingRendering() will @@ -2976,7 +3042,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mGuts.getIntrinsicHeight(); } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) { - return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); + return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { return mChildrenContainer.getMinHeight(); } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) { @@ -3218,8 +3284,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); if (snoozeMenu != null) { AccessibilityAction action = new AccessibilityAction(R.id.action_snooze, - getContext().getResources() - .getString(R.string.notification_menu_snooze_action)); + getContext().getResources() + .getString(R.string.notification_menu_snooze_action)); info.addAction(action); } } @@ -3280,17 +3346,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationContentView contentView = (NotificationContentView) child; if (isClippingNeeded()) { return true; - } else if (!hasNoRounding() - && contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f, - getCurrentBottomRoundness() != 0.0f)) { + } else if (hasRoundedCorner() + && contentView.shouldClipToRounding(getTopRoundness() != 0.0f, + getBottomRoundness() != 0.0f)) { return true; } } else if (child == mChildrenContainer) { - if (isClippingNeeded() || !hasNoRounding()) { + if (isClippingNeeded() || hasRoundedCorner()) { return true; } } else if (child instanceof NotificationGuts) { - return !hasNoRounding(); + return hasRoundedCorner(); } return super.childNeedsClipping(child); } @@ -3316,14 +3382,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - protected void applyRoundness() { + public void applyRoundness() { super.applyRoundness(); applyChildrenRoundness(); } private void applyChildrenRoundness() { if (mIsSummaryWithChildren) { - mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness()); + mChildrenContainer.requestBottomRoundness( + getBottomRoundness(), + /* animate = */ false, + SourceType.DefaultValue); } } @@ -3335,10 +3404,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return super.getCustomClipPath(child); } - private boolean hasNoRounding() { - return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f; - } - public boolean isMediaRow() { return mEntry.getSbn().getNotification().isMediaNotification(); } @@ -3434,6 +3499,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public interface LongPressListener { /** * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates + * * @return whether the longpress was handled */ boolean onLongPress(View v, int x, int y, MenuItem item); @@ -3455,6 +3521,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public interface CoordinateOnClickListener { /** * Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates + * * @return whether the click was handled */ boolean onClick(View v, int x, int y, MenuItem item); @@ -3511,7 +3578,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void setTargetPoint(Point p) { mTargetPoint = p; } + public Point getTargetPoint() { return mTargetPoint; } + + /** + * Enable the support for rounded corner in notification group + * @param enabled true if is supported + */ + public void enableNotificationGroupCorner(boolean enabled) { + mIsNotificationGroupCornerEnabled = enabled; + if (mChildrenContainer != null) { + mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index a493a676e3d8..842526ee0371 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -231,6 +231,8 @@ public class ExpandableNotificationRowController implements NotifViewController mStatusBarStateController.removeCallback(mStatusBarStateListener); } }); + mView.enableNotificationGroupCorner( + mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)); } private final StatusBarStateController.StateListener mStatusBarStateListener = 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 d58fe3b3c4a3..4fde5d06f816 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 @@ -28,46 +28,21 @@ import android.view.View; import android.view.ViewOutlineProvider; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.AnimatableProperty; -import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.notification.RoundableState; +import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; /** * Like {@link ExpandableView}, but setting an outline for the height and clipping. */ public abstract class ExpandableOutlineView extends ExpandableView { - private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from( - "topRoundness", - ExpandableOutlineView::setTopRoundnessInternal, - ExpandableOutlineView::getCurrentTopRoundness, - R.id.top_roundess_animator_tag, - R.id.top_roundess_animator_end_tag, - R.id.top_roundess_animator_start_tag); - private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from( - "bottomRoundness", - ExpandableOutlineView::setBottomRoundnessInternal, - ExpandableOutlineView::getCurrentBottomRoundness, - R.id.bottom_roundess_animator_tag, - R.id.bottom_roundess_animator_end_tag, - R.id.bottom_roundess_animator_start_tag); - private static final AnimationProperties ROUNDNESS_PROPERTIES = - new AnimationProperties().setDuration( - StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS); + private RoundableState mRoundableState; private static final Path EMPTY_PATH = new Path(); - private final Rect mOutlineRect = new Rect(); - private final Path mClipPath = new Path(); private boolean mCustomOutline; private float mOutlineAlpha = -1f; - protected float mOutlineRadius; private boolean mAlwaysRoundBothCorners; private Path mTmpPath = new Path(); - private float mCurrentBottomRoundness; - private float mCurrentTopRoundness; - private float mBottomRoundness; - private float mTopRoundness; private int mBackgroundTop; /** @@ -80,8 +55,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - if (!mCustomOutline && getCurrentTopRoundness() == 0.0f - && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) { + if (!mCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) { // 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); @@ -99,14 +73,18 @@ public abstract class ExpandableOutlineView extends ExpandableView { } }; + @Override + public RoundableState getRoundableState() { + return mRoundableState; + } + protected Path getClipPath(boolean ignoreTranslation) { int left; int top; int right; int bottom; int height; - float topRoundness = mAlwaysRoundBothCorners - ? mOutlineRadius : getCurrentBackgroundRadiusTop(); + float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius(); if (!mCustomOutline) { // The outline just needs to be shifted if we're translating the contents. Otherwise // it's already in the right place. @@ -130,12 +108,11 @@ public abstract class ExpandableOutlineView extends ExpandableView { if (height == 0) { return EMPTY_PATH; } - float bottomRoundness = mAlwaysRoundBothCorners - ? mOutlineRadius : getCurrentBackgroundRadiusBottom(); + float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius(); if (topRoundness + bottomRoundness > height) { float overShoot = topRoundness + bottomRoundness - height; - float currentTopRoundness = getCurrentTopRoundness(); - float currentBottomRoundness = getCurrentBottomRoundness(); + float currentTopRoundness = getTopRoundness(); + float currentBottomRoundness = getBottomRoundness(); topRoundness -= overShoot * currentTopRoundness / (currentTopRoundness + currentBottomRoundness); bottomRoundness -= overShoot * currentBottomRoundness @@ -145,8 +122,18 @@ public abstract class ExpandableOutlineView extends ExpandableView { return mTmpPath; } - public void getRoundedRectPath(int left, int top, int right, int bottom, - float topRoundness, float bottomRoundness, Path outPath) { + /** + * Add a round rect in {@code outPath} + * @param outPath destination path + */ + public void getRoundedRectPath( + int left, + int top, + int right, + int bottom, + float topRoundness, + float bottomRoundness, + Path outPath) { outPath.reset(); mTmpCornerRadii[0] = topRoundness; mTmpCornerRadii[1] = topRoundness; @@ -168,15 +155,28 @@ public abstract class ExpandableOutlineView extends ExpandableView { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { canvas.save(); + Path clipPath = null; + Path childClipPath = null; if (childNeedsClipping(child)) { - Path clipPath = getCustomClipPath(child); + clipPath = getCustomClipPath(child); if (clipPath == null) { clipPath = getClipPath(false /* ignoreTranslation */); } - if (clipPath != null) { - canvas.clipPath(clipPath); + // If the notification uses "RowTranslationX" as dismiss behavior, we should clip the + // children instead. + if (mDismissUsingRowTranslationX && child instanceof NotificationChildrenContainer) { + childClipPath = clipPath; + clipPath = null; } } + + if (child instanceof NotificationChildrenContainer) { + ((NotificationChildrenContainer) child).setChildClipPath(childClipPath); + } + if (clipPath != null) { + canvas.clipPath(clipPath); + } + boolean result = super.drawChild(canvas, child, drawingTime); canvas.restore(); return result; @@ -207,73 +207,21 @@ public abstract class ExpandableOutlineView extends ExpandableView { private void initDimens() { Resources res = getResources(); - mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius); mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); - if (!mAlwaysRoundBothCorners) { - mOutlineRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); + float maxRadius; + if (mAlwaysRoundBothCorners) { + maxRadius = res.getDimension(R.dimen.notification_shadow_radius); + } else { + maxRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); } + mRoundableState = new RoundableState(this, this, maxRadius); setClipToOutline(mAlwaysRoundBothCorners); } @Override - public boolean setTopRoundness(float topRoundness, boolean animate) { - if (mTopRoundness != topRoundness) { - float diff = Math.abs(topRoundness - mTopRoundness); - mTopRoundness = topRoundness; - boolean shouldAnimate = animate; - if (PropertyAnimator.isAnimating(this, TOP_ROUNDNESS) && diff > 0.5f) { - // Fail safe: - // when we've been animating previously and we're now getting an update in the - // other direction, make sure to animate it too, otherwise, the localized updating - // may make the start larger than 1.0. - shouldAnimate = true; - } - PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness, - ROUNDNESS_PROPERTIES, shouldAnimate); - return true; - } - return false; - } - - protected void applyRoundness() { + public void applyRoundness() { invalidateOutline(); - invalidate(); - } - - public float getCurrentBackgroundRadiusTop() { - return getCurrentTopRoundness() * mOutlineRadius; - } - - public float getCurrentTopRoundness() { - return mCurrentTopRoundness; - } - - public float getCurrentBottomRoundness() { - return mCurrentBottomRoundness; - } - - public float getCurrentBackgroundRadiusBottom() { - return getCurrentBottomRoundness() * mOutlineRadius; - } - - @Override - public boolean setBottomRoundness(float bottomRoundness, boolean animate) { - if (mBottomRoundness != bottomRoundness) { - float diff = Math.abs(bottomRoundness - mBottomRoundness); - mBottomRoundness = bottomRoundness; - boolean shouldAnimate = animate; - if (PropertyAnimator.isAnimating(this, BOTTOM_ROUNDNESS) && diff > 0.5f) { - // Fail safe: - // when we've been animating previously and we're now getting an update in the - // other direction, make sure to animate it too, otherwise, the localized updating - // may make the start larger than 1.0. - shouldAnimate = true; - } - PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness, - ROUNDNESS_PROPERTIES, shouldAnimate); - return true; - } - return false; + super.applyRoundness(); } protected void setBackgroundTop(int backgroundTop) { @@ -283,16 +231,6 @@ public abstract class ExpandableOutlineView extends ExpandableView { } } - private void setTopRoundnessInternal(float topRoundness) { - mCurrentTopRoundness = topRoundness; - applyRoundness(); - } - - private void setBottomRoundnessInternal(float bottomRoundness) { - mCurrentBottomRoundness = bottomRoundness; - applyRoundness(); - } - public void onDensityOrFontScaleChanged() { initDimens(); applyRoundness(); @@ -348,9 +286,10 @@ 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. + * translationX, otherwise the contents will be + * translated. */ public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { mDismissUsingRowTranslationX = usingRowTranslationX; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 38f0c550d4fc..955d7c18f870 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -36,6 +36,8 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.Roundable; +import com.android.systemui.statusbar.notification.RoundableState; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.util.DumpUtilsKt; @@ -47,9 +49,10 @@ import java.util.List; /** * An abstract view for expandable views. */ -public abstract class ExpandableView extends FrameLayout implements Dumpable { +public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable { private static final String TAG = "ExpandableView"; + private RoundableState mRoundableState = null; protected OnHeightChangedListener mOnHeightChangedListener; private int mActualHeight; protected int mClipTopAmount; @@ -78,6 +81,14 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { initDimens(); } + @Override + public RoundableState getRoundableState() { + if (mRoundableState == null) { + mRoundableState = new RoundableState(this, this, 0f); + } + return mRoundableState; + } + private void initDimens() { mContentShift = getResources().getDimensionPixelSize( R.dimen.shelf_transform_content_shift); @@ -440,8 +451,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { int top = getClipTopAmount(); int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding() - mClipBottomAmount, top), mMinimumHeightForClipping); - int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); - mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom); + mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom); setClipBounds(mClipRect); } else { setClipBounds(null); @@ -455,7 +465,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { public void setExtraWidthForClipping(float extraWidthForClipping) { mExtraWidthForClipping = extraWidthForClipping; - updateClipping(); } public float getHeaderVisibleAmount() { @@ -844,22 +853,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { return mFirstInSection; } - /** - * Set the topRoundness of this view. - * @return Whether the roundness was changed. - */ - public boolean setTopRoundness(float topRoundness, boolean animate) { - return false; - } - - /** - * Set the bottom roundness of this view. - * @return Whether the roundness was changed. - */ - public boolean setBottomRoundness(float bottomRoundness, boolean animate) { - return false; - } - public int getHeadsUpHeightWithoutHeader() { return getHeight(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 7a654365e0ae..f13e48d55ae4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -35,12 +35,15 @@ import androidx.annotation.Nullable; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.NotificationExpandButton; +import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.ViewTransformationHelper; import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation; import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.ImageTransformState; +import com.android.systemui.statusbar.notification.Roundable; +import com.android.systemui.statusbar.notification.RoundableState; import com.android.systemui.statusbar.notification.TransformState; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -49,13 +52,12 @@ import java.util.Stack; /** * Wraps a notification view which may or may not include a header. */ -public class NotificationHeaderViewWrapper extends NotificationViewWrapper { +public class NotificationHeaderViewWrapper extends NotificationViewWrapper implements Roundable { + private final RoundableState mRoundableState; private static final Interpolator LOW_PRIORITY_HEADER_CLOSE = new PathInterpolator(0.4f, 0f, 0.7f, 1f); - protected final ViewTransformationHelper mTransformationHelper; - private CachingIconView mIcon; private NotificationExpandButton mExpandButton; private View mAltExpandTarget; @@ -67,12 +69,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private ImageView mWorkProfileImage; private View mAudiblyAlertedIcon; private View mFeedbackIcon; - private boolean mIsLowPriority; private boolean mTransformLowPriorityTitle; protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); + mRoundableState = new RoundableState( + mView, + this, + ctx.getResources().getDimension(R.dimen.notification_corner_radius) + ); mTransformationHelper = new ViewTransformationHelper(); // we want to avoid that the header clashes with the other text when transforming @@ -81,7 +87,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) { @Override - public Interpolator getCustomInterpolator(int interpolationType, + public Interpolator getCustomInterpolator( + int interpolationType, boolean isFrom) { boolean isLowPriority = mView instanceof NotificationHeaderView; if (interpolationType == TRANSFORM_Y) { @@ -99,11 +106,17 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected boolean hasCustomTransformation() { return mIsLowPriority && mTransformLowPriorityTitle; } - }, TRANSFORMING_VIEW_TITLE); + }, + TRANSFORMING_VIEW_TITLE); resolveHeaderViews(); addFeedbackOnClickListener(row); } + @Override + public RoundableState getRoundableState() { + return mRoundableState; + } + protected void resolveHeaderViews() { mIcon = mView.findViewById(com.android.internal.R.id.icon); mHeaderText = mView.findViewById(com.android.internal.R.id.header_text); @@ -128,7 +141,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } } - /** Shows the given feedback icon, or hides the icon if null. */ + /** + * Shows the given feedback icon, or hides the icon if null. + */ @Override public void setFeedbackIcon(@Nullable FeedbackIcon icon) { if (mFeedbackIcon != null) { @@ -193,7 +208,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { // its animation && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) { ((ImageView) child).setCropToPadding(true); - } else if (child instanceof ViewGroup){ + } else if (child instanceof ViewGroup) { ViewGroup group = (ViewGroup) child; for (int i = 0; i < group.getChildCount(); i++) { stack.push(group.getChildAt(i)); @@ -215,7 +230,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override - public void updateExpandability(boolean expandable, View.OnClickListener onClickListener, + public void updateExpandability( + boolean expandable, + View.OnClickListener onClickListener, boolean requestLayout) { mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); mExpandButton.setOnClickListener(expandable ? onClickListener : null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 7b23a56af836..26f0ad9eca87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -21,6 +21,9 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.Path.Direction; import android.graphics.drawable.ColorDrawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; @@ -33,6 +36,7 @@ import android.view.ViewGroup; import android.widget.RemoteViews; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -43,10 +47,14 @@ import com.android.systemui.statusbar.NotificationGroupingUtil; import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.Roundable; +import com.android.systemui.statusbar.notification.RoundableState; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.HybridGroupManager; import com.android.systemui.statusbar.notification.row.HybridNotificationView; +import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import java.util.ArrayList; @@ -56,7 +64,7 @@ import java.util.List; * A container containing child notifications */ public class NotificationChildrenContainer extends ViewGroup - implements NotificationFadeAware { + implements NotificationFadeAware, Roundable { private static final String TAG = "NotificationChildrenContainer"; @@ -100,9 +108,9 @@ public class NotificationChildrenContainer extends ViewGroup private boolean mEnableShadowOnChildNotifications; private NotificationHeaderView mNotificationHeader; - private NotificationViewWrapper mNotificationHeaderWrapper; + private NotificationHeaderViewWrapper mNotificationHeaderWrapper; private NotificationHeaderView mNotificationHeaderLowPriority; - private NotificationViewWrapper mNotificationHeaderWrapperLowPriority; + private NotificationHeaderViewWrapper mNotificationHeaderWrapperLowPriority; private NotificationGroupingUtil mGroupingUtil; private ViewState mHeaderViewState; private int mClipBottomAmount; @@ -110,7 +118,8 @@ public class NotificationChildrenContainer extends ViewGroup private OnClickListener mHeaderClickListener; private ViewGroup mCurrentHeader; private boolean mIsConversation; - + private Path mChildClipPath = null; + private final Path mHeaderPath = new Path(); private boolean mShowGroupCountInExpander; private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; @@ -119,6 +128,8 @@ public class NotificationChildrenContainer extends ViewGroup private float mHeaderVisibleAmount = 1.0f; private int mUntruncatedChildCount; private boolean mContainingNotificationIsFaded = false; + private RoundableState mRoundableState; + private boolean mIsNotificationGroupCornerEnabled; public NotificationChildrenContainer(Context context) { this(context, null); @@ -132,10 +143,14 @@ public class NotificationChildrenContainer extends ViewGroup this(context, attrs, defStyleAttr, 0); } - public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, + public NotificationChildrenContainer( + Context context, + AttributeSet attrs, + int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mHybridGroupManager = new HybridGroupManager(getContext()); + mRoundableState = new RoundableState(this, this, 0f); initDimens(); setClipChildren(false); } @@ -167,6 +182,12 @@ public class NotificationChildrenContainer extends ViewGroup mHybridGroupManager.initDimens(); } + @NonNull + @Override + public RoundableState getRoundableState() { + return mRoundableState; + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = @@ -271,7 +292,7 @@ public class NotificationChildrenContainer extends ViewGroup /** * Add a child notification to this view. * - * @param row the row to add + * @param row the row to add * @param childIndex the index to add it at, if -1 it will be added at the end */ public void addNotification(ExpandableNotificationRow row, int childIndex) { @@ -347,8 +368,11 @@ public class NotificationChildrenContainer extends ViewGroup mNotificationHeader.findViewById(com.android.internal.R.id.expand_button) .setVisibility(VISIBLE); mNotificationHeader.setOnClickListener(mHeaderClickListener); - mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(), - mNotificationHeader, mContainingNotification); + mNotificationHeaderWrapper = + (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap( + getContext(), + mNotificationHeader, + mContainingNotification); addView(mNotificationHeader, 0); invalidate(); } else { @@ -381,8 +405,11 @@ public class NotificationChildrenContainer extends ViewGroup mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button) .setVisibility(VISIBLE); mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener); - mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(), - mNotificationHeaderLowPriority, mContainingNotification); + mNotificationHeaderWrapperLowPriority = + (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap( + getContext(), + mNotificationHeaderLowPriority, + mContainingNotification); addView(mNotificationHeaderLowPriority, 0); invalidate(); } else { @@ -461,7 +488,9 @@ public class NotificationChildrenContainer extends ViewGroup return mAttachedChildren; } - /** To be called any time the rows have been updated */ + /** + * To be called any time the rows have been updated + */ public void updateExpansionStates() { if (mChildrenExpanded || mUserLocked) { // we don't modify it the group is expanded or if we are expanding it @@ -475,7 +504,6 @@ public class NotificationChildrenContainer extends ViewGroup } /** - * * @return the intrinsic size of this children container, i.e the natural fully expanded state */ public int getIntrinsicHeight() { @@ -485,7 +513,7 @@ public class NotificationChildrenContainer extends ViewGroup /** * @return the intrinsic height with a number of children given - * in @param maxAllowedVisibleChildren + * in @param maxAllowedVisibleChildren */ private int getIntrinsicHeight(float maxAllowedVisibleChildren) { if (showingAsLowPriority()) { @@ -539,7 +567,8 @@ public class NotificationChildrenContainer extends ViewGroup /** * Update the state of all its children based on a linear layout algorithm. - * @param parentState the state of the parent + * + * @param parentState the state of the parent * @param ambientState the ambient state containing ambient information */ public void updateState(ExpandableViewState parentState, AmbientState ambientState) { @@ -655,14 +684,17 @@ public class NotificationChildrenContainer extends ViewGroup * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its * height, children in the group after this are gone. * - * @param child the child who's height to adjust. + * @param child the child who's height to adjust. * @param parentHeight the height of the parent. - * @param childState the state to update. - * @param yPosition the yPosition of the view. + * @param childState the state to update. + * @param yPosition the yPosition of the view. * @return true if children after this one should be hidden. */ - private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child, - int parentHeight, ExpandableViewState childState, int yPosition) { + private boolean updateChildStateForExpandedGroup( + ExpandableNotificationRow child, + int parentHeight, + ExpandableViewState childState, + int yPosition) { final int top = yPosition + child.getClipTopAmount(); final int intrinsicHeight = child.getIntrinsicHeight(); final int bottom = top + intrinsicHeight; @@ -690,13 +722,15 @@ public class NotificationChildrenContainer extends ViewGroup if (mIsLowPriority || (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded()) || (mContainingNotification.isHeadsUpState() - && mContainingNotification.canShowHeadsUp())) { + && mContainingNotification.canShowHeadsUp())) { return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; } return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; } - /** Applies state to children. */ + /** + * Applies state to children. + */ public void applyState() { int childCount = mAttachedChildren.size(); ViewState tmpState = new ViewState(); @@ -768,17 +802,73 @@ public class NotificationChildrenContainer extends ViewGroup } } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean isCanvasChanged = false; + + Path clipPath = mChildClipPath; + if (clipPath != null) { + final float translation; + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child; + translation = notificationRow.getTranslation(); + } else { + translation = child.getTranslationX(); + } + + isCanvasChanged = true; + canvas.save(); + if (mIsNotificationGroupCornerEnabled && translation != 0f) { + clipPath.offset(translation, 0f); + canvas.clipPath(clipPath); + clipPath.offset(-translation, 0f); + } else { + canvas.clipPath(clipPath); + } + } + + if (child instanceof NotificationHeaderView + && mNotificationHeaderWrapper.hasRoundedCorner()) { + float[] radii = mNotificationHeaderWrapper.getUpdatedRadii(); + mHeaderPath.reset(); + mHeaderPath.addRoundRect( + child.getLeft(), + child.getTop(), + child.getRight(), + child.getBottom(), + radii, + Direction.CW + ); + if (!isCanvasChanged) { + isCanvasChanged = true; + canvas.save(); + } + canvas.clipPath(mHeaderPath); + } + + if (isCanvasChanged) { + boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return result; + } else { + // If there have been no changes to the canvas we can proceed as usual + return super.drawChild(canvas, child, drawingTime); + } + } + + /** * This is called when the children expansion has changed and positions the children properly * for an appear animation. - * */ public void prepareExpansionChanged() { // TODO: do something that makes sense, like placing the invisible views correctly return; } - /** Animate to a given state. */ + /** + * Animate to a given state. + */ public void startAnimationToState(AnimationProperties properties) { int childCount = mAttachedChildren.size(); ViewState tmpState = new ViewState(); @@ -1102,7 +1192,8 @@ public class NotificationChildrenContainer extends ViewGroup * Get the minimum Height for this group. * * @param maxAllowedVisibleChildren the number of children that should be visible - * @param likeHighPriority if the height should be calculated as if it were not low priority + * @param likeHighPriority if the height should be calculated as if it were not low + * priority */ private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) { return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation); @@ -1112,10 +1203,13 @@ public class NotificationChildrenContainer extends ViewGroup * Get the minimum Height for this group. * * @param maxAllowedVisibleChildren the number of children that should be visible - * @param likeHighPriority if the height should be calculated as if it were not low priority - * @param headerTranslation the translation amount of the header + * @param likeHighPriority if the height should be calculated as if it were not low + * priority + * @param headerTranslation the translation amount of the header */ - private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority, + private int getMinHeight( + int maxAllowedVisibleChildren, + boolean likeHighPriority, int headerTranslation) { if (!likeHighPriority && showingAsLowPriority()) { if (mNotificationHeaderLowPriority == null) { @@ -1274,16 +1368,19 @@ public class NotificationChildrenContainer extends ViewGroup return mUserLocked; } - public void setCurrentBottomRoundness(float currentBottomRoundness) { + @Override + public void applyRoundness() { + Roundable.super.applyRoundness(); boolean last = true; for (int i = mAttachedChildren.size() - 1; i >= 0; i--) { ExpandableNotificationRow child = mAttachedChildren.get(i); if (child.getVisibility() == View.GONE) { continue; } - float bottomRoundness = last ? currentBottomRoundness : 0.0f; - child.setBottomRoundness(bottomRoundness, isShown() /* animate */); - child.setTopRoundness(0.0f, false /* animate */); + child.requestBottomRoundness( + last ? getBottomRoundness() : 0f, + /* animate = */ isShown(), + SourceType.DefaultValue); last = false; } } @@ -1293,7 +1390,9 @@ public class NotificationChildrenContainer extends ViewGroup mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader); } - /** Shows the given feedback icon, or hides the icon if null. */ + /** + * Shows the given feedback icon, or hides the icon if null. + */ public void setFeedbackIcon(@Nullable FeedbackIcon icon) { if (mNotificationHeaderWrapper != null) { mNotificationHeaderWrapper.setFeedbackIcon(icon); @@ -1325,4 +1424,26 @@ public class NotificationChildrenContainer extends ViewGroup child.setNotificationFaded(faded); } } + + /** + * Allow to define a path the clip the children in #drawChild() + * + * @param childClipPath path used to clip the children + */ + public void setChildClipPath(@Nullable Path childClipPath) { + mChildClipPath = childClipPath; + invalidate(); + } + + public NotificationHeaderViewWrapper getNotificationHeaderWrapper() { + return mNotificationHeaderWrapper; + } + + /** + * Enable the support for rounded corner in notification group + * @param enabled true if is supported + */ + public void enableNotificationGroupCorner(boolean enabled) { + mIsNotificationGroupCornerEnabled = enabled; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 2015c87aac2b..6810055ad3bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -26,6 +26,8 @@ import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; +import com.android.systemui.statusbar.notification.Roundable; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -59,8 +61,8 @@ public class NotificationRoundnessManager implements Dumpable { private boolean mIsClearAllInProgress; private ExpandableView mSwipedView = null; - private ExpandableView mViewBeforeSwipedView = null; - private ExpandableView mViewAfterSwipedView = null; + private Roundable mViewBeforeSwipedView = null; + private Roundable mViewAfterSwipedView = null; @Inject NotificationRoundnessManager( @@ -101,11 +103,12 @@ public class NotificationRoundnessManager implements Dumpable { public boolean isViewAffectedBySwipe(ExpandableView expandableView) { return expandableView != null && (expandableView == mSwipedView - || expandableView == mViewBeforeSwipedView - || expandableView == mViewAfterSwipedView); + || expandableView == mViewBeforeSwipedView + || expandableView == mViewAfterSwipedView); } - boolean updateViewWithoutCallback(ExpandableView view, + boolean updateViewWithoutCallback( + ExpandableView view, boolean animate) { if (view == null || view == mViewBeforeSwipedView @@ -113,11 +116,15 @@ public class NotificationRoundnessManager implements Dumpable { return false; } - final float topRoundness = getRoundnessFraction(view, true /* top */); - final float bottomRoundness = getRoundnessFraction(view, false /* top */); + final boolean isTopChanged = view.requestTopRoundness( + getRoundnessDefaultValue(view, true /* top */), + animate, + SourceType.DefaultValue); - final boolean topChanged = view.setTopRoundness(topRoundness, animate); - final boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate); + final boolean isBottomChanged = view.requestBottomRoundness( + getRoundnessDefaultValue(view, /* top = */ false), + animate, + SourceType.DefaultValue); final boolean isFirstInSection = isFirstInSection(view); final boolean isLastInSection = isLastInSection(view); @@ -126,9 +133,9 @@ public class NotificationRoundnessManager implements Dumpable { view.setLastInSection(isLastInSection); mNotifLogger.onCornersUpdated(view, isFirstInSection, - isLastInSection, topChanged, bottomChanged); + isLastInSection, isTopChanged, isBottomChanged); - return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged); + return (isFirstInSection || isLastInSection) && (isTopChanged || isBottomChanged); } private boolean isFirstInSection(ExpandableView view) { @@ -150,42 +157,46 @@ public class NotificationRoundnessManager implements Dumpable { } void setViewsAffectedBySwipe( - ExpandableView viewBefore, + Roundable viewBefore, ExpandableView viewSwiped, - ExpandableView viewAfter) { + Roundable viewAfter) { final boolean animate = true; + final SourceType source = SourceType.OnDismissAnimation; + + // This method requires you to change the roundness of the current View targets and reset + // the roundness of the old View targets (if any) to 0f. + // To avoid conflicts, it generates a set of old Views and removes the current Views + // from this set. + HashSet<Roundable> oldViews = new HashSet<>(); + if (mViewBeforeSwipedView != null) oldViews.add(mViewBeforeSwipedView); + if (mSwipedView != null) oldViews.add(mSwipedView); + if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView); - ExpandableView oldViewBefore = mViewBeforeSwipedView; mViewBeforeSwipedView = viewBefore; - if (oldViewBefore != null) { - final float bottomRoundness = getRoundnessFraction(oldViewBefore, false /* top */); - oldViewBefore.setBottomRoundness(bottomRoundness, animate); - } if (viewBefore != null) { - viewBefore.setBottomRoundness(1f, animate); + oldViews.remove(viewBefore); + viewBefore.requestTopRoundness(0f, animate, source); + viewBefore.requestBottomRoundness(1f, animate, source); } - ExpandableView oldSwipedview = mSwipedView; mSwipedView = viewSwiped; - if (oldSwipedview != null) { - final float bottomRoundness = getRoundnessFraction(oldSwipedview, false /* top */); - final float topRoundness = getRoundnessFraction(oldSwipedview, true /* top */); - oldSwipedview.setTopRoundness(topRoundness, animate); - oldSwipedview.setBottomRoundness(bottomRoundness, animate); - } if (viewSwiped != null) { - viewSwiped.setTopRoundness(1f, animate); - viewSwiped.setBottomRoundness(1f, animate); + oldViews.remove(viewSwiped); + viewSwiped.requestTopRoundness(1f, animate, source); + viewSwiped.requestBottomRoundness(1f, animate, source); } - ExpandableView oldViewAfter = mViewAfterSwipedView; mViewAfterSwipedView = viewAfter; - if (oldViewAfter != null) { - final float topRoundness = getRoundnessFraction(oldViewAfter, true /* top */); - oldViewAfter.setTopRoundness(topRoundness, animate); - } if (viewAfter != null) { - viewAfter.setTopRoundness(1f, animate); + oldViews.remove(viewAfter); + viewAfter.requestTopRoundness(1f, animate, source); + viewAfter.requestBottomRoundness(0f, animate, source); + } + + // After setting the current Views, reset the views that are still present in the set. + for (Roundable oldView : oldViews) { + oldView.requestTopRoundness(0f, animate, source); + oldView.requestBottomRoundness(0f, animate, source); } } @@ -193,7 +204,7 @@ public class NotificationRoundnessManager implements Dumpable { mIsClearAllInProgress = isClearingAll; } - private float getRoundnessFraction(ExpandableView view, boolean top) { + private float getRoundnessDefaultValue(Roundable view, boolean top) { if (view == null) { return 0f; } @@ -207,28 +218,35 @@ public class NotificationRoundnessManager implements Dumpable { && mIsClearAllInProgress) { return 1.0f; } - if ((view.isPinned() - || (view.isHeadsUpAnimatingAway()) && !mExpanded)) { - return 1.0f; - } - if (isFirstInSection(view) && top) { - return 1.0f; - } - if (isLastInSection(view) && !top) { - return 1.0f; - } + if (view instanceof ExpandableView) { + ExpandableView expandableView = (ExpandableView) view; + if ((expandableView.isPinned() + || (expandableView.isHeadsUpAnimatingAway()) && !mExpanded)) { + return 1.0f; + } + if (isFirstInSection(expandableView) && top) { + return 1.0f; + } + if (isLastInSection(expandableView) && !top) { + return 1.0f; + } - if (view == mTrackedHeadsUp) { - // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be - // rounded. - return MathUtils.saturate(1.0f - mAppearFraction); - } - if (view.showingPulsing() && mRoundForPulsingViews) { - return 1.0f; + if (view == mTrackedHeadsUp) { + // If we're pushing up on a headsup the appear fraction is < 0 and it needs to + // still be rounded. + return MathUtils.saturate(1.0f - mAppearFraction); + } + if (expandableView.showingPulsing() && mRoundForPulsingViews) { + return 1.0f; + } + if (expandableView.isChildInGroup()) { + return 0f; + } + final Resources resources = expandableView.getResources(); + return resources.getDimension(R.dimen.notification_corner_radius_small) + / resources.getDimension(R.dimen.notification_corner_radius); } - final Resources resources = view.getResources(); - return resources.getDimension(R.dimen.notification_corner_radius_small) - / resources.getDimension(R.dimen.notification_corner_radius); + return 0f; } public void setExpanded(float expandedHeight, float appearFraction) { @@ -258,8 +276,10 @@ public class NotificationRoundnessManager implements Dumpable { mNotifLogger.onSectionCornersUpdated(sections, anyChanged); } - private boolean handleRemovedOldViews(NotificationSection[] sections, - ExpandableView[] oldViews, boolean first) { + private boolean handleRemovedOldViews( + NotificationSection[] sections, + ExpandableView[] oldViews, + boolean first) { boolean anyChanged = false; for (ExpandableView oldView : oldViews) { if (oldView != null) { @@ -289,8 +309,10 @@ public class NotificationRoundnessManager implements Dumpable { return anyChanged; } - private boolean handleAddedNewViews(NotificationSection[] sections, - ExpandableView[] oldViews, boolean first) { + private boolean handleAddedNewViews( + NotificationSection[] sections, + ExpandableView[] oldViews, + boolean first) { boolean anyChanged = false; for (NotificationSection section : sections) { ExpandableView newView = 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 2272411b4314..df705c5afeef 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 @@ -1188,7 +1188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (mChildrenToAddAnimated.contains(child)) { final int startingPosition = getPositionInLinearLayout(child); final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements; @@ -1658,7 +1658,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // find the view under the pointer, accounting for GONE views final int count = getChildCount(); for (int childIdx = 0; childIdx < count; childIdx++) { - ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); + ExpandableView slidingChild = getChildAtIndex(childIdx); if (slidingChild.getVisibility() != VISIBLE || (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) { continue; @@ -1691,6 +1691,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return null; } + private ExpandableView getChildAtIndex(int index) { + return (ExpandableView) getChildAt(index); + } + public ExpandableView getChildAtRawPosition(float touchX, float touchY) { getLocationOnScreen(mTempInt2); return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); @@ -2276,7 +2280,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable int childCount = getChildCount(); int count = 0; for (int i = 0; i < childCount; i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) { count++; } @@ -2496,7 +2500,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private ExpandableView getLastChildWithBackground() { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) && child != mShelf) { return child; @@ -2509,7 +2513,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private ExpandableView getFirstChildWithBackground() { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) && child != mShelf) { return child; @@ -2523,7 +2527,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable ArrayList<ExpandableView> children = new ArrayList<>(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) && child != mShelf) { @@ -2882,7 +2886,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } int position = 0; for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); boolean notGone = child.getVisibility() != View.GONE; if (notGone && !child.hasNoContentHeight()) { if (position != 0) { @@ -2936,7 +2940,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } mAmbientState.setLastVisibleBackgroundChild(lastChild); // TODO: Refactor SectionManager and put the RoundnessManager there. - mController.getNoticationRoundessManager().updateRoundedChildren(mSections); + mController.getNotificationRoundnessManager().updateRoundedChildren(mSections); mAnimateBottomOnLayout = false; invalidate(); } @@ -3968,7 +3972,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearUserLockedViews() { for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; row.setUserLocked(false); @@ -3981,7 +3985,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // lets make sure nothing is transient anymore clearTemporaryViewsInGroup(this); for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; clearTemporaryViewsInGroup(row.getChildrenContainer()); @@ -4230,7 +4234,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (hideSensitive != mAmbientState.isHideSensitive()) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { - ExpandableView v = (ExpandableView) getChildAt(i); + ExpandableView v = getChildAtIndex(i); v.setHideSensitiveForIntrinsicHeight(hideSensitive); } mAmbientState.setHideSensitive(hideSensitive); @@ -4265,7 +4269,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void applyCurrentState() { int numChildren = getChildCount(); for (int i = 0; i < numChildren; i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); child.applyViewState(); } @@ -4285,7 +4289,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // Lefts first sort by Z difference for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child.getVisibility() != GONE) { mTmpSortedChildren.add(child); } @@ -4512,7 +4516,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void setClearAllInProgress(boolean clearAllInProgress) { mClearAllInProgress = clearAllInProgress; mAmbientState.setClearAllInProgress(clearAllInProgress); - mController.getNoticationRoundessManager().setClearAllInProgress(clearAllInProgress); + mController.getNotificationRoundnessManager().setClearAllInProgress(clearAllInProgress); } boolean getClearAllInProgress() { @@ -4555,7 +4559,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final int count = getChildCount(); float max = 0; for (int childIdx = 0; childIdx < count; childIdx++) { - ExpandableView child = (ExpandableView) getChildAt(childIdx); + ExpandableView child = getChildAtIndex(childIdx); if (child.getVisibility() == GONE) { continue; } @@ -4586,7 +4590,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public boolean isBelowLastNotification(float touchX, float touchY) { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); if (child.getVisibility() != View.GONE) { float childTop = child.getY(); if (childTop > touchY) { @@ -5052,7 +5056,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable pw.println(); for (int i = 0; i < childCount; i++) { - ExpandableView child = (ExpandableView) getChildAt(i); + ExpandableView child = getChildAtIndex(i); child.dump(pw, args); pw.println(); } @@ -5341,7 +5345,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable float wakeUplocation = -1f; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { - ExpandableView view = (ExpandableView) getChildAt(i); + ExpandableView view = getChildAtIndex(i); if (view.getVisibility() == View.GONE) { continue; } @@ -5380,7 +5384,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void setController( NotificationStackScrollLayoutController notificationStackScrollLayoutController) { mController = notificationStackScrollLayoutController; - mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated); + mController.getNotificationRoundnessManager().setAnimatedChildren(mChildrenToAddAnimated); } void addSwipedOutView(View v) { @@ -5391,31 +5395,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (!(viewSwiped instanceof ExpandableNotificationRow)) { return; } - final int indexOfSwipedView = indexOfChild(viewSwiped); - if (indexOfSwipedView < 0) { - return; - } mSectionsManager.updateFirstAndLastViewsForAllSections( - mSections, getChildrenWithBackground()); - View viewBefore = null; - if (indexOfSwipedView > 0) { - viewBefore = getChildAt(indexOfSwipedView - 1); - if (mSectionsManager.beginsSection(viewSwiped, viewBefore)) { - viewBefore = null; - } - } - View viewAfter = null; - if (indexOfSwipedView < getChildCount()) { - viewAfter = getChildAt(indexOfSwipedView + 1); - if (mSectionsManager.beginsSection(viewAfter, viewSwiped)) { - viewAfter = null; - } - } - mController.getNoticationRoundessManager() + mSections, + getChildrenWithBackground() + ); + + RoundableTargets targets = mController.getNotificationTargetsHelper().findRoundableTargets( + (ExpandableNotificationRow) viewSwiped, + this, + mSectionsManager + ); + + mController.getNotificationRoundnessManager() .setViewsAffectedBySwipe( - (ExpandableView) viewBefore, - (ExpandableView) viewSwiped, - (ExpandableView) viewAfter); + targets.getBefore(), + targets.getSwiped(), + targets.getAfter()); updateFirstAndLastBackgroundViews(); requestDisallowInterceptTouchEvent(true); @@ -5426,7 +5421,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable void onSwipeEnd() { updateFirstAndLastBackgroundViews(); - mController.getNoticationRoundessManager() + mController.getNotificationRoundnessManager() .setViewsAffectedBySwipe(null, null, null); // Round bottom corners for notification right before shelf. mShelf.updateAppearance(); 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 bde98ff73706..e13378269ba6 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 @@ -180,6 +180,7 @@ public class NotificationStackScrollLayoutController { private int mBarState; private HeadsUpAppearanceController mHeadsUpAppearanceController; private final FeatureFlags mFeatureFlags; + private final NotificationTargetsHelper mNotificationTargetsHelper; private View mLongPressedView; @@ -642,7 +643,8 @@ public class NotificationStackScrollLayoutController { StackStateLogger stackLogger, NotificationStackScrollLogger logger, NotificationStackSizeCalculator notificationStackSizeCalculator, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + NotificationTargetsHelper notificationTargetsHelper) { mStackStateLogger = stackLogger; mLogger = logger; mAllowLongPress = allowLongPress; @@ -679,6 +681,7 @@ public class NotificationStackScrollLayoutController { mRemoteInputManager = remoteInputManager; mShadeController = shadeController; mFeatureFlags = featureFlags; + mNotificationTargetsHelper = notificationTargetsHelper; updateResources(); } @@ -1380,7 +1383,7 @@ public class NotificationStackScrollLayoutController { return mView.calculateGapHeight(previousView, child, count); } - NotificationRoundnessManager getNoticationRoundessManager() { + NotificationRoundnessManager getNotificationRoundnessManager() { return mNotificationRoundnessManager; } @@ -1537,6 +1540,10 @@ public class NotificationStackScrollLayoutController { mNotificationActivityStarter = activityStarter; } + public NotificationTargetsHelper getNotificationTargetsHelper() { + return mNotificationTargetsHelper; + } + /** * Enum for UiEvent logged from this class */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt new file mode 100644 index 000000000000..991a14bb9c2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt @@ -0,0 +1,100 @@ +package com.android.systemui.statusbar.notification.stack + +import androidx.core.view.children +import androidx.core.view.isVisible +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.Roundable +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.ExpandableView +import javax.inject.Inject + +/** + * Utility class that helps us find the targets of an animation, often used to find the notification + * ([Roundable]) above and below the current one (see [findRoundableTargets]). + */ +@SysUISingleton +class NotificationTargetsHelper +@Inject +constructor( + featureFlags: FeatureFlags, +) { + private val isNotificationGroupCornerEnabled = + featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER) + + /** + * This method looks for views that can be rounded (and implement [Roundable]) during a + * notification swipe. + * @return The [Roundable] targets above/below the [viewSwiped] (if available). The + * [RoundableTargets.before] and [RoundableTargets.after] parameters can be `null` if there is + * no above/below notification or the notification is not part of the same section. + */ + fun findRoundableTargets( + viewSwiped: ExpandableNotificationRow, + stackScrollLayout: NotificationStackScrollLayout, + sectionsManager: NotificationSectionsManager, + ): RoundableTargets { + val viewBefore: Roundable? + val viewAfter: Roundable? + + val notificationParent = viewSwiped.notificationParent + val childrenContainer = notificationParent?.childrenContainer + val visibleStackChildren = + stackScrollLayout.children + .filterIsInstance<ExpandableView>() + .filter { it.isVisible } + .toList() + if (notificationParent != null && childrenContainer != null) { + // We are inside a notification group + + if (!isNotificationGroupCornerEnabled) { + return RoundableTargets(null, null, null) + } + + val visibleGroupChildren = childrenContainer.attachedChildren.filter { it.isVisible } + val indexOfParentSwipedView = visibleGroupChildren.indexOf(viewSwiped) + + viewBefore = + visibleGroupChildren.getOrNull(indexOfParentSwipedView - 1) + ?: childrenContainer.notificationHeaderWrapper + + viewAfter = + visibleGroupChildren.getOrNull(indexOfParentSwipedView + 1) + ?: visibleStackChildren.indexOf(notificationParent).let { + visibleStackChildren.getOrNull(it + 1) + } + } else { + // Assumption: we are inside the NotificationStackScrollLayout + + val indexOfSwipedView = visibleStackChildren.indexOf(viewSwiped) + + viewBefore = + visibleStackChildren.getOrNull(indexOfSwipedView - 1)?.takeIf { + !sectionsManager.beginsSection(viewSwiped, it) + } + + viewAfter = + visibleStackChildren.getOrNull(indexOfSwipedView + 1)?.takeIf { + !sectionsManager.beginsSection(it, viewSwiped) + } + } + + return RoundableTargets( + before = viewBefore, + swiped = viewSwiped, + after = viewAfter, + ) + } +} + +/** + * This object contains targets above/below the [swiped] (if available). The [before] and [after] + * parameters can be `null` if there is no above/below notification or the notification is not part + * of the same section. + */ +data class RoundableTargets( + val before: Roundable?, + val swiped: ExpandableNotificationRow?, + val after: Roundable?, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 0502159f46cd..eea1d9118fb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -31,6 +31,7 @@ import com.android.systemui.R; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -804,7 +805,7 @@ public class StackScrollAlgorithm { row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius); final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(), ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius); - row.setBottomRoundness(roundness, /* animate= */ false); + row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll); } @VisibleForTesting diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index a95a49c31adf..8c8b64424814 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -147,8 +147,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f); } @Test @@ -170,13 +170,13 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true); row.setHeadsUp(true); mRoundnessManager.updateView(entry.getRow(), false); - Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1f, row.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1f, row.getBottomRoundness(), 0.0f); + Assert.assertEquals(1f, row.getTopRoundness(), 0.0f); row.setHeadsUp(false); mRoundnessManager.updateView(entry.getRow(), false); - Assert.assertEquals(mSmallRadiusRatio, row.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(mSmallRadiusRatio, row.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, row.getBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, row.getTopRoundness(), 0.0f); } @Test @@ -185,8 +185,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mFirst), createSection(null, mSecond) }); - Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f); } @Test @@ -195,8 +195,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mFirst), createSection(mSecond, null) }); - Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mSecond.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mSecond.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mSecond.getTopRoundness(), 0.0f); } @Test @@ -205,8 +205,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, null), createSection(null, null) }); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f); } @Test @@ -215,8 +215,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f); } @Test @@ -226,8 +226,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f); } @Test @@ -238,8 +238,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f); } @Test @@ -250,8 +250,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f); } @Test @@ -262,8 +262,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f); } @Test @@ -274,8 +274,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f); } @Test @@ -286,8 +286,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(0.5f, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(0.5f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(0.5f, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(0.5f, mFirst.getTopRoundness(), 0.0f); } @Test @@ -298,8 +298,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(null, null) }); mFirst.setHeadsUpAnimatingAway(true); - Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f); } @@ -312,8 +312,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { }); mFirst.setHeadsUpAnimatingAway(true); mFirst.setHeadsUpAnimatingAway(false); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index a3c95dcaeb3c..90061b078afe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -129,6 +129,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; @Mock private ShadeTransitionController mShadeTransitionController; @Mock private FeatureFlags mFeatureFlags; + @Mock private NotificationTargetsHelper mNotificationTargetsHelper; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; @@ -177,7 +178,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mStackLogger, mLogger, mNotificationStackSizeCalculator, - mFeatureFlags + mFeatureFlags, + mNotificationTargetsHelper ); when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 35c8b61b6383..91aecd8cf753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -163,7 +163,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.setCentralSurfaces(mCentralSurfaces); mStackScroller.setEmptyShadeView(mEmptyShadeView); when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true); - when(mStackScrollLayoutController.getNoticationRoundessManager()) + when(mStackScrollLayoutController.getNotificationRoundnessManager()) .thenReturn(mNotificationRoundnessManager); mStackScroller.setController(mStackScrollLayoutController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt new file mode 100644 index 000000000000..a2e92305bf27 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt @@ -0,0 +1,107 @@ +package com.android.systemui.statusbar.notification.stack + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.android.systemui.util.mockito.mock +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for {@link NotificationTargetsHelper}. */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationTargetsHelperTest : SysuiTestCase() { + lateinit var notificationTestHelper: NotificationTestHelper + private val sectionsManager: NotificationSectionsManager = mock() + private val stackScrollLayout: NotificationStackScrollLayout = mock() + + @Before + fun setUp() { + allowTestableLooperAsMainThread() + notificationTestHelper = + NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + } + + private fun notificationTargetsHelper( + notificationGroupCorner: Boolean = true, + ) = + NotificationTargetsHelper( + FakeFeatureFlags().apply { + set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner) + } + ) + + @Test + fun targetsForFirstNotificationInGroup() { + val children = notificationTestHelper.createGroup(3).childrenContainer + val swiped = children.attachedChildren[0] + + val actual = + notificationTargetsHelper() + .findRoundableTargets( + viewSwiped = swiped, + stackScrollLayout = stackScrollLayout, + sectionsManager = sectionsManager, + ) + + val expected = + RoundableTargets( + before = children.notificationHeaderWrapper, // group header + swiped = swiped, + after = children.attachedChildren[1], + ) + assertEquals(expected, actual) + } + + @Test + fun targetsForMiddleNotificationInGroup() { + val children = notificationTestHelper.createGroup(3).childrenContainer + val swiped = children.attachedChildren[1] + + val actual = + notificationTargetsHelper() + .findRoundableTargets( + viewSwiped = swiped, + stackScrollLayout = stackScrollLayout, + sectionsManager = sectionsManager, + ) + + val expected = + RoundableTargets( + before = children.attachedChildren[0], + swiped = swiped, + after = children.attachedChildren[2], + ) + assertEquals(expected, actual) + } + + @Test + fun targetsForLastNotificationInGroup() { + val children = notificationTestHelper.createGroup(3).childrenContainer + val swiped = children.attachedChildren[2] + + val actual = + notificationTargetsHelper() + .findRoundableTargets( + viewSwiped = swiped, + stackScrollLayout = stackScrollLayout, + sectionsManager = sectionsManager, + ) + + val expected = + RoundableTargets( + before = children.attachedChildren[1], + swiped = swiped, + after = null, + ) + assertEquals(expected, actual) + } +} |