Merge "Add support for rounded notification in groups" into tm-qpr-dev am: 46b33d3f81
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20064744 Change-Id: I1c3a4c808d788ab17baede6457c240bc095f99a6 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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 **
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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?,
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user