Merge "Add support for rounded notification in groups" into tm-qpr-dev

This commit is contained in:
Omar Miatello
2022-10-24 18:51:07 +00:00
committed by Android (Google) Code Review
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)
val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
// next id: 116
@JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, true)
// next id: 117
// 200 - keyguard/lockscreen
// ** Flag retired **

View File

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

View File

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

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() {}
@Override
protected void applyRoundness() {
public void applyRoundness() {
super.applyRoundness();
applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
getCurrentBackgroundRadiusBottom());
applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
}
@Override
public float getCurrentBackgroundRadiusTop() {
public float getTopCornerRadius() {
float fraction = getInterpolatedAppearAnimationFraction();
return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction);
return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
}
@Override
public float getCurrentBackgroundRadiusBottom() {
public float getBottomCornerRadius() {
float fraction = getInterpolatedAppearAnimationFraction();
return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction);
return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
}
private void applyBackgroundRoundness(float topRadius, float bottomRadius) {

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

View File

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

View File

@@ -28,46 +28,21 @@ import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
"topRoundness",
ExpandableOutlineView::setTopRoundnessInternal,
ExpandableOutlineView::getCurrentTopRoundness,
R.id.top_roundess_animator_tag,
R.id.top_roundess_animator_end_tag,
R.id.top_roundess_animator_start_tag);
private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
"bottomRoundness",
ExpandableOutlineView::setBottomRoundnessInternal,
ExpandableOutlineView::getCurrentBottomRoundness,
R.id.bottom_roundess_animator_tag,
R.id.bottom_roundess_animator_end_tag,
R.id.bottom_roundess_animator_start_tag);
private static final AnimationProperties ROUNDNESS_PROPERTIES =
new AnimationProperties().setDuration(
StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS);
private RoundableState mRoundableState;
private static final Path EMPTY_PATH = new Path();
private final Rect mOutlineRect = new Rect();
private final Path mClipPath = new Path();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
protected float mOutlineRadius;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
private float mCurrentBottomRoundness;
private float mCurrentTopRoundness;
private float mBottomRoundness;
private float mTopRoundness;
private int mBackgroundTop;
/**
@@ -80,8 +55,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
if (!mCustomOutline && getCurrentTopRoundness() == 0.0f
&& getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) {
if (!mCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) {
// Only when translating just the contents, does the outline need to be shifted.
int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
int left = Math.max(translation, 0);
@@ -99,14 +73,18 @@ public abstract class ExpandableOutlineView extends ExpandableView {
}
};
@Override
public RoundableState getRoundableState() {
return mRoundableState;
}
protected Path getClipPath(boolean ignoreTranslation) {
int left;
int top;
int right;
int bottom;
int height;
float topRoundness = mAlwaysRoundBothCorners
? mOutlineRadius : getCurrentBackgroundRadiusTop();
float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
if (!mCustomOutline) {
// The outline just needs to be shifted if we're translating the contents. Otherwise
// it's already in the right place.
@@ -130,12 +108,11 @@ public abstract class ExpandableOutlineView extends ExpandableView {
if (height == 0) {
return EMPTY_PATH;
}
float bottomRoundness = mAlwaysRoundBothCorners
? mOutlineRadius : getCurrentBackgroundRadiusBottom();
float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
if (topRoundness + bottomRoundness > height) {
float overShoot = topRoundness + bottomRoundness - height;
float currentTopRoundness = getCurrentTopRoundness();
float currentBottomRoundness = getCurrentBottomRoundness();
float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getBottomRoundness();
topRoundness -= overShoot * currentTopRoundness
/ (currentTopRoundness + currentBottomRoundness);
bottomRoundness -= overShoot * currentBottomRoundness
@@ -145,8 +122,18 @@ public abstract class ExpandableOutlineView extends ExpandableView {
return mTmpPath;
}
public void getRoundedRectPath(int left, int top, int right, int bottom,
float topRoundness, float bottomRoundness, Path outPath) {
/**
* Add a round rect in {@code outPath}
* @param outPath destination path
*/
public void getRoundedRectPath(
int left,
int top,
int right,
int bottom,
float topRoundness,
float bottomRoundness,
Path outPath) {
outPath.reset();
mTmpCornerRadii[0] = topRoundness;
mTmpCornerRadii[1] = topRoundness;
@@ -168,15 +155,28 @@ public abstract class ExpandableOutlineView extends ExpandableView {
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
canvas.save();
Path clipPath = null;
Path childClipPath = null;
if (childNeedsClipping(child)) {
Path clipPath = getCustomClipPath(child);
clipPath = getCustomClipPath(child);
if (clipPath == null) {
clipPath = getClipPath(false /* ignoreTranslation */);
}
if (clipPath != null) {
canvas.clipPath(clipPath);
// If the notification uses "RowTranslationX" as dismiss behavior, we should clip the
// children instead.
if (mDismissUsingRowTranslationX && child instanceof NotificationChildrenContainer) {
childClipPath = clipPath;
clipPath = null;
}
}
if (child instanceof NotificationChildrenContainer) {
((NotificationChildrenContainer) child).setChildClipPath(childClipPath);
}
if (clipPath != null) {
canvas.clipPath(clipPath);
}
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
@@ -207,73 +207,21 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private void initDimens() {
Resources res = getResources();
mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
if (!mAlwaysRoundBothCorners) {
mOutlineRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
float maxRadius;
if (mAlwaysRoundBothCorners) {
maxRadius = res.getDimension(R.dimen.notification_shadow_radius);
} else {
maxRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
}
mRoundableState = new RoundableState(this, this, maxRadius);
setClipToOutline(mAlwaysRoundBothCorners);
}
@Override
public boolean setTopRoundness(float topRoundness, boolean animate) {
if (mTopRoundness != topRoundness) {
float diff = Math.abs(topRoundness - mTopRoundness);
mTopRoundness = topRoundness;
boolean shouldAnimate = animate;
if (PropertyAnimator.isAnimating(this, TOP_ROUNDNESS) && diff > 0.5f) {
// Fail safe:
// when we've been animating previously and we're now getting an update in the
// other direction, make sure to animate it too, otherwise, the localized updating
// may make the start larger than 1.0.
shouldAnimate = true;
}
PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
ROUNDNESS_PROPERTIES, shouldAnimate);
return true;
}
return false;
}
protected void applyRoundness() {
public void applyRoundness() {
invalidateOutline();
invalidate();
}
public float getCurrentBackgroundRadiusTop() {
return getCurrentTopRoundness() * mOutlineRadius;
}
public float getCurrentTopRoundness() {
return mCurrentTopRoundness;
}
public float getCurrentBottomRoundness() {
return mCurrentBottomRoundness;
}
public float getCurrentBackgroundRadiusBottom() {
return getCurrentBottomRoundness() * mOutlineRadius;
}
@Override
public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
if (mBottomRoundness != bottomRoundness) {
float diff = Math.abs(bottomRoundness - mBottomRoundness);
mBottomRoundness = bottomRoundness;
boolean shouldAnimate = animate;
if (PropertyAnimator.isAnimating(this, BOTTOM_ROUNDNESS) && diff > 0.5f) {
// Fail safe:
// when we've been animating previously and we're now getting an update in the
// other direction, make sure to animate it too, otherwise, the localized updating
// may make the start larger than 1.0.
shouldAnimate = true;
}
PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
ROUNDNESS_PROPERTIES, shouldAnimate);
return true;
}
return false;
super.applyRoundness();
}
protected void setBackgroundTop(int backgroundTop) {
@@ -283,16 +231,6 @@ public abstract class ExpandableOutlineView extends ExpandableView {
}
}
private void setTopRoundnessInternal(float topRoundness) {
mCurrentTopRoundness = topRoundness;
applyRoundness();
}
private void setBottomRoundnessInternal(float bottomRoundness) {
mCurrentBottomRoundness = bottomRoundness;
applyRoundness();
}
public void onDensityOrFontScaleChanged() {
initDimens();
applyRoundness();
@@ -348,9 +286,10 @@ public abstract class ExpandableOutlineView extends ExpandableView {
/**
* Set the dismiss behavior of the view.
*
* @param usingRowTranslationX {@code true} if the view should translate using regular
* translationX, otherwise the contents will be
* translated.
* translationX, otherwise the contents will be
* translated.
*/
public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
mDismissUsingRowTranslationX = usingRowTranslationX;

View File

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

View File

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

View File

@@ -21,6 +21,9 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.drawable.ColorDrawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
@@ -33,6 +36,7 @@ import android.view.ViewGroup;
import android.widget.RemoteViews;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -43,10 +47,14 @@ import com.android.systemui.statusbar.NotificationGroupingUtil;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import java.util.ArrayList;
@@ -56,7 +64,7 @@ import java.util.List;
* A container containing child notifications
*/
public class NotificationChildrenContainer extends ViewGroup
implements NotificationFadeAware {
implements NotificationFadeAware, Roundable {
private static final String TAG = "NotificationChildrenContainer";
@@ -100,9 +108,9 @@ public class NotificationChildrenContainer extends ViewGroup
private boolean mEnableShadowOnChildNotifications;
private NotificationHeaderView mNotificationHeader;
private NotificationViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderView mNotificationHeaderLowPriority;
private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
private NotificationHeaderViewWrapper mNotificationHeaderWrapperLowPriority;
private NotificationGroupingUtil mGroupingUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
@@ -110,7 +118,8 @@ public class NotificationChildrenContainer extends ViewGroup
private OnClickListener mHeaderClickListener;
private ViewGroup mCurrentHeader;
private boolean mIsConversation;
private Path mChildClipPath = null;
private final Path mHeaderPath = new Path();
private boolean mShowGroupCountInExpander;
private boolean mShowDividersWhenExpanded;
private boolean mHideDividersDuringExpand;
@@ -119,6 +128,8 @@ public class NotificationChildrenContainer extends ViewGroup
private float mHeaderVisibleAmount = 1.0f;
private int mUntruncatedChildCount;
private boolean mContainingNotificationIsFaded = false;
private RoundableState mRoundableState;
private boolean mIsNotificationGroupCornerEnabled;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -132,10 +143,14 @@ public class NotificationChildrenContainer extends ViewGroup
this(context, attrs, defStyleAttr, 0);
}
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
public NotificationChildrenContainer(
Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mHybridGroupManager = new HybridGroupManager(getContext());
mRoundableState = new RoundableState(this, this, 0f);
initDimens();
setClipChildren(false);
}
@@ -167,6 +182,12 @@ public class NotificationChildrenContainer extends ViewGroup
mHybridGroupManager.initDimens();
}
@NonNull
@Override
public RoundableState getRoundableState() {
return mRoundableState;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount =
@@ -271,7 +292,7 @@ public class NotificationChildrenContainer extends ViewGroup
/**
* Add a child notification to this view.
*
* @param row the row to add
* @param row the row to add
* @param childIndex the index to add it at, if -1 it will be added at the end
*/
public void addNotification(ExpandableNotificationRow row, int childIndex) {
@@ -347,8 +368,11 @@ public class NotificationChildrenContainer extends ViewGroup
mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
.setVisibility(VISIBLE);
mNotificationHeader.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
mNotificationHeader, mContainingNotification);
mNotificationHeaderWrapper =
(NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
getContext(),
mNotificationHeader,
mContainingNotification);
addView(mNotificationHeader, 0);
invalidate();
} else {
@@ -381,8 +405,11 @@ public class NotificationChildrenContainer extends ViewGroup
mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button)
.setVisibility(VISIBLE);
mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
mNotificationHeaderLowPriority, mContainingNotification);
mNotificationHeaderWrapperLowPriority =
(NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
getContext(),
mNotificationHeaderLowPriority,
mContainingNotification);
addView(mNotificationHeaderLowPriority, 0);
invalidate();
} else {
@@ -461,7 +488,9 @@ public class NotificationChildrenContainer extends ViewGroup
return mAttachedChildren;
}
/** To be called any time the rows have been updated */
/**
* To be called any time the rows have been updated
*/
public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {
// we don't modify it the group is expanded or if we are expanding it
@@ -475,7 +504,6 @@ public class NotificationChildrenContainer extends ViewGroup
}
/**
*
* @return the intrinsic size of this children container, i.e the natural fully expanded state
*/
public int getIntrinsicHeight() {
@@ -485,7 +513,7 @@ public class NotificationChildrenContainer extends ViewGroup
/**
* @return the intrinsic height with a number of children given
* in @param maxAllowedVisibleChildren
* in @param maxAllowedVisibleChildren
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
if (showingAsLowPriority()) {
@@ -539,7 +567,8 @@ public class NotificationChildrenContainer extends ViewGroup
/**
* Update the state of all its children based on a linear layout algorithm.
* @param parentState the state of the parent
*
* @param parentState the state of the parent
* @param ambientState the ambient state containing ambient information
*/
public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
@@ -655,14 +684,17 @@ public class NotificationChildrenContainer extends ViewGroup
* When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
* height, children in the group after this are gone.
*
* @param child the child who's height to adjust.
* @param child the child who's height to adjust.
* @param parentHeight the height of the parent.
* @param childState the state to update.
* @param yPosition the yPosition of the view.
* @param childState the state to update.
* @param yPosition the yPosition of the view.
* @return true if children after this one should be hidden.
*/
private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
int parentHeight, ExpandableViewState childState, int yPosition) {
private boolean updateChildStateForExpandedGroup(
ExpandableNotificationRow child,
int parentHeight,
ExpandableViewState childState,
int yPosition) {
final int top = yPosition + child.getClipTopAmount();
final int intrinsicHeight = child.getIntrinsicHeight();
final int bottom = top + intrinsicHeight;
@@ -690,13 +722,15 @@ public class NotificationChildrenContainer extends ViewGroup
if (mIsLowPriority
|| (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded())
|| (mContainingNotification.isHeadsUpState()
&& mContainingNotification.canShowHeadsUp())) {
&& mContainingNotification.canShowHeadsUp())) {
return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
}
return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
}
/** Applies state to children. */
/**
* Applies state to children.
*/
public void applyState() {
int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState();
@@ -768,17 +802,73 @@ public class NotificationChildrenContainer extends ViewGroup
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean isCanvasChanged = false;
Path clipPath = mChildClipPath;
if (clipPath != null) {
final float translation;
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child;
translation = notificationRow.getTranslation();
} else {
translation = child.getTranslationX();
}
isCanvasChanged = true;
canvas.save();
if (mIsNotificationGroupCornerEnabled && translation != 0f) {
clipPath.offset(translation, 0f);
canvas.clipPath(clipPath);
clipPath.offset(-translation, 0f);
} else {
canvas.clipPath(clipPath);
}
}
if (child instanceof NotificationHeaderView
&& mNotificationHeaderWrapper.hasRoundedCorner()) {
float[] radii = mNotificationHeaderWrapper.getUpdatedRadii();
mHeaderPath.reset();
mHeaderPath.addRoundRect(
child.getLeft(),
child.getTop(),
child.getRight(),
child.getBottom(),
radii,
Direction.CW
);
if (!isCanvasChanged) {
isCanvasChanged = true;
canvas.save();
}
canvas.clipPath(mHeaderPath);
}
if (isCanvasChanged) {
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
} else {
// If there have been no changes to the canvas we can proceed as usual
return super.drawChild(canvas, child, drawingTime);
}
}
/**
* This is called when the children expansion has changed and positions the children properly
* for an appear animation.
*
*/
public void prepareExpansionChanged() {
// TODO: do something that makes sense, like placing the invisible views correctly
return;
}
/** Animate to a given state. */
/**
* Animate to a given state.
*/
public void startAnimationToState(AnimationProperties properties) {
int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState();
@@ -1102,7 +1192,8 @@ public class NotificationChildrenContainer extends ViewGroup
* Get the minimum Height for this group.
*
* @param maxAllowedVisibleChildren the number of children that should be visible
* @param likeHighPriority if the height should be calculated as if it were not low priority
* @param likeHighPriority if the height should be calculated as if it were not low
* priority
*/
private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation);
@@ -1112,10 +1203,13 @@ public class NotificationChildrenContainer extends ViewGroup
* Get the minimum Height for this group.
*
* @param maxAllowedVisibleChildren the number of children that should be visible
* @param likeHighPriority if the height should be calculated as if it were not low priority
* @param headerTranslation the translation amount of the header
* @param likeHighPriority if the height should be calculated as if it were not low
* priority
* @param headerTranslation the translation amount of the header
*/
private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority,
private int getMinHeight(
int maxAllowedVisibleChildren,
boolean likeHighPriority,
int headerTranslation) {
if (!likeHighPriority && showingAsLowPriority()) {
if (mNotificationHeaderLowPriority == null) {
@@ -1274,16 +1368,19 @@ public class NotificationChildrenContainer extends ViewGroup
return mUserLocked;
}
public void setCurrentBottomRoundness(float currentBottomRoundness) {
@Override
public void applyRoundness() {
Roundable.super.applyRoundness();
boolean last = true;
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) {
continue;
}
float bottomRoundness = last ? currentBottomRoundness : 0.0f;
child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
child.setTopRoundness(0.0f, false /* animate */);
child.requestBottomRoundness(
last ? getBottomRoundness() : 0f,
/* animate = */ isShown(),
SourceType.DefaultValue);
last = false;
}
}
@@ -1293,7 +1390,9 @@ public class NotificationChildrenContainer extends ViewGroup
mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
}
/** Shows the given feedback icon, or hides the icon if null. */
/**
* Shows the given feedback icon, or hides the icon if null.
*/
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mNotificationHeaderWrapper != null) {
mNotificationHeaderWrapper.setFeedbackIcon(icon);
@@ -1325,4 +1424,26 @@ public class NotificationChildrenContainer extends ViewGroup
child.setNotificationFaded(faded);
}
}
/**
* Allow to define a path the clip the children in #drawChild()
*
* @param childClipPath path used to clip the children
*/
public void setChildClipPath(@Nullable Path childClipPath) {
mChildClipPath = childClipPath;
invalidate();
}
public NotificationHeaderViewWrapper getNotificationHeaderWrapper() {
return mNotificationHeaderWrapper;
}
/**
* Enable the support for rounded corner in notification group
* @param enabled true if is supported
*/
public void enableNotificationGroupCorner(boolean enabled) {
mIsNotificationGroupCornerEnabled = enabled;
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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