Merge "Add support for rounded notification in groups" into tm-qpr-dev am: 46b33d3f81

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20064744

Change-Id: I1c3a4c808d788ab17baede6457c240bc095f99a6
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Omar Miatello
2022-10-24 20:21:16 +00:00
committed by Automerger Merge Worker
20 changed files with 1081 additions and 403 deletions

View File

@@ -63,7 +63,8 @@ object Flags {
@JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true) @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true) val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
val SEMI_STABLE_SORT = UnreleasedFlag(115, 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 // 200 - keyguard/lockscreen
// ** Flag retired ** // ** Flag retired **

View File

@@ -40,6 +40,7 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils; 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.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -110,8 +111,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
setClipChildren(false); setClipChildren(false);
setClipToPadding(false); setClipToPadding(false);
mShelfIcons.setIsStaticLayout(false); mShelfIcons.setIsStaticLayout(false);
setBottomRoundness(1.0f, false /* animate */); requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
setTopRoundness(1f, false /* animate */); requestTopRoundness(1f, false, SourceType.DefaultValue);
// Setting this to first in section to get the clipping to the top roundness correct. This // 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 // 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) { if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
// only if the first icon is fully in the shelf we want to clip to it! // only if the first icon is fully in the shelf we want to clip to it!
backgroundTop = (int) (child.getTranslationY() - getTranslationY()); 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 // Round bottom corners within animation bounds
final float changeFraction = MathUtils.saturate( final float changeFraction = MathUtils.saturate(
(viewEnd - cornerAnimationTop) / cornerAnimationDistance); (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
anv.setBottomRoundness(anv.isLastInSection() ? 1f : changeFraction, anv.requestBottomRoundness(
false /* animate */); anv.isLastInSection() ? 1f : changeFraction,
/* animate = */ false,
SourceType.OnScroll);
} else if (viewEnd < cornerAnimationTop) { } else if (viewEnd < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding. // Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds. // Reset top and bottom corners outside of animation bounds.
anv.setBottomRoundness(anv.isLastInSection() ? 1f : smallCornerRadius, anv.requestBottomRoundness(
false /* animate */); anv.isLastInSection() ? 1f : smallCornerRadius,
/* animate = */ false,
SourceType.OnScroll);
} }
if (viewStart >= cornerAnimationTop) { if (viewStart >= cornerAnimationTop) {
// Round top corners within animation bounds // Round top corners within animation bounds
final float changeFraction = MathUtils.saturate( final float changeFraction = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance); (viewStart - cornerAnimationTop) / cornerAnimationDistance);
anv.setTopRoundness(anv.isFirstInSection() ? 1f : changeFraction, anv.requestTopRoundness(
false /* animate */); anv.isFirstInSection() ? 1f : changeFraction,
false,
SourceType.OnScroll);
} else if (viewStart < cornerAnimationTop) { } else if (viewStart < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding. // Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds. // Reset top and bottom corners outside of animation bounds.
anv.setTopRoundness(anv.isFirstInSection() ? 1f : smallCornerRadius, anv.requestTopRoundness(
false /* animate */); anv.isFirstInSection() ? 1f : smallCornerRadius,
false,
SourceType.OnScroll);
} }
} }

View File

