summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Omar Miatello <omarmt@google.com> 2022-10-24 18:51:07 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-10-24 18:51:07 +0000
commit46b33d3f81a3af6dd3a6876476dcfa41250eea73 (patch)
tree296a38260784df579f5f031a46f6e18805f54a71
parent6117424fe383eda3c6e0f66b2404735245729d52 (diff)
parentecf8e840eb410839301feffa497a3b88c003ff50 (diff)
Merge "Add support for rounded notification in groups" into tm-qpr-dev
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt284
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java213
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java165
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java185
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java140
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt107
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)
+ }
+}