@@ -70,8 +70,8 @@ class NotificationLaunchAnimatorController(
val height = max(0, notification.actualHeight - notification.clipBottomAmount) val height = max(0, notification.actualHeight - notification.clipBottomAmount)
val location = notification.locationOnScreen val location = notification.locationOnScreen
val clipStartLocation = notificationListContainer.getTopClippingStartLocation() val clipStartLocation = notificationListContainer.topClippingStartLocation
val roundedTopClipping = Math.max(clipStartLocation - location[1], 0) val roundedTopClipping = (clipStartLocation - location[1]).coerceAtLeast(0)
val windowTop = location[1] + roundedTopClipping val windowTop = location[1] + roundedTopClipping
val topCornerRadius = if (roundedTopClipping > 0) { val topCornerRadius = if (roundedTopClipping > 0) {
// Because the rounded Rect clipping is complex, we start the top rounding at // 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. // if we'd like to have this perfect, but this is close enough.
0f 0f
} else { } else {
notification.currentBackgroundRadiusTop notification.topCornerRadius
} }
val params = LaunchAnimationParameters( val params = LaunchAnimationParameters(
top = windowTop, top = windowTop,
@@ -88,7 +88,7 @@ class NotificationLaunchAnimatorController(
left = location[0], left = location[0],
right = location[0] + notification.width, right = location[0] + notification.width,
topCornerRadius = topCornerRadius, topCornerRadius = topCornerRadius,
bottomCornerRadius = notification.currentBackgroundRadiusBottom bottomCornerRadius = notification.bottomCornerRadius
) )
params.startTranslationZ = notification.translationZ params.startTranslationZ = notification.translationZ
@@ -97,8 +97,8 @@ class NotificationLaunchAnimatorController(
params.startClipTopAmount = notification.clipTopAmount params.startClipTopAmount = notification.clipTopAmount
if (notification.isChildInGroup) { if (notification.isChildInGroup) {
params.startNotificationTop += notification.notificationParent.translationY params.startNotificationTop += notification.notificationParent.translationY
val parentRoundedClip = Math.max( val locationOnScreen = notification.notificationParent.locationOnScreen[1]
clipStartLocation - notification.notificationParent.locationOnScreen[1], 0) val parentRoundedClip = (clipStartLocation - locationOnScreen).coerceAtLeast(0)
params.parentStartRoundedTopClipping = parentRoundedClip params.parentStartRoundedTopClipping = parentRoundedClip
val parentClip = notification.notificationParent.clipTopAmount val parentClip = notification.notificationParent.clipTopAmount

View File

@@ -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,
}

View File

@@ -613,22 +613,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
protected void resetAllContentAlphas() {} protected void resetAllContentAlphas() {}
@Override @Override
protected void applyRoundness() { public void applyRoundness() {
super.applyRoundness(); super.applyRoundness();
applyBackgroundRoundness(getCurrentBackgroundRadiusTop(), applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
getCurrentBackgroundRadiusBottom());
} }
@Override @Override
public float getCurrentBackgroundRadiusTop() { public float getTopCornerRadius() {
float fraction = getInterpolatedAppearAnimationFraction(); float fraction = getInterpolatedAppearAnimationFraction();
return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction); return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
} }
@Override @Override
public float getCurrentBackgroundRadiusBottom() { public float getBottomCornerRadius() {
float fraction = getInterpolatedAppearAnimationFraction(); float fraction = getInterpolatedAppearAnimationFraction();
return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction); return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
} }
private void applyBackgroundRoundness(float topRadius, float bottomRadius) { private void applyBackgroundRoundness(float topRadius, float bottomRadius) {

View File

@@ -93,6 +93,7 @@ import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils; 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.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -154,7 +155,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
void onLayout(); 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 { public interface OnExpansionChangedListener {
void onExpansionChanged(boolean isExpanded); void onExpansionChanged(boolean isExpanded);
} }
@@ -183,22 +186,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mNotificationLaunchHeight; private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen; 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; 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; private boolean mHasUserChangedExpansion;
/** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ /**
* If {@link #mHasUserChangedExpansion}, has the user expanded this row
*/
private boolean mUserExpanded; 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; private boolean mIsBlockingHelperShowing;
/** /**
* Has this notification been expanded while it was pinned * Has this notification been expanded while it was pinned
*/ */
private boolean mExpandedWhenPinned; private boolean mExpandedWhenPinned;
/** Is the user touching this row */ /**
* Is the user touching this row
*/
private boolean mUserLocked; private boolean mUserLocked;
/** Are we showing the "public" version */ /**
* Are we showing the "public" version
*/
private boolean mShowingPublic; private boolean mShowingPublic;
private boolean mSensitive; private boolean mSensitive;
private boolean mSensitiveHiddenInGeneral; private boolean mSensitiveHiddenInGeneral;
@@ -351,11 +366,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mWasChildInGroupWhenRemoved; private boolean mWasChildInGroupWhenRemoved;
private NotificationInlineImageResolver mImageResolver; private NotificationInlineImageResolver mImageResolver;
private NotificationMediaManager mMediaManager; private NotificationMediaManager mMediaManager;
@Nullable private OnExpansionChangedListener mExpansionChangedListener; @Nullable
@Nullable private Runnable mOnIntrinsicHeightReachedRunnable; private OnExpansionChangedListener mExpansionChangedListener;
@Nullable
private Runnable mOnIntrinsicHeightReachedRunnable;
private float mTopRoundnessDuringLaunchAnimation; private float mTopRoundnessDuringLaunchAnimation;
private float mBottomRoundnessDuringLaunchAnimation; private float mBottomRoundnessDuringLaunchAnimation;
private boolean mIsNotificationGroupCornerEnabled;
/** /**
* Returns whether the given {@code statusBarNotification} is a system notification. * 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() { public void onNotificationRankingUpdated() {
if (mMenuRow != null) { if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mEntry.getSbn()); 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() { public void updateBubbleButton() {
for (NotificationContentView l : mLayouts) { for (NotificationContentView l : mLayouts) {
l.updateBubbleButton(mEntry); 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. * Sets a supplier that can determine whether the keyguard is secure or not.
*
* @param secureStateProvider A function that returns true if keyguard is secure. * @param secureStateProvider A function that returns true if keyguard is secure.
*/ */
public void setSecureStateProvider(BooleanSupplier secureStateProvider) { public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
@@ -781,7 +804,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setUntruncatedChildCount(childCount); 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() { public void resetChildSystemExpandedStates() {
if (isSummaryWithChildren()) { if (isSummaryWithChildren()) {
mChildrenContainer.updateExpansionStates(); mChildrenContainer.updateExpansionStates();
@@ -791,7 +816,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** /**
* Add a child notification to this view. * 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 * @param childIndex the index to add it at, if -1 it will be added at the end
*/ */
public void addChildNotification(ExpandableNotificationRow row, int childIndex) { public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
@@ -809,10 +834,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
onAttachedChildrenCountChanged(); onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null); 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 @Nullable
public ExpandableNotificationRow getChildNotificationAt(int index) { public ExpandableNotificationRow getChildNotificationAt(int index) {
if (mChildrenContainer == null if (mChildrenContainer == null
@@ -834,7 +861,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** /**
* @param isChildInGroup Is this notification now in a group * @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) { public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) { if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
@@ -898,7 +925,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren(); return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
} }
/** Updates states of all children. */ /**
* Updates states of all children.
*/
public void updateChildrenStates(AmbientState ambientState) { public void updateChildrenStates(AmbientState ambientState) {
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
ExpandableViewState parentState = getViewState(); ExpandableViewState parentState = getViewState();
@@ -906,21 +935,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
} }
/** Applies children states. */ /**
* Applies children states.
*/
public void applyChildrenState() { public void applyChildrenState() {
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
mChildrenContainer.applyState(); mChildrenContainer.applyState();
} }
} }
/** Prepares expansion changed. */ /**
* Prepares expansion changed.
*/
public void prepareExpansionChanged() { public void prepareExpansionChanged() {
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
mChildrenContainer.prepareExpansionChanged(); mChildrenContainer.prepareExpansionChanged();
} }
} }
/** Starts child animations. */ /**
* Starts child animations.
*/
public void startChildAnimation(AnimationProperties properties) { public void startChildAnimation(AnimationProperties properties) {
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
mChildrenContainer.startAnimationToState(properties); mChildrenContainer.startAnimationToState(properties);
@@ -984,7 +1019,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight(); return mChildrenContainer.getIntrinsicHeight();
} }
if(mExpandedWhenPinned) { if (mExpandedWhenPinned) {
return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
} else if (atLeastMinHeight) { } else if (atLeastMinHeight) {
return Math.max(getCollapsedHeight(), getHeadsUpHeight()); return Math.max(getCollapsedHeight(), getHeadsUpHeight());
@@ -1079,18 +1114,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateClickAndFocus(); updateClickAndFocus();
} }
/** The click listener for the bubble button. */ /**
* The click listener for the bubble button.
*/
public View.OnClickListener getBubbleClickListener() { public View.OnClickListener getBubbleClickListener() {
return v -> { return v -> {
if (mBubblesManagerOptional.isPresent()) { if (mBubblesManagerOptional.isPresent()) {
mBubblesManagerOptional.get() mBubblesManagerOptional.get()
.onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */); .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
} }
mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */); 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) { public View.OnClickListener getSnoozeClickListener(MenuItem item) {
return v -> { return v -> {
// Dismiss a snoozed notification if one is still left behind // 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, public void setContentBackground(int customBackgroundColor, boolean animate,
NotificationContentView notificationContentView) { NotificationContentView notificationContentView) {
if (getShowingLayout() == notificationContentView) { if (getShowingLayout() == notificationContentView) {
setTintColor(customBackgroundColor, animate); setTintColor(customBackgroundColor, animate);
} }
@@ -1637,7 +1676,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setTargetPoint(null); 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) { public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mIsSummaryWithChildren) { if (mIsSummaryWithChildren) {
mChildrenContainer.setFeedbackIcon(icon); mChildrenContainer.setFeedbackIcon(icon);
@@ -1646,7 +1687,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPublicLayout.setFeedbackIcon(icon); 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) { public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
@@ -1700,7 +1743,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
Trace.endSection(); 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 @NonNull
private String appendTraceStyleTag(@NonNull String traceTag) { private String appendTraceStyleTag(@NonNull String traceTag) {
if (!Trace.isEnabled()) { if (!Trace.isEnabled()) {
@@ -1721,7 +1766,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
super.onFinishInflate(); super.onFinishInflate();
mPublicLayout = findViewById(R.id.expandedPublic); mPublicLayout = findViewById(R.id.expandedPublic);
mPrivateLayout = findViewById(R.id.expanded); mPrivateLayout = findViewById(R.id.expanded);
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
for (NotificationContentView l : mLayouts) { for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener); l.setExpandClickListener(mExpandClickListener);
@@ -1740,6 +1785,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setIsLowPriority(mIsLowPriority); mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated(); mChildrenContainer.onNotificationUpdated();
mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
mTranslateableViews.add(mChildrenContainer); mTranslateableViews.add(mChildrenContainer);
}); });
@@ -1796,6 +1842,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** /**
* Perform a smart action which triggers a longpress (expose guts). * Perform a smart action which triggers a longpress (expose guts).
* Based on the semanticAction passed, may update the state of the guts view. * Based on the semanticAction passed, may update the state of the guts view.
*
* @param semanticAction associated with this smart action click * @param semanticAction associated with this smart action click
*/ */
public void doSmartActionClick(int x, int y, int semanticAction) { 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. * Set the dismiss behavior of the view.
*
* @param usingRowTranslationX {@code true} if the view should translate using regular * @param usingRowTranslationX {@code true} if the view should translate using regular
* translationX, otherwise the contents will be * translationX, otherwise the contents will be
* translated. * translated.
*/ */
@Override @Override
public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
@@ -1955,6 +2003,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (previousTranslation != 0) { if (previousTranslation != 0) {
setTranslation(previousTranslation); 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, public Animator getTranslateViewAnimator(final float leftTarget,
AnimatorUpdateListener listener) { AnimatorUpdateListener listener) {
if (mTranslateAnim != null) { if (mTranslateAnim != null) {
mTranslateAnim.cancel(); mTranslateAnim.cancel();
} }
@@ -2115,7 +2171,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING)); NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
float startTop = params.getStartNotificationTop(); float startTop = params.getStartNotificationTop();
top = (int) Math.min(MathUtils.lerp(startTop, top = (int) Math.min(MathUtils.lerp(startTop,
params.getTop(), expandProgress), params.getTop(), expandProgress),
startTop); startTop);
} else { } else {
top = params.getTop(); top = params.getTop();
@@ -2151,29 +2207,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
setTranslationY(top); setTranslationY(top);
mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / mOutlineRadius; final float maxRadius = getMaxRadius();
mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / mOutlineRadius; mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius;
mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius;
invalidateOutline(); invalidateOutline();
mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight); mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight);
} }
@Override @Override
public float getCurrentTopRoundness() { public float getTopRoundness() {
if (mExpandAnimationRunning) { if (mExpandAnimationRunning) {
return mTopRoundnessDuringLaunchAnimation; return mTopRoundnessDuringLaunchAnimation;
} }
return super.getCurrentTopRoundness(); return super.getTopRoundness();
} }
@Override @Override
public float getCurrentBottomRoundness() { public float getBottomRoundness() {
if (mExpandAnimationRunning) { if (mExpandAnimationRunning) {
return mBottomRoundnessDuringLaunchAnimation; return mBottomRoundnessDuringLaunchAnimation;
} }
return super.getCurrentBottomRoundness(); return super.getBottomRoundness();
} }
public void setExpandAnimationRunning(boolean expandAnimationRunning) { public void setExpandAnimationRunning(boolean expandAnimationRunning) {
@@ -2284,7 +2341,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** /**
* Set this notification to be expanded by the user * 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 * @param allowChildExpansion whether a call to this method allows expanding children
*/ */
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { 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 * @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() { public boolean canShowHeadsUp() {
if (mOnKeyguard && !isDozing() && !isBypassEnabled()) { if (mOnKeyguard && !isDozing() && !isBypassEnabled()) {
@@ -2625,7 +2682,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override @Override
public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
long duration) { long duration) {
if (getVisibility() == GONE) { if (getVisibility() == GONE) {
// If we are GONE, the hideSensitive parameter will not be calculated and always be // 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. // 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) { private void animateShowingPublic(long delay, long duration, boolean showingPublic) {
View[] privateViews = mIsSummaryWithChildren View[] privateViews = mIsSummaryWithChildren
? new View[] {mChildrenContainer} ? new View[]{mChildrenContainer}
: new View[] {mPrivateLayout}; : new View[]{mPrivateLayout};
View[] publicViews = new View[] {mPublicLayout}; View[] publicViews = new View[]{mPublicLayout};
View[] hiddenChildren = showingPublic ? privateViews : publicViews; View[] hiddenChildren = showingPublic ? privateViews : publicViews;
View[] shownChildren = showingPublic ? publicViews : privateViews; View[] shownChildren = showingPublic ? publicViews : privateViews;
for (final View hiddenView : hiddenChildren) { 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 * @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 * otherwise some state might not be updated. To request about the general clearability
* see {@link NotificationEntry#isDismissable()}. * see {@link NotificationEntry#isDismissable()}.
*/ */
public boolean canViewBeDismissed() { public boolean canViewBeDismissed() {
return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
@@ -2777,8 +2834,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
@Override @Override
public long performRemoveAnimation(long duration, long delay, float translationDirection, public long performRemoveAnimation(
boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, long duration,
long delay,
float translationDirection,
boolean isHeadsUpAnimation,
float endLocation,
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) { AnimatorListenerAdapter animationListener) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) { if (mMenuRow != null && mMenuRow.isMenuVisible()) {
Animator anim = getTranslateViewAnimator(0f, null /* listener */); 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 @Override
public boolean isNotificationFaded() { public boolean isNotificationFaded() {
return mIsFaded; return mIsFaded;
@@ -2843,7 +2907,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* notifications return false from {@link #hasOverlappingRendering()} and delegate the * 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 * layerType to child views which really need it in order to render correctly, such as icon
* views or the conversation face pile. * views or the conversation face pile.
* * <p>
* Another compounding factor for notifications is that we change clipping on each frame of the * 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 * 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 * 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) { private void setNotificationFadedOnChildren(boolean faded) {
delegateNotificationFaded(mChildrenContainer, faded); delegateNotificationFaded(mChildrenContainer, faded);
for (NotificationContentView layout : mLayouts) { 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 * 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 * row should require overlapping rendering to ensure that the overlapped view doesn't bleed
* through when alpha fading. * through when alpha fading.
* * <p>
* Note that this currently works for top-level notifications which squish their height down * 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 * 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 * accordion affect does not apply to those views, so super.hasOverlappingRendering() will
@@ -2976,7 +3042,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGuts.getIntrinsicHeight(); return mGuts.getIntrinsicHeight();
} else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp
&& mHeadsUpManager.isTrackingHeadsUp()) { && mHeadsUpManager.isTrackingHeadsUp()) {
return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
} else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {
return mChildrenContainer.getMinHeight(); return mChildrenContainer.getMinHeight();
} else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) { } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) {
@@ -3218,8 +3284,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
if (snoozeMenu != null) { if (snoozeMenu != null) {
AccessibilityAction action = new AccessibilityAction(R.id.action_snooze, AccessibilityAction action = new AccessibilityAction(R.id.action_snooze,
getContext().getResources() getContext().getResources()
.getString(R.string.notification_menu_snooze_action)); .getString(R.string.notification_menu_snooze_action));
info.addAction(action); info.addAction(action);
} }
} }
@@ -3280,17 +3346,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationContentView contentView = (NotificationContentView) child; NotificationContentView contentView = (NotificationContentView) child;
if (isClippingNeeded()) { if (isClippingNeeded()) {
return true; return true;
} else if (!hasNoRounding() } else if (hasRoundedCorner()
&& contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f, && contentView.shouldClipToRounding(getTopRoundness() != 0.0f,
getCurrentBottomRoundness() != 0.0f)) { getBottomRoundness() != 0.0f)) {
return true; return true;
} }
} else if (child == mChildrenContainer) { } else if (child == mChildrenContainer) {
if (isClippingNeeded() || !hasNoRounding()) { if (isClippingNeeded() || hasRoundedCorner()) {
return true; return true;
} }
} else if (child instanceof NotificationGuts) { } else if (child instanceof NotificationGuts) {
return !hasNoRounding(); return hasRoundedCorner();
} }
return super.childNeedsClipping(child); return super.childNeedsClipping(child);
} }
@@ -3316,14 +3382,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
@Override @Override
protected void applyRoundness() { public void applyRoundness() {
super.applyRoundness(); super.applyRoundness();
applyChildrenRoundness(); applyChildrenRoundness();
} }
private void applyChildrenRoundness() { private void applyChildrenRoundness() {
if (mIsSummaryWithChildren) { 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); return super.getCustomClipPath(child);
} }
private boolean hasNoRounding() {
return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;
}
public boolean isMediaRow() { public boolean isMediaRow() {
return mEntry.getSbn().getNotification().isMediaNotification(); return mEntry.getSbn().getNotification().isMediaNotification();
} }
@@ -3434,6 +3499,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public interface LongPressListener { public interface LongPressListener {
/** /**
* Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
*
* @return whether the longpress was handled * @return whether the longpress was handled
*/ */
boolean onLongPress(View v, int x, int y, MenuItem item); boolean onLongPress(View v, int x, int y, MenuItem item);
@@ -3455,6 +3521,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public interface CoordinateOnClickListener { public interface CoordinateOnClickListener {
/** /**
* Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates * Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates
*
* @return whether the click was handled * @return whether the click was handled
*/ */
boolean onClick(View v, int x, int y, MenuItem item); boolean onClick(View v, int x, int y, MenuItem item);
@@ -3511,7 +3578,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void setTargetPoint(Point p) { private void setTargetPoint(Point p) {
mTargetPoint = p; mTargetPoint = p;
} }
public Point getTargetPoint() { public Point getTargetPoint() {
return mTargetPoint; 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);
}
}
} }

View File

@@ -231,6 +231,8 @@ public class ExpandableNotificationRowController implements NotifViewController
mStatusBarStateController.removeCallback(mStatusBarStateListener); mStatusBarStateController.removeCallback(mStatusBarStateListener);
} }
}); });
mView.enableNotificationGroupCorner(
mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
} }
private final StatusBarStateController.StateListener mStatusBarStateListener = private final StatusBarStateController.StateListener mStatusBarStateListener =

View File

@@ -28,46 +28,21 @@ import android.view.View;
import android.view.ViewOutlineProvider; import android.view.ViewOutlineProvider;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/** /**
* Like {@link ExpandableView}, but setting an outline for the height and clipping. * Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/ */
public abstract class ExpandableOutlineView extends ExpandableView { public abstract class ExpandableOutlineView extends ExpandableView {
private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from( private RoundableState mRoundableState;
"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 static final Path EMPTY_PATH = new Path(); private static final Path EMPTY_PATH = new Path();
private final Rect mOutlineRect = new Rect(); private final Rect mOutlineRect = new Rect();
private final Path mClipPath = new Path();
private boolean mCustomOutline; private boolean mCustomOutline;
private float mOutlineAlpha = -1f; private float mOutlineAlpha = -1f;
protected float mOutlineRadius;
private boolean mAlwaysRoundBothCorners; private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path(); private Path mTmpPath = new Path();
private float mCurrentBottomRoundness;
private float mCurrentTopRoundness;
private float mBottomRoundness;
private float mTopRoundness;
private int mBackgroundTop; private int mBackgroundTop;
/** /**
@@ -80,8 +55,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override @Override
public void getOutline(View view, Outline outline) { public void getOutline(View view, Outline outline) {
if (!mCustomOutline && getCurrentTopRoundness() == 0.0f if (!mCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) {
&& getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) {
// Only when translating just the contents, does the outline need to be shifted. // Only when translating just the contents, does the outline need to be shifted.
int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0; int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
int left = Math.max(translation, 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) { protected Path getClipPath(boolean ignoreTranslation) {
int left; int left;
int top; int top;
int right; int right;
int bottom; int bottom;
int height; int height;
float topRoundness = mAlwaysRoundBothCorners float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
? mOutlineRadius : getCurrentBackgroundRadiusTop();
if (!mCustomOutline) { if (!mCustomOutline) {
// The outline just needs to be shifted if we're translating the contents. Otherwise // The outline just needs to be shifted if we're translating the contents. Otherwise
// it's already in the right place. // it's already in the right place.
@@ -130,12 +108,11 @@ public abstract class ExpandableOutlineView extends ExpandableView {
if (height == 0) { if (height == 0) {
return EMPTY_PATH; return EMPTY_PATH;
} }
float bottomRoundness = mAlwaysRoundBothCorners float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
? mOutlineRadius : getCurrentBackgroundRadiusBottom();
if (topRoundness + bottomRoundness > height) { if (topRoundness + bottomRoundness > height) {
float overShoot = topRoundness + bottomRoundness - height; float overShoot = topRoundness + bottomRoundness - height;
float currentTopRoundness = getCurrentTopRoundness(); float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getCurrentBottomRoundness(); float currentBottomRoundness = getBottomRoundness();
topRoundness -= overShoot * currentTopRoundness topRoundness -= overShoot * currentTopRoundness
/ (currentTopRoundness + currentBottomRoundness); / (currentTopRoundness + currentBottomRoundness);
bottomRoundness -= overShoot * currentBottomRoundness bottomRoundness -= overShoot * currentBottomRoundness
@@ -145,8 +122,18 @@ public abstract class ExpandableOutlineView extends ExpandableView {
return mTmpPath; 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(); outPath.reset();
mTmpCornerRadii[0] = topRoundness; mTmpCornerRadii[0] = topRoundness;
mTmpCornerRadii[1] = topRoundness; mTmpCornerRadii[1] = topRoundness;
@@ -168,15 +155,28 @@ public abstract class ExpandableOutlineView extends ExpandableView {
@Override @Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
canvas.save(); canvas.save();
Path clipPath = null;
Path childClipPath = null;
if (childNeedsClipping(child)) { if (childNeedsClipping(child)) {
Path clipPath = getCustomClipPath(child); clipPath = getCustomClipPath(child);
if (clipPath == null) { if (clipPath == null) {
clipPath = getClipPath(false /* ignoreTranslation */); clipPath = getClipPath(false /* ignoreTranslation */);
} }
if (clipPath != null) { // If the notification uses "RowTranslationX" as dismiss behavior, we should clip the
canvas.clipPath(clipPath); // 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); boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore(); canvas.restore();
return result; return result;
@@ -207,73 +207,21 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private void initDimens() { private void initDimens() {
Resources res = getResources(); Resources res = getResources();
mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
if (!mAlwaysRoundBothCorners) { float maxRadius;
mOutlineRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); 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); setClipToOutline(mAlwaysRoundBothCorners);
} }
@Override @Override
public boolean setTopRoundness(float topRoundness, boolean animate) { public void applyRoundness() {
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() {
invalidateOutline(); invalidateOutline();
invalidate(); super.applyRoundness();
}
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;
} }
protected void setBackgroundTop(int backgroundTop) { 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() { public void onDensityOrFontScaleChanged() {
initDimens(); initDimens();
applyRoundness(); applyRoundness();
@@ -348,9 +286,10 @@ public abstract class ExpandableOutlineView extends ExpandableView {
/** /**
* Set the dismiss behavior of the view. * Set the dismiss behavior of the view.
*
* @param usingRowTranslationX {@code true} if the view should translate using regular * @param usingRowTranslationX {@code true} if the view should translate using regular
* translationX, otherwise the contents will be * translationX, otherwise the contents will be
* translated. * translated.
*/ */
public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
mDismissUsingRowTranslationX = usingRowTranslationX; mDismissUsingRowTranslationX = usingRowTranslationX;

View File

@@ -36,6 +36,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.StatusBarIconView; 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.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.DumpUtilsKt;
@@ -47,9 +49,10 @@ import java.util.List;
/** /**
* An abstract view for expandable views. * 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 static final String TAG = "ExpandableView";
private RoundableState mRoundableState = null;
protected OnHeightChangedListener mOnHeightChangedListener; protected OnHeightChangedListener mOnHeightChangedListener;
private int mActualHeight; private int mActualHeight;
protected int mClipTopAmount; protected int mClipTopAmount;
@@ -78,6 +81,14 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
initDimens(); initDimens();
} }
@Override
public RoundableState getRoundableState() {
if (mRoundableState == null) {
mRoundableState = new RoundableState(this, this, 0f);
}
return mRoundableState;
}
private void initDimens() { private void initDimens() {
mContentShift = getResources().getDimensionPixelSize( mContentShift = getResources().getDimensionPixelSize(
R.dimen.shelf_transform_content_shift); R.dimen.shelf_transform_content_shift);
@@ -440,8 +451,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
int top = getClipTopAmount(); int top = getClipTopAmount();
int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding() int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
- mClipBottomAmount, top), mMinimumHeightForClipping); - mClipBottomAmount, top), mMinimumHeightForClipping);
int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
setClipBounds(mClipRect); setClipBounds(mClipRect);
} else { } else {
setClipBounds(null); setClipBounds(null);
@@ -455,7 +465,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
public void setExtraWidthForClipping(float extraWidthForClipping) { public void setExtraWidthForClipping(float extraWidthForClipping) {
mExtraWidthForClipping = extraWidthForClipping; mExtraWidthForClipping = extraWidthForClipping;
updateClipping();
} }
public float getHeaderVisibleAmount() { public float getHeaderVisibleAmount() {
@@ -844,22 +853,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
return mFirstInSection; 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() { public int getHeadsUpHeightWithoutHeader() {
return getHeight(); return getHeight();
} }

View File

@@ -35,12 +35,15 @@ import androidx.annotation.Nullable;
import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton; import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper; import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation; import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.ImageTransformState; 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.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 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. * 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 private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
= new PathInterpolator(0.4f, 0f, 0.7f, 1f); = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
protected final ViewTransformationHelper mTransformationHelper; protected final ViewTransformationHelper mTransformationHelper;
private CachingIconView mIcon; private CachingIconView mIcon;
private NotificationExpandButton mExpandButton; private NotificationExpandButton mExpandButton;
private View mAltExpandTarget; private View mAltExpandTarget;
@@ -67,12 +69,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private ImageView mWorkProfileImage; private ImageView mWorkProfileImage;
private View mAudiblyAlertedIcon; private View mAudiblyAlertedIcon;
private View mFeedbackIcon; private View mFeedbackIcon;
private boolean mIsLowPriority; private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle; private boolean mTransformLowPriorityTitle;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row); super(ctx, view, row);
mRoundableState = new RoundableState(
mView,
this,
ctx.getResources().getDimension(R.dimen.notification_corner_radius)
);
mTransformationHelper = new ViewTransformationHelper(); mTransformationHelper = new ViewTransformationHelper();
// we want to avoid that the header clashes with the other text when transforming // 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) { new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
@Override @Override
public Interpolator getCustomInterpolator(int interpolationType, public Interpolator getCustomInterpolator(
int interpolationType,
boolean isFrom) { boolean isFrom) {
boolean isLowPriority = mView instanceof NotificationHeaderView; boolean isLowPriority = mView instanceof NotificationHeaderView;
if (interpolationType == TRANSFORM_Y) { if (interpolationType == TRANSFORM_Y) {
@@ -99,11 +106,17 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
protected boolean hasCustomTransformation() { protected boolean hasCustomTransformation() {
return mIsLowPriority && mTransformLowPriorityTitle; return mIsLowPriority && mTransformLowPriorityTitle;
} }
}, TRANSFORMING_VIEW_TITLE); },
TRANSFORMING_VIEW_TITLE);
resolveHeaderViews(); resolveHeaderViews();
addFeedbackOnClickListener(row); addFeedbackOnClickListener(row);
} }
@Override
public RoundableState getRoundableState() {
return mRoundableState;
}
protected void resolveHeaderViews() { protected void resolveHeaderViews() {
mIcon = mView.findViewById(com.android.internal.R.id.icon); mIcon = mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text); 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 @Override
public void setFeedbackIcon(@Nullable FeedbackIcon icon) { public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mFeedbackIcon != null) { if (mFeedbackIcon != null) {
@@ -193,7 +208,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
// its animation // its animation
&& child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) { && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) {
((ImageView) child).setCropToPadding(true); ((ImageView) child).setCropToPadding(true);
} else if (child instanceof ViewGroup){ } else if (child instanceof ViewGroup) {
ViewGroup group = (ViewGroup) child; ViewGroup group = (ViewGroup) child;
for (int i = 0; i < group.getChildCount(); i++) { for (int i = 0; i < group.getChildCount(); i++) {
stack.push(group.getChildAt(i)); stack.push(group.getChildAt(i));
@@ -215,7 +230,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
} }
@Override @Override
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener, public void updateExpandability(
boolean expandable,
View.OnClickListener onClickListener,
boolean requestLayout) { boolean requestLayout) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null); mExpandButton.setOnClickListener(expandable ? onClickListener : null);

View File

@@ -21,6 +21,9 @@ import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; 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.graphics.drawable.ColorDrawable;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -33,6 +36,7 @@ import android.view.ViewGroup;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting; 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.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils; 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.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager; import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView; 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 com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import java.util.ArrayList; import java.util.ArrayList;
@@ -56,7 +64,7 @@ import java.util.List;
* A container containing child notifications * A container containing child notifications
*/ */
public class NotificationChildrenContainer extends ViewGroup public class NotificationChildrenContainer extends ViewGroup
implements NotificationFadeAware { implements NotificationFadeAware, Roundable {
private static final String TAG = "NotificationChildrenContainer"; private static final String TAG = "NotificationChildrenContainer";
@@ -100,9 +108,9 @@ public class NotificationChildrenContainer extends ViewGroup
private boolean mEnableShadowOnChildNotifications; private boolean mEnableShadowOnChildNotifications;
private NotificationHeaderView mNotificationHeader; private NotificationHeaderView mNotificationHeader;
private NotificationViewWrapper mNotificationHeaderWrapper; private NotificationHeaderViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderView mNotificationHeaderLowPriority; private NotificationHeaderView mNotificationHeaderLowPriority;
private NotificationViewWrapper mNotificationHeaderWrapperLowPriority; private NotificationHeaderViewWrapper mNotificationHeaderWrapperLowPriority;
private NotificationGroupingUtil mGroupingUtil; private NotificationGroupingUtil mGroupingUtil;
private ViewState mHeaderViewState; private ViewState mHeaderViewState;
private int mClipBottomAmount; private int mClipBottomAmount;
@@ -110,7 +118,8 @@ public class NotificationChildrenContainer extends ViewGroup
private OnClickListener mHeaderClickListener; private OnClickListener mHeaderClickListener;
private ViewGroup mCurrentHeader; private ViewGroup mCurrentHeader;
private boolean mIsConversation; private boolean mIsConversation;
private Path mChildClipPath = null;
private final Path mHeaderPath = new Path();
private boolean mShowGroupCountInExpander; private boolean mShowGroupCountInExpander;
private boolean mShowDividersWhenExpanded; private boolean mShowDividersWhenExpanded;
private boolean mHideDividersDuringExpand; private boolean mHideDividersDuringExpand;
@@ -119,6 +128,8 @@ public class NotificationChildrenContainer extends ViewGroup
private float mHeaderVisibleAmount = 1.0f; private float mHeaderVisibleAmount = 1.0f;
private int mUntruncatedChildCount; private int mUntruncatedChildCount;
private boolean mContainingNotificationIsFaded = false; private boolean mContainingNotificationIsFaded = false;
private RoundableState mRoundableState;
private boolean mIsNotificationGroupCornerEnabled;
public NotificationChildrenContainer(Context context) { public NotificationChildrenContainer(Context context) {
this(context, null); this(context, null);
@@ -132,10 +143,14 @@ public class NotificationChildrenContainer extends ViewGroup
this(context, attrs, defStyleAttr, 0); this(context, attrs, defStyleAttr, 0);
} }
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, public NotificationChildrenContainer(
Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes) { int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
mHybridGroupManager = new HybridGroupManager(getContext()); mHybridGroupManager = new HybridGroupManager(getContext());
mRoundableState = new RoundableState(this, this, 0f);
initDimens(); initDimens();
setClipChildren(false); setClipChildren(false);
} }
@@ -167,6 +182,12 @@ public class NotificationChildrenContainer extends ViewGroup
mHybridGroupManager.initDimens(); mHybridGroupManager.initDimens();
} }
@NonNull
@Override
public RoundableState getRoundableState() {
return mRoundableState;
}
@Override @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = int childCount =
@@ -271,7 +292,7 @@ public class NotificationChildrenContainer extends ViewGroup
/** /**
* Add a child notification to this view. * 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 * @param childIndex the index to add it at, if -1 it will be added at the end
*/ */
public void addNotification(ExpandableNotificationRow row, int childIndex) { 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) mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
.setVisibility(VISIBLE); .setVisibility(VISIBLE);
mNotificationHeader.setOnClickListener(mHeaderClickListener); mNotificationHeader.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(), mNotificationHeaderWrapper =
mNotificationHeader, mContainingNotification); (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
getContext(),
mNotificationHeader,
mContainingNotification);
addView(mNotificationHeader, 0); addView(mNotificationHeader, 0);
invalidate(); invalidate();
} else { } else {
@@ -381,8 +405,11 @@ public class NotificationChildrenContainer extends ViewGroup
mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button) mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button)
.setVisibility(VISIBLE); .setVisibility(VISIBLE);
mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener); mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(), mNotificationHeaderWrapperLowPriority =
mNotificationHeaderLowPriority, mContainingNotification); (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
getContext(),
mNotificationHeaderLowPriority,
mContainingNotification);
addView(mNotificationHeaderLowPriority, 0); addView(mNotificationHeaderLowPriority, 0);
invalidate(); invalidate();
} else { } else {
@@ -461,7 +488,9 @@ public class NotificationChildrenContainer extends ViewGroup
return mAttachedChildren; 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() { public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) { if (mChildrenExpanded || mUserLocked) {
// we don't modify it the group is expanded or if we are expanding it // 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 * @return the intrinsic size of this children container, i.e the natural fully expanded state
*/ */
public int getIntrinsicHeight() { public int getIntrinsicHeight() {
@@ -485,7 +513,7 @@ public class NotificationChildrenContainer extends ViewGroup
/** /**
* @return the intrinsic height with a number of children given * @return the intrinsic height with a number of children given
* in @param maxAllowedVisibleChildren * in @param maxAllowedVisibleChildren
*/ */
private int getIntrinsicHeight(float maxAllowedVisibleChildren) { private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
if (showingAsLowPriority()) { if (showingAsLowPriority()) {
@@ -539,7 +567,8 @@ public class NotificationChildrenContainer extends ViewGroup
/** /**
* Update the state of all its children based on a linear layout algorithm. * 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 * @param ambientState the ambient state containing ambient information
*/ */
public void updateState(ExpandableViewState parentState, AmbientState ambientState) { 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 * 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. * 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 parentHeight the height of the parent.
* @param childState the state to update. * @param childState the state to update.
* @param yPosition the yPosition of the view. * @param yPosition the yPosition of the view.
* @return true if children after this one should be hidden. * @return true if children after this one should be hidden.
*/ */
private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child, private boolean updateChildStateForExpandedGroup(
int parentHeight, ExpandableViewState childState, int yPosition) { ExpandableNotificationRow child,
int parentHeight,
ExpandableViewState childState,
int yPosition) {
final int top = yPosition + child.getClipTopAmount(); final int top = yPosition + child.getClipTopAmount();
final int intrinsicHeight = child.getIntrinsicHeight(); final int intrinsicHeight = child.getIntrinsicHeight();
final int bottom = top + intrinsicHeight; final int bottom = top + intrinsicHeight;
@@ -690,13 +722,15 @@ public class NotificationChildrenContainer extends ViewGroup
if (mIsLowPriority if (mIsLowPriority
|| (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded()) || (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded())
|| (mContainingNotification.isHeadsUpState() || (mContainingNotification.isHeadsUpState()
&& mContainingNotification.canShowHeadsUp())) { && mContainingNotification.canShowHeadsUp())) {
return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
} }
return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
} }
/** Applies state to children. */ /**
* Applies state to children.
*/
public void applyState() { public void applyState() {
int childCount = mAttachedChildren.size(); int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState(); 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 * This is called when the children expansion has changed and positions the children properly
* for an appear animation. * for an appear animation.
*
*/ */
public void prepareExpansionChanged() { public void prepareExpansionChanged() {
// TODO: do something that makes sense, like placing the invisible views correctly // TODO: do something that makes sense, like placing the invisible views correctly
return; return;
} }
/** Animate to a given state. */ /**
* Animate to a given state.
*/
public void startAnimationToState(AnimationProperties properties) { public void startAnimationToState(AnimationProperties properties) {
int childCount = mAttachedChildren.size(); int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState(); ViewState tmpState = new ViewState();
@@ -1102,7 +1192,8 @@ public class NotificationChildrenContainer extends ViewGroup
* Get the minimum Height for this group. * Get the minimum Height for this group.
* *
* @param maxAllowedVisibleChildren the number of children that should be visible * @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) { private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation); return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation);
@@ -1112,10 +1203,13 @@ public class NotificationChildrenContainer extends ViewGroup
* Get the minimum Height for this group. * Get the minimum Height for this group.
* *
* @param maxAllowedVisibleChildren the number of children that should be visible * @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
* @param headerTranslation the translation amount of the header * 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) { int headerTranslation) {
if (!likeHighPriority && showingAsLowPriority()) { if (!likeHighPriority && showingAsLowPriority()) {
if (mNotificationHeaderLowPriority == null) { if (mNotificationHeaderLowPriority == null) {
@@ -1274,16 +1368,19 @@ public class NotificationChildrenContainer extends ViewGroup
return mUserLocked; return mUserLocked;
} }
public void setCurrentBottomRoundness(float currentBottomRoundness) { @Override
public void applyRoundness() {
Roundable.super.applyRoundness();
boolean last = true; boolean last = true;
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) { for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
ExpandableNotificationRow child = mAttachedChildren.get(i); ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) { if (child.getVisibility() == View.GONE) {
continue; continue;
} }
float bottomRoundness = last ? currentBottomRoundness : 0.0f; child.requestBottomRoundness(
child.setBottomRoundness(bottomRoundness, isShown() /* animate */); last ? getBottomRoundness() : 0f,
child.setTopRoundness(0.0f, false /* animate */); /* animate = */ isShown(),
SourceType.DefaultValue);
last = false; last = false;
} }
} }
@@ -1293,7 +1390,9 @@ public class NotificationChildrenContainer extends ViewGroup
mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader); 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) { public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mNotificationHeaderWrapper != null) { if (mNotificationHeaderWrapper != null) {
mNotificationHeaderWrapper.setFeedbackIcon(icon); mNotificationHeaderWrapper.setFeedbackIcon(icon);
@@ -1325,4 +1424,26 @@ public class NotificationChildrenContainer extends ViewGroup
child.setNotificationFaded(faded); 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;
}
} }

View File

@@ -26,6 +26,8 @@ import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; 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.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger; import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -59,8 +61,8 @@ public class NotificationRoundnessManager implements Dumpable {
private boolean mIsClearAllInProgress; private boolean mIsClearAllInProgress;
private ExpandableView mSwipedView = null; private ExpandableView mSwipedView = null;
private ExpandableView mViewBeforeSwipedView = null; private Roundable mViewBeforeSwipedView = null;
private ExpandableView mViewAfterSwipedView = null; private Roundable mViewAfterSwipedView = null;
@Inject @Inject
NotificationRoundnessManager( NotificationRoundnessManager(
@@ -101,11 +103,12 @@ public class NotificationRoundnessManager implements Dumpable {
public boolean isViewAffectedBySwipe(ExpandableView expandableView) { public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
return expandableView != null return expandableView != null
&& (expandableView == mSwipedView && (expandableView == mSwipedView
|| expandableView == mViewBeforeSwipedView || expandableView == mViewBeforeSwipedView
|| expandableView == mViewAfterSwipedView); || expandableView == mViewAfterSwipedView);
} }
boolean updateViewWithoutCallback(ExpandableView view, boolean updateViewWithoutCallback(
ExpandableView view,
boolean animate) { boolean animate) {
if (view == null if (view == null
|| view == mViewBeforeSwipedView || view == mViewBeforeSwipedView
@@ -113,11 +116,15 @@ public class NotificationRoundnessManager implements Dumpable {
return false; return false;
} }
final float topRoundness = getRoundnessFraction(view, true /* top */); final boolean isTopChanged = view.requestTopRoundness(
final float bottomRoundness = getRoundnessFraction(view, false /* top */); getRoundnessDefaultValue(view, true /* top */),
animate,
SourceType.DefaultValue);
final boolean topChanged = view.setTopRoundness(topRoundness, animate); final boolean isBottomChanged = view.requestBottomRoundness(
final boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate); getRoundnessDefaultValue(view, /* top = */ false),
animate,
SourceType.DefaultValue);
final boolean isFirstInSection = isFirstInSection(view); final boolean isFirstInSection = isFirstInSection(view);
final boolean isLastInSection = isLastInSection(view); final boolean isLastInSection = isLastInSection(view);
@@ -126,9 +133,9 @@ public class NotificationRoundnessManager implements Dumpable {
view.setLastInSection(isLastInSection); view.setLastInSection(isLastInSection);
mNotifLogger.onCornersUpdated(view, isFirstInSection, 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) { private boolean isFirstInSection(ExpandableView view) {
@@ -150,42 +157,46 @@ public class NotificationRoundnessManager implements Dumpable {
} }
void setViewsAffectedBySwipe( void setViewsAffectedBySwipe(
ExpandableView viewBefore, Roundable viewBefore,
ExpandableView viewSwiped, ExpandableView viewSwiped,
ExpandableView viewAfter) { Roundable viewAfter) {
final boolean animate = true; 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; mViewBeforeSwipedView = viewBefore;
if (oldViewBefore != null) {
final float bottomRoundness = getRoundnessFraction(oldViewBefore, false /* top */);
oldViewBefore.setBottomRoundness(bottomRoundness, animate);
}
if (viewBefore != null) { 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; 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) { if (viewSwiped != null) {
viewSwiped.setTopRoundness(1f, animate); oldViews.remove(viewSwiped);
viewSwiped.setBottomRoundness(1f, animate); viewSwiped.requestTopRoundness(1f, animate, source);
viewSwiped.requestBottomRoundness(1f, animate, source);
} }
ExpandableView oldViewAfter = mViewAfterSwipedView;
mViewAfterSwipedView = viewAfter; mViewAfterSwipedView = viewAfter;
if (oldViewAfter != null) {
final float topRoundness = getRoundnessFraction(oldViewAfter, true /* top */);
oldViewAfter.setTopRoundness(topRoundness, animate);
}
if (viewAfter != null) { 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; mIsClearAllInProgress = isClearingAll;
} }
private float getRoundnessFraction(ExpandableView view, boolean top) { private float getRoundnessDefaultValue(Roundable view, boolean top) {
if (view == null) { if (view == null) {
return 0f; return 0f;
} }
@@ -207,28 +218,35 @@ public class NotificationRoundnessManager implements Dumpable {
&& mIsClearAllInProgress) { && mIsClearAllInProgress) {
return 1.0f; return 1.0f;
} }
if ((view.isPinned() if (view instanceof ExpandableView) {
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) { ExpandableView expandableView = (ExpandableView) view;
return 1.0f; if ((expandableView.isPinned()
} || (expandableView.isHeadsUpAnimatingAway()) && !mExpanded)) {
if (isFirstInSection(view) && top) { return 1.0f;
return 1.0f; }
} if (isFirstInSection(expandableView) && top) {
if (isLastInSection(view) && !top) { return 1.0f;
return 1.0f; }
} if (isLastInSection(expandableView) && !top) {
return 1.0f;
}
if (view == mTrackedHeadsUp) { if (view == mTrackedHeadsUp) {
// If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be // If we're pushing up on a headsup the appear fraction is < 0 and it needs to
// rounded. // still be rounded.
return MathUtils.saturate(1.0f - mAppearFraction); 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);
} }
if (view.showingPulsing() && mRoundForPulsingViews) { return 0f;
return 1.0f;
}
final Resources resources = view.getResources();
return resources.getDimension(R.dimen.notification_corner_radius_small)
/ resources.getDimension(R.dimen.notification_corner_radius);
} }
public void setExpanded(float expandedHeight, float appearFraction) { public void setExpanded(float expandedHeight, float appearFraction) {
@@ -258,8 +276,10 @@ public class NotificationRoundnessManager implements Dumpable {
mNotifLogger.onSectionCornersUpdated(sections, anyChanged); mNotifLogger.onSectionCornersUpdated(sections, anyChanged);
} }
private boolean handleRemovedOldViews(NotificationSection[] sections, private boolean handleRemovedOldViews(
ExpandableView[] oldViews, boolean first) { NotificationSection[] sections,
ExpandableView[] oldViews,
boolean first) {
boolean anyChanged = false; boolean anyChanged = false;
for (ExpandableView oldView : oldViews) { for (ExpandableView oldView : oldViews) {
if (oldView != null) { if (oldView != null) {
@@ -289,8 +309,10 @@ public class NotificationRoundnessManager implements Dumpable {
return anyChanged; return anyChanged;
} }
private boolean handleAddedNewViews(NotificationSection[] sections, private boolean handleAddedNewViews(
ExpandableView[] oldViews, boolean first) { NotificationSection[] sections,
ExpandableView[] oldViews,
boolean first) {
boolean anyChanged = false; boolean anyChanged = false;
for (NotificationSection section : sections) { for (NotificationSection section : sections) {
ExpandableView newView = ExpandableView newView =

View File

@@ -1188,7 +1188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return; return;
} }
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (mChildrenToAddAnimated.contains(child)) { if (mChildrenToAddAnimated.contains(child)) {
final int startingPosition = getPositionInLinearLayout(child); final int startingPosition = getPositionInLinearLayout(child);
final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements; 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 // find the view under the pointer, accounting for GONE views
final int count = getChildCount(); final int count = getChildCount();
for (int childIdx = 0; childIdx < count; childIdx++) { for (int childIdx = 0; childIdx < count; childIdx++) {
ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); ExpandableView slidingChild = getChildAtIndex(childIdx);
if (slidingChild.getVisibility() != VISIBLE if (slidingChild.getVisibility() != VISIBLE
|| (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) { || (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) {
continue; continue;
@@ -1691,6 +1691,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return null; return null;
} }
private ExpandableView getChildAtIndex(int index) {
return (ExpandableView) getChildAt(index);
}
public ExpandableView getChildAtRawPosition(float touchX, float touchY) { public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
getLocationOnScreen(mTempInt2); getLocationOnScreen(mTempInt2);
return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
@@ -2276,7 +2280,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
int childCount = getChildCount(); int childCount = getChildCount();
int count = 0; int count = 0;
for (int i = 0; i < childCount; i++) { 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) { if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
count++; count++;
} }
@@ -2496,7 +2500,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private ExpandableView getLastChildWithBackground() { private ExpandableView getLastChildWithBackground() {
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) { 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) if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) { && child != mShelf) {
return child; return child;
@@ -2509,7 +2513,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private ExpandableView getFirstChildWithBackground() { private ExpandableView getFirstChildWithBackground() {
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) { && child != mShelf) {
return child; return child;
@@ -2523,7 +2527,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
ArrayList<ExpandableView> children = new ArrayList<>(); ArrayList<ExpandableView> children = new ArrayList<>();
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE if (child.getVisibility() != View.GONE
&& !(child instanceof StackScrollerDecorView) && !(child instanceof StackScrollerDecorView)
&& child != mShelf) { && child != mShelf) {
@@ -2882,7 +2886,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} }
int position = 0; int position = 0;
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
boolean notGone = child.getVisibility() != View.GONE; boolean notGone = child.getVisibility() != View.GONE;
if (notGone && !child.hasNoContentHeight()) { if (notGone && !child.hasNoContentHeight()) {
if (position != 0) { if (position != 0) {
@@ -2936,7 +2940,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} }
mAmbientState.setLastVisibleBackgroundChild(lastChild); mAmbientState.setLastVisibleBackgroundChild(lastChild);
// TODO: Refactor SectionManager and put the RoundnessManager there. // TODO: Refactor SectionManager and put the RoundnessManager there.
mController.getNoticationRoundessManager().updateRoundedChildren(mSections); mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
mAnimateBottomOnLayout = false; mAnimateBottomOnLayout = false;
invalidate(); invalidate();
} }
@@ -3968,7 +3972,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void clearUserLockedViews() { private void clearUserLockedViews() {
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (child instanceof ExpandableNotificationRow) { if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child; ExpandableNotificationRow row = (ExpandableNotificationRow) child;
row.setUserLocked(false); row.setUserLocked(false);
@@ -3981,7 +3985,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// lets make sure nothing is transient anymore // lets make sure nothing is transient anymore
clearTemporaryViewsInGroup(this); clearTemporaryViewsInGroup(this);
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (child instanceof ExpandableNotificationRow) { if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child; ExpandableNotificationRow row = (ExpandableNotificationRow) child;
clearTemporaryViewsInGroup(row.getChildrenContainer()); clearTemporaryViewsInGroup(row.getChildrenContainer());
@@ -4230,7 +4234,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (hideSensitive != mAmbientState.isHideSensitive()) { if (hideSensitive != mAmbientState.isHideSensitive()) {
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) getChildAt(i); ExpandableView v = getChildAtIndex(i);
v.setHideSensitiveForIntrinsicHeight(hideSensitive); v.setHideSensitiveForIntrinsicHeight(hideSensitive);
} }
mAmbientState.setHideSensitive(hideSensitive); mAmbientState.setHideSensitive(hideSensitive);
@@ -4265,7 +4269,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void applyCurrentState() { private void applyCurrentState() {
int numChildren = getChildCount(); int numChildren = getChildCount();
for (int i = 0; i < numChildren; i++) { for (int i = 0; i < numChildren; i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
child.applyViewState(); child.applyViewState();
} }
@@ -4285,7 +4289,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// Lefts first sort by Z difference // Lefts first sort by Z difference
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != GONE) { if (child.getVisibility() != GONE) {
mTmpSortedChildren.add(child); mTmpSortedChildren.add(child);
} }
@@ -4512,7 +4516,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setClearAllInProgress(boolean clearAllInProgress) { public void setClearAllInProgress(boolean clearAllInProgress) {
mClearAllInProgress = clearAllInProgress; mClearAllInProgress = clearAllInProgress;
mAmbientState.setClearAllInProgress(clearAllInProgress); mAmbientState.setClearAllInProgress(clearAllInProgress);
mController.getNoticationRoundessManager().setClearAllInProgress(clearAllInProgress); mController.getNotificationRoundnessManager().setClearAllInProgress(clearAllInProgress);
} }
boolean getClearAllInProgress() { boolean getClearAllInProgress() {
@@ -4555,7 +4559,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
final int count = getChildCount(); final int count = getChildCount();
float max = 0; float max = 0;
for (int childIdx = 0; childIdx < count; childIdx++) { for (int childIdx = 0; childIdx < count; childIdx++) {
ExpandableView child = (ExpandableView) getChildAt(childIdx); ExpandableView child = getChildAtIndex(childIdx);
if (child.getVisibility() == GONE) { if (child.getVisibility() == GONE) {
continue; continue;
} }
@@ -4586,7 +4590,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public boolean isBelowLastNotification(float touchX, float touchY) { public boolean isBelowLastNotification(float touchX, float touchY) {
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) { for (int i = childCount - 1; i >= 0; i--) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE) { if (child.getVisibility() != View.GONE) {
float childTop = child.getY(); float childTop = child.getY();
if (childTop > touchY) { if (childTop > touchY) {
@@ -5052,7 +5056,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
pw.println(); pw.println();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
ExpandableView child = (ExpandableView) getChildAt(i); ExpandableView child = getChildAtIndex(i);
child.dump(pw, args); child.dump(pw, args);
pw.println(); pw.println();
} }
@@ -5341,7 +5345,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
float wakeUplocation = -1f; float wakeUplocation = -1f;
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
ExpandableView view = (ExpandableView) getChildAt(i); ExpandableView view = getChildAtIndex(i);
if (view.getVisibility() == View.GONE) { if (view.getVisibility() == View.GONE) {
continue; continue;
} }
@@ -5380,7 +5384,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setController( public void setController(
NotificationStackScrollLayoutController notificationStackScrollLayoutController) { NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
mController = notificationStackScrollLayoutController; mController = notificationStackScrollLayoutController;
mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated); mController.getNotificationRoundnessManager().setAnimatedChildren(mChildrenToAddAnimated);
} }
void addSwipedOutView(View v) { void addSwipedOutView(View v) {
@@ -5391,31 +5395,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!(viewSwiped instanceof ExpandableNotificationRow)) { if (!(viewSwiped instanceof ExpandableNotificationRow)) {
return; return;
} }
final int indexOfSwipedView = indexOfChild(viewSwiped);
if (indexOfSwipedView < 0) {
return;
}
mSectionsManager.updateFirstAndLastViewsForAllSections( mSectionsManager.updateFirstAndLastViewsForAllSections(
mSections, getChildrenWithBackground()); mSections,
View viewBefore = null; getChildrenWithBackground()
if (indexOfSwipedView > 0) { );
viewBefore = getChildAt(indexOfSwipedView - 1);
if (mSectionsManager.beginsSection(viewSwiped, viewBefore)) { RoundableTargets targets = mController.getNotificationTargetsHelper().findRoundableTargets(
viewBefore = null; (ExpandableNotificationRow) viewSwiped,
} this,
} mSectionsManager
View viewAfter = null; );
if (indexOfSwipedView < getChildCount()) {
viewAfter = getChildAt(indexOfSwipedView + 1); mController.getNotificationRoundnessManager()
if (mSectionsManager.beginsSection(viewAfter, viewSwiped)) {
viewAfter = null;
}
}
mController.getNoticationRoundessManager()
.setViewsAffectedBySwipe( .setViewsAffectedBySwipe(
(ExpandableView) viewBefore, targets.getBefore(),
(ExpandableView) viewSwiped, targets.getSwiped(),
(ExpandableView) viewAfter); targets.getAfter());
updateFirstAndLastBackgroundViews(); updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true); requestDisallowInterceptTouchEvent(true);
@@ -5426,7 +5421,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
void onSwipeEnd() { void onSwipeEnd() {
updateFirstAndLastBackgroundViews(); updateFirstAndLastBackgroundViews();
mController.getNoticationRoundessManager() mController.getNotificationRoundnessManager()
.setViewsAffectedBySwipe(null, null, null); .setViewsAffectedBySwipe(null, null, null);
// Round bottom corners for notification right before shelf. // Round bottom corners for notification right before shelf.
mShelf.updateAppearance(); mShelf.updateAppearance();

View File

@@ -180,6 +180,7 @@ public class NotificationStackScrollLayoutController {
private int mBarState; private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController; private HeadsUpAppearanceController mHeadsUpAppearanceController;
private final FeatureFlags mFeatureFlags; private final FeatureFlags mFeatureFlags;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private View mLongPressedView; private View mLongPressedView;
@@ -642,7 +643,8 @@ public class NotificationStackScrollLayoutController {
StackStateLogger stackLogger, StackStateLogger stackLogger,
NotificationStackScrollLogger logger, NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator, NotificationStackSizeCalculator notificationStackSizeCalculator,
FeatureFlags featureFlags) { FeatureFlags featureFlags,
NotificationTargetsHelper notificationTargetsHelper) {
mStackStateLogger = stackLogger; mStackStateLogger = stackLogger;
mLogger = logger; mLogger = logger;
mAllowLongPress = allowLongPress; mAllowLongPress = allowLongPress;
@@ -679,6 +681,7 @@ public class NotificationStackScrollLayoutController {
mRemoteInputManager = remoteInputManager; mRemoteInputManager = remoteInputManager;
mShadeController = shadeController; mShadeController = shadeController;
mFeatureFlags = featureFlags; mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
updateResources(); updateResources();
} }
@@ -1380,7 +1383,7 @@ public class NotificationStackScrollLayoutController {
return mView.calculateGapHeight(previousView, child, count); return mView.calculateGapHeight(previousView, child, count);
} }
NotificationRoundnessManager getNoticationRoundessManager() { NotificationRoundnessManager getNotificationRoundnessManager() {
return mNotificationRoundnessManager; return mNotificationRoundnessManager;
} }
@@ -1537,6 +1540,10 @@ public class NotificationStackScrollLayoutController {
mNotificationActivityStarter = activityStarter; mNotificationActivityStarter = activityStarter;
} }
public NotificationTargetsHelper getNotificationTargetsHelper() {
return mNotificationTargetsHelper;
}
/** /**
* Enum for UiEvent logged from this class * Enum for UiEvent logged from this class
*/ */

View File

@@ -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?,
)

View File

@@ -31,6 +31,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf; 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.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -804,7 +805,7 @@ public class StackScrollAlgorithm {
row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius); row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(), final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius); ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
row.setBottomRoundness(roundness, /* animate= */ false); row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
} }
@VisibleForTesting @VisibleForTesting

View File

@@ -147,8 +147,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mSecond), createSection(mFirst, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f);
Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -170,13 +170,13 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true); when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true);
row.setHeadsUp(true); row.setHeadsUp(true);
mRoundnessManager.updateView(entry.getRow(), false); mRoundnessManager.updateView(entry.getRow(), false);
Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1f, row.getBottomRoundness(), 0.0f);
Assert.assertEquals(1f, row.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1f, row.getTopRoundness(), 0.0f);
row.setHeadsUp(false); row.setHeadsUp(false);
mRoundnessManager.updateView(entry.getRow(), false); mRoundnessManager.updateView(entry.getRow(), false);
Assert.assertEquals(mSmallRadiusRatio, row.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, row.getBottomRoundness(), 0.0f);
Assert.assertEquals(mSmallRadiusRatio, row.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, row.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -185,8 +185,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mFirst), createSection(mFirst, mFirst),
createSection(null, mSecond) createSection(null, mSecond)
}); });
Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f);
Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -195,8 +195,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mFirst), createSection(mFirst, mFirst),
createSection(mSecond, null) createSection(mSecond, null)
}); });
Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mSecond.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mSecond.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mSecond.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -205,8 +205,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, null), createSection(mFirst, null),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -215,8 +215,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -226,8 +226,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -238,8 +238,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -250,8 +250,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -262,8 +262,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -274,8 +274,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -286,8 +286,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond), createSection(mSecond, mSecond),
createSection(null, null) createSection(null, null)
}); });
Assert.assertEquals(0.5f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(0.5f, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(0.5f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(0.5f, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test
@@ -298,8 +298,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(null, null) createSection(null, null)
}); });
mFirst.setHeadsUpAnimatingAway(true); mFirst.setHeadsUpAnimatingAway(true);
Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
} }
@@ -312,8 +312,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
}); });
mFirst.setHeadsUpAnimatingAway(true); mFirst.setHeadsUpAnimatingAway(true);
mFirst.setHeadsUpAnimatingAway(false); mFirst.setHeadsUpAnimatingAway(false);
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
} }
@Test @Test

View File

@@ -129,6 +129,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private ShadeTransitionController mShadeTransitionController; @Mock private ShadeTransitionController mShadeTransitionController;
@Mock private FeatureFlags mFeatureFlags; @Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Captor @Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -177,7 +178,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mStackLogger, mStackLogger,
mLogger, mLogger,
mNotificationStackSizeCalculator, mNotificationStackSizeCalculator,
mFeatureFlags mFeatureFlags,
mNotificationTargetsHelper
); );
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true); when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);

View File

@@ -163,7 +163,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setCentralSurfaces(mCentralSurfaces); mStackScroller.setCentralSurfaces(mCentralSurfaces);
mStackScroller.setEmptyShadeView(mEmptyShadeView); mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true); when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNoticationRoundessManager()) when(mStackScrollLayoutController.getNotificationRoundnessManager())
.thenReturn(mNotificationRoundnessManager); .thenReturn(mNotificationRoundnessManager);
mStackScroller.setController(mStackScrollLayoutController); mStackScroller.setController(mStackScrollLayoutController);

View File

@@ -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)
}
}