Loading packages/SystemUI/src/com/android/systemui/flags/Flags.kt +1 −2 Original line number Diff line number Diff line Loading @@ -83,8 +83,7 @@ object Flags { val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true) @JvmField val NOTIFICATION_GROUP_CORNER = unreleasedFlag(116, "notification_group_corner", teamfood = true) val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true) // TODO(b/259217907) @JvmField Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +86 −64 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.systemui.R; 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.LegacySourceType; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; Loading Loading @@ -66,6 +67,8 @@ public class NotificationShelf extends ActivatableNotificationView implements // the next icon has translated out of the way, to avoid overlapping. private static final Interpolator ICON_ALPHA_INTERPOLATOR = new PathInterpolator(0.6f, 0f, 0.6f, 0f); private static final SourceType BASE_VALUE = SourceType.from("BaseValue"); private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll"); private NotificationIconContainer mShelfIcons; private int[] mTmp = new int[2]; Loading Loading @@ -112,12 +115,14 @@ public class NotificationShelf extends ActivatableNotificationView implements setClipChildren(false); setClipToPadding(false); mShelfIcons.setIsStaticLayout(false); requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue); requestTopRoundness(1f, false, SourceType.DefaultValue); requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false); // 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 if (!mUseRoundnessSourceTypes) { // 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 setFirstInSection(true); } updateResources(); } Loading @@ -125,6 +130,9 @@ public class NotificationShelf extends ActivatableNotificationView implements NotificationStackScrollLayoutController hostLayoutController) { mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { child.requestRoundnessReset(SHELF_SCROLL); }); } private void updateResources() { Loading Loading @@ -185,7 +193,9 @@ public class NotificationShelf extends ActivatableNotificationView implements + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')'; } /** Update the state of the shelf. */ /** * Update the state of the shelf. */ public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, AmbientState ambientState) { ExpandableView lastView = ambientState.getLastVisibleBackgroundChild(); Loading Loading @@ -478,13 +488,15 @@ public class NotificationShelf extends ActivatableNotificationView implements } } private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart, private void updateCornerRoundnessOnScroll( ActivatableNotificationView anv, float viewStart, float shelfStart) { final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard() && !mAmbientState.isShadeExpanded() && anv instanceof ExpandableNotificationRow && ((ExpandableNotificationRow) anv).isHeadsUp(); && anv.isHeadsUp(); final boolean isHunGoingToShade = mAmbientState.isShadeExpanded() && anv == mAmbientState.getTrackedHeadsUpRow(); Loading @@ -506,41 +518,40 @@ public class NotificationShelf extends ActivatableNotificationView implements * mAmbientState.getExpansionFraction(); final float cornerAnimationTop = shelfStart - cornerAnimationDistance; if (viewEnd >= cornerAnimationTop) { // Round bottom corners within animation bounds final float changeFraction = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); anv.requestBottomRoundness( /* value = */ 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.requestBottomRoundness( /* value = */ anv.isLastInSection() ? 1f : 0f, /* animate = */ false, SourceType.OnScroll); final SourceType sourceType; if (mUseRoundnessSourceTypes) { sourceType = SHELF_SCROLL; } else { sourceType = LegacySourceType.OnScroll; } if (viewStart >= cornerAnimationTop) { final float topValue; if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) { topValue = 1f; } else if (viewStart >= cornerAnimationTop) { // Round top corners within animation bounds final float changeFraction = MathUtils.saturate( topValue = MathUtils.saturate( (viewStart - cornerAnimationTop) / cornerAnimationDistance); anv.requestTopRoundness( /* value = */ anv.isFirstInSection() ? 1f : changeFraction, /* animate = */ false, SourceType.OnScroll); } else { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. topValue = 0f; } anv.requestTopRoundness(topValue, sourceType, /* animate = */ false); } else if (viewStart < cornerAnimationTop) { final float bottomValue; if (!mUseRoundnessSourceTypes && anv.isLastInSection()) { bottomValue = 1f; } else if (viewEnd >= cornerAnimationTop) { // Round bottom corners within animation bounds bottomValue = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); } else { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. anv.requestTopRoundness( /* value = */ anv.isFirstInSection() ? 1f : 0f, /* animate = */ false, SourceType.OnScroll); bottomValue = 0f; } anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false); } /** Loading Loading @@ -626,6 +637,7 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * Update the clipping of this view. * * @return the amount that our own top should be clipped */ private int updateNotificationClipHeight(ExpandableView view, Loading Loading @@ -675,8 +687,14 @@ public class NotificationShelf extends ActivatableNotificationView implements * 0f is not in shelf. 1f is completely in shelf. */ @VisibleForTesting public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild, float shelfClipStart) { public float getAmountInShelf( int i, ExpandableView view, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild, float shelfClipStart ) { // Let's calculate how much the view is in the shelf float viewStart = view.getTranslationY(); Loading Loading @@ -755,8 +773,13 @@ public class NotificationShelf extends ActivatableNotificationView implements return start; } private void updateIconPositioning(ExpandableView view, float iconTransitionAmount, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { private void updateIconPositioning( ExpandableView view, float iconTransitionAmount, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild ) { StatusBarIconView icon = view.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); if (iconState == null) { Loading Loading @@ -981,12 +1004,11 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * This method resets the OnScroll roundness of a view to 0f * * <p> * Note: This should be the only class that handles roundness {@code SourceType.OnScroll} */ public static void resetOnScrollRoundness(ExpandableView expandableView) { expandableView.requestTopRoundness(0f, false, SourceType.OnScroll); expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll); public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) { expandableView.requestRoundnessReset(LegacySourceType.OnScroll); } public class ShelfState extends ExpandableViewState { Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java +8 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; Loading @@ -42,14 +44,17 @@ public class NotificationShelfController { private AmbientState mAmbientState; @Inject public NotificationShelfController(NotificationShelf notificationShelf, public NotificationShelfController( NotificationShelf notificationShelf, ActivatableNotificationViewController activatableNotificationViewController, KeyguardBypassController keyguardBypassController, SysuiStatusBarStateController statusBarStateController) { SysuiStatusBarStateController statusBarStateController, FeatureFlags featureFlags) { mView = notificationShelf; mActivatableNotificationViewController = activatableNotificationViewController; mKeyguardBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)); mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { Loading Loading @@ -88,7 +93,7 @@ public class NotificationShelfController { public @View.Visibility int getVisibility() { return mView.getVisibility(); }; } public void setCollapsedIcons(NotificationIconContainer notificationIcons) { mView.setCollapsedIcons(notificationIcons); Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +156 −8 Original line number Diff line number Diff line Loading @@ -74,8 +74,8 @@ interface Roundable { @JvmDefault fun requestTopRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, animate: Boolean, sourceType: SourceType, animate: Boolean, ): Boolean { val roundnessMap = roundableState.topRoundnessMap val lastValue = roundnessMap.values.maxOrNull() ?: 0f Loading Loading @@ -104,6 +104,30 @@ interface Roundable { return false } /** * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the * view is shown. * * 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 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, sourceType: SourceType, ): Boolean { return requestTopRoundness( value = value, sourceType = sourceType, animate = roundableState.targetView.isShown ) } /** * Request the bottom roundness [value] for a specific [sourceType]. * Loading @@ -119,8 +143,8 @@ interface Roundable { @JvmDefault fun requestBottomRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, animate: Boolean, sourceType: SourceType, animate: Boolean, ): Boolean { val roundnessMap = roundableState.bottomRoundnessMap val lastValue = roundnessMap.values.maxOrNull() ?: 0f Loading Loading @@ -149,9 +173,101 @@ interface Roundable { return false } /** * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if * the view is shown. * * 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 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, sourceType: SourceType, ): Boolean { return requestBottomRoundness( value = value, sourceType = sourceType, animate = roundableState.targetView.isShown ) } /** * Request the roundness [value] for a specific [sourceType]. * * The top/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 top top value between 0f and 1f. * @param bottom bottom value between 0f and 1f. * @param sourceType the source from which the request for roundness comes. * @param animate true if it should animate to that value. * @return Whether the roundness was changed. */ @JvmDefault fun requestRoundness( @FloatRange(from = 0.0, to = 1.0) top: Float, @FloatRange(from = 0.0, to = 1.0) bottom: Float, sourceType: SourceType, animate: Boolean, ): Boolean { val hasTopChanged = requestTopRoundness(value = top, sourceType = sourceType, animate = animate) val hasBottomChanged = requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate) return hasTopChanged || hasBottomChanged } /** * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view * is shown. * * The top/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 top top value between 0f and 1f. * @param bottom bottom value between 0f and 1f. * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ @JvmDefault fun requestRoundness( @FloatRange(from = 0.0, to = 1.0) top: Float, @FloatRange(from = 0.0, to = 1.0) bottom: Float, sourceType: SourceType, ): Boolean { return requestRoundness( top = top, bottom = bottom, sourceType = sourceType, animate = roundableState.targetView.isShown, ) } /** * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown. * * The top/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 sourceType the source from which the request for roundness comes. */ @JvmDefault fun requestRoundnessReset(sourceType: SourceType) { requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType) } /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */ @JvmDefault fun applyRoundness() { fun applyRoundnessAndInvalidate() { roundableState.targetView.invalidate() } Loading Loading @@ -227,7 +343,7 @@ class RoundableState( /** Set the current top roundness */ internal fun setTopRoundness( value: Float, animated: Boolean = targetView.isShown, animated: Boolean, ) { PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated) } Loading @@ -235,11 +351,19 @@ class RoundableState( /** Set the current bottom roundness */ internal fun setBottomRoundness( value: Float, animated: Boolean = targetView.isShown, animated: Boolean, ) { PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated) } fun debugString() = buildString { append("TargetView: ${targetView.hashCode()} ") append("Top: $topRoundness ") append(topRoundnessMap.map { "${it.key} ${it.value}" }) append(" Bottom: $bottomRoundness ") append(bottomRoundnessMap.map { "${it.key} ${it.value}" }) } companion object { private val DURATION: AnimationProperties = AnimationProperties() Loading @@ -252,7 +376,7 @@ class RoundableState( override fun setValue(view: View, value: Float) { roundable.roundableState.topRoundness = value roundable.applyRoundness() roundable.applyRoundnessAndInvalidate() } }, R.id.top_roundess_animator_tag, Loading @@ -267,7 +391,7 @@ class RoundableState( override fun setValue(view: View, value: Float) { roundable.roundableState.bottomRoundness = value roundable.applyRoundness() roundable.applyRoundnessAndInvalidate() } }, R.id.bottom_roundess_animator_tag, Loading @@ -277,7 +401,31 @@ class RoundableState( } } enum class SourceType { /** * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a * private property of a class. */ interface SourceType { companion object { /** * This is the most convenient way to define a new [SourceType]. * * For example: * * ```kotlin * private val SECTION = SourceType.from("Section") * ``` */ @JvmStatic fun from(name: String) = object : SourceType { override fun toString() = name } } } @Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()")) enum class LegacySourceType : SourceType { DefaultValue, OnDismissAnimation, OnScroll, Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +35 −2 Original line number Diff line number Diff line Loading @@ -39,9 +39,13 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.HashSet; import java.util.Set; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} * to implement dimming/activating on Keyguard for the double-tap gesture Loading Loading @@ -91,6 +95,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView = new PathInterpolator(0.6f, 0, 0.5f, 1); private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR = new PathInterpolator(0, 0, 0.5f, 1); private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>(); private int mTintedRippleColor; private int mNormalRippleColor; private Gefingerpoken mTouchHandler; Loading Loading @@ -134,6 +139,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mDismissed; private boolean mRefocusOnDismiss; private AccessibilityManager mAccessibilityManager; protected boolean mUseRoundnessSourceTypes; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); Loading Loading @@ -613,9 +619,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected void resetAllContentAlphas() {} @Override public void applyRoundness() { super.applyRoundness(); public void applyRoundnessAndInvalidate() { applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius()); super.applyRoundnessAndInvalidate(); } @Override Loading Loading @@ -775,6 +781,33 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAccessibilityManager = accessibilityManager; } /** * Enable the support for rounded corner based on the SourceType * @param enabled true if is supported */ public void useRoundnessSourceTypes(boolean enabled) { mUseRoundnessSourceTypes = enabled; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) { for (SourceType sourceType : mOnDetachResetRoundness) { requestRoundnessReset(sourceType); } mOnDetachResetRoundness.clear(); } } /** * SourceType which should be reset when this View is detached * @param sourceType will be reset on View detached */ public void addOnDetachResetRoundness(SourceType sourceType) { mOnDetachResetRoundness.add(sourceType); } public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); Loading Loading
packages/SystemUI/src/com/android/systemui/flags/Flags.kt +1 −2 Original line number Diff line number Diff line Loading @@ -83,8 +83,7 @@ object Flags { val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true) @JvmField val NOTIFICATION_GROUP_CORNER = unreleasedFlag(116, "notification_group_corner", teamfood = true) val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true) // TODO(b/259217907) @JvmField Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +86 −64 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.systemui.R; 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.LegacySourceType; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; Loading Loading @@ -66,6 +67,8 @@ public class NotificationShelf extends ActivatableNotificationView implements // the next icon has translated out of the way, to avoid overlapping. private static final Interpolator ICON_ALPHA_INTERPOLATOR = new PathInterpolator(0.6f, 0f, 0.6f, 0f); private static final SourceType BASE_VALUE = SourceType.from("BaseValue"); private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll"); private NotificationIconContainer mShelfIcons; private int[] mTmp = new int[2]; Loading Loading @@ -112,12 +115,14 @@ public class NotificationShelf extends ActivatableNotificationView implements setClipChildren(false); setClipToPadding(false); mShelfIcons.setIsStaticLayout(false); requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue); requestTopRoundness(1f, false, SourceType.DefaultValue); requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false); // 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 if (!mUseRoundnessSourceTypes) { // 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 setFirstInSection(true); } updateResources(); } Loading @@ -125,6 +130,9 @@ public class NotificationShelf extends ActivatableNotificationView implements NotificationStackScrollLayoutController hostLayoutController) { mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { child.requestRoundnessReset(SHELF_SCROLL); }); } private void updateResources() { Loading Loading @@ -185,7 +193,9 @@ public class NotificationShelf extends ActivatableNotificationView implements + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')'; } /** Update the state of the shelf. */ /** * Update the state of the shelf. */ public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, AmbientState ambientState) { ExpandableView lastView = ambientState.getLastVisibleBackgroundChild(); Loading Loading @@ -478,13 +488,15 @@ public class NotificationShelf extends ActivatableNotificationView implements } } private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart, private void updateCornerRoundnessOnScroll( ActivatableNotificationView anv, float viewStart, float shelfStart) { final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard() && !mAmbientState.isShadeExpanded() && anv instanceof ExpandableNotificationRow && ((ExpandableNotificationRow) anv).isHeadsUp(); && anv.isHeadsUp(); final boolean isHunGoingToShade = mAmbientState.isShadeExpanded() && anv == mAmbientState.getTrackedHeadsUpRow(); Loading @@ -506,41 +518,40 @@ public class NotificationShelf extends ActivatableNotificationView implements * mAmbientState.getExpansionFraction(); final float cornerAnimationTop = shelfStart - cornerAnimationDistance; if (viewEnd >= cornerAnimationTop) { // Round bottom corners within animation bounds final float changeFraction = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); anv.requestBottomRoundness( /* value = */ 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.requestBottomRoundness( /* value = */ anv.isLastInSection() ? 1f : 0f, /* animate = */ false, SourceType.OnScroll); final SourceType sourceType; if (mUseRoundnessSourceTypes) { sourceType = SHELF_SCROLL; } else { sourceType = LegacySourceType.OnScroll; } if (viewStart >= cornerAnimationTop) { final float topValue; if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) { topValue = 1f; } else if (viewStart >= cornerAnimationTop) { // Round top corners within animation bounds final float changeFraction = MathUtils.saturate( topValue = MathUtils.saturate( (viewStart - cornerAnimationTop) / cornerAnimationDistance); anv.requestTopRoundness( /* value = */ anv.isFirstInSection() ? 1f : changeFraction, /* animate = */ false, SourceType.OnScroll); } else { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. topValue = 0f; } anv.requestTopRoundness(topValue, sourceType, /* animate = */ false); } else if (viewStart < cornerAnimationTop) { final float bottomValue; if (!mUseRoundnessSourceTypes && anv.isLastInSection()) { bottomValue = 1f; } else if (viewEnd >= cornerAnimationTop) { // Round bottom corners within animation bounds bottomValue = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); } else { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. anv.requestTopRoundness( /* value = */ anv.isFirstInSection() ? 1f : 0f, /* animate = */ false, SourceType.OnScroll); bottomValue = 0f; } anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false); } /** Loading Loading @@ -626,6 +637,7 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * Update the clipping of this view. * * @return the amount that our own top should be clipped */ private int updateNotificationClipHeight(ExpandableView view, Loading Loading @@ -675,8 +687,14 @@ public class NotificationShelf extends ActivatableNotificationView implements * 0f is not in shelf. 1f is completely in shelf. */ @VisibleForTesting public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild, float shelfClipStart) { public float getAmountInShelf( int i, ExpandableView view, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild, float shelfClipStart ) { // Let's calculate how much the view is in the shelf float viewStart = view.getTranslationY(); Loading Loading @@ -755,8 +773,13 @@ public class NotificationShelf extends ActivatableNotificationView implements return start; } private void updateIconPositioning(ExpandableView view, float iconTransitionAmount, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { private void updateIconPositioning( ExpandableView view, float iconTransitionAmount, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild ) { StatusBarIconView icon = view.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); if (iconState == null) { Loading Loading @@ -981,12 +1004,11 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * This method resets the OnScroll roundness of a view to 0f * * <p> * Note: This should be the only class that handles roundness {@code SourceType.OnScroll} */ public static void resetOnScrollRoundness(ExpandableView expandableView) { expandableView.requestTopRoundness(0f, false, SourceType.OnScroll); expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll); public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) { expandableView.requestRoundnessReset(LegacySourceType.OnScroll); } public class ShelfState extends ExpandableViewState { Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java +8 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; Loading @@ -42,14 +44,17 @@ public class NotificationShelfController { private AmbientState mAmbientState; @Inject public NotificationShelfController(NotificationShelf notificationShelf, public NotificationShelfController( NotificationShelf notificationShelf, ActivatableNotificationViewController activatableNotificationViewController, KeyguardBypassController keyguardBypassController, SysuiStatusBarStateController statusBarStateController) { SysuiStatusBarStateController statusBarStateController, FeatureFlags featureFlags) { mView = notificationShelf; mActivatableNotificationViewController = activatableNotificationViewController; mKeyguardBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)); mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { Loading Loading @@ -88,7 +93,7 @@ public class NotificationShelfController { public @View.Visibility int getVisibility() { return mView.getVisibility(); }; } public void setCollapsedIcons(NotificationIconContainer notificationIcons) { mView.setCollapsedIcons(notificationIcons); Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +156 −8 Original line number Diff line number Diff line Loading @@ -74,8 +74,8 @@ interface Roundable { @JvmDefault fun requestTopRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, animate: Boolean, sourceType: SourceType, animate: Boolean, ): Boolean { val roundnessMap = roundableState.topRoundnessMap val lastValue = roundnessMap.values.maxOrNull() ?: 0f Loading Loading @@ -104,6 +104,30 @@ interface Roundable { return false } /** * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the * view is shown. * * 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 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, sourceType: SourceType, ): Boolean { return requestTopRoundness( value = value, sourceType = sourceType, animate = roundableState.targetView.isShown ) } /** * Request the bottom roundness [value] for a specific [sourceType]. * Loading @@ -119,8 +143,8 @@ interface Roundable { @JvmDefault fun requestBottomRoundness( @FloatRange(from = 0.0, to = 1.0) value: Float, animate: Boolean, sourceType: SourceType, animate: Boolean, ): Boolean { val roundnessMap = roundableState.bottomRoundnessMap val lastValue = roundnessMap.values.maxOrNull() ?: 0f Loading Loading @@ -149,9 +173,101 @@ interface Roundable { return false } /** * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if * the view is shown. * * 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 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, sourceType: SourceType, ): Boolean { return requestBottomRoundness( value = value, sourceType = sourceType, animate = roundableState.targetView.isShown ) } /** * Request the roundness [value] for a specific [sourceType]. * * The top/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 top top value between 0f and 1f. * @param bottom bottom value between 0f and 1f. * @param sourceType the source from which the request for roundness comes. * @param animate true if it should animate to that value. * @return Whether the roundness was changed. */ @JvmDefault fun requestRoundness( @FloatRange(from = 0.0, to = 1.0) top: Float, @FloatRange(from = 0.0, to = 1.0) bottom: Float, sourceType: SourceType, animate: Boolean, ): Boolean { val hasTopChanged = requestTopRoundness(value = top, sourceType = sourceType, animate = animate) val hasBottomChanged = requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate) return hasTopChanged || hasBottomChanged } /** * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view * is shown. * * The top/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 top top value between 0f and 1f. * @param bottom bottom value between 0f and 1f. * @param sourceType the source from which the request for roundness comes. * @return Whether the roundness was changed. */ @JvmDefault fun requestRoundness( @FloatRange(from = 0.0, to = 1.0) top: Float, @FloatRange(from = 0.0, to = 1.0) bottom: Float, sourceType: SourceType, ): Boolean { return requestRoundness( top = top, bottom = bottom, sourceType = sourceType, animate = roundableState.targetView.isShown, ) } /** * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown. * * The top/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 sourceType the source from which the request for roundness comes. */ @JvmDefault fun requestRoundnessReset(sourceType: SourceType) { requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType) } /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */ @JvmDefault fun applyRoundness() { fun applyRoundnessAndInvalidate() { roundableState.targetView.invalidate() } Loading Loading @@ -227,7 +343,7 @@ class RoundableState( /** Set the current top roundness */ internal fun setTopRoundness( value: Float, animated: Boolean = targetView.isShown, animated: Boolean, ) { PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated) } Loading @@ -235,11 +351,19 @@ class RoundableState( /** Set the current bottom roundness */ internal fun setBottomRoundness( value: Float, animated: Boolean = targetView.isShown, animated: Boolean, ) { PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated) } fun debugString() = buildString { append("TargetView: ${targetView.hashCode()} ") append("Top: $topRoundness ") append(topRoundnessMap.map { "${it.key} ${it.value}" }) append(" Bottom: $bottomRoundness ") append(bottomRoundnessMap.map { "${it.key} ${it.value}" }) } companion object { private val DURATION: AnimationProperties = AnimationProperties() Loading @@ -252,7 +376,7 @@ class RoundableState( override fun setValue(view: View, value: Float) { roundable.roundableState.topRoundness = value roundable.applyRoundness() roundable.applyRoundnessAndInvalidate() } }, R.id.top_roundess_animator_tag, Loading @@ -267,7 +391,7 @@ class RoundableState( override fun setValue(view: View, value: Float) { roundable.roundableState.bottomRoundness = value roundable.applyRoundness() roundable.applyRoundnessAndInvalidate() } }, R.id.bottom_roundess_animator_tag, Loading @@ -277,7 +401,31 @@ class RoundableState( } } enum class SourceType { /** * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a * private property of a class. */ interface SourceType { companion object { /** * This is the most convenient way to define a new [SourceType]. * * For example: * * ```kotlin * private val SECTION = SourceType.from("Section") * ``` */ @JvmStatic fun from(name: String) = object : SourceType { override fun toString() = name } } } @Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()")) enum class LegacySourceType : SourceType { DefaultValue, OnDismissAnimation, OnScroll, Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +35 −2 Original line number Diff line number Diff line Loading @@ -39,9 +39,13 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.HashSet; import java.util.Set; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} * to implement dimming/activating on Keyguard for the double-tap gesture Loading Loading @@ -91,6 +95,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView = new PathInterpolator(0.6f, 0, 0.5f, 1); private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR = new PathInterpolator(0, 0, 0.5f, 1); private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>(); private int mTintedRippleColor; private int mNormalRippleColor; private Gefingerpoken mTouchHandler; Loading Loading @@ -134,6 +139,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mDismissed; private boolean mRefocusOnDismiss; private AccessibilityManager mAccessibilityManager; protected boolean mUseRoundnessSourceTypes; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); Loading Loading @@ -613,9 +619,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected void resetAllContentAlphas() {} @Override public void applyRoundness() { super.applyRoundness(); public void applyRoundnessAndInvalidate() { applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius()); super.applyRoundnessAndInvalidate(); } @Override Loading Loading @@ -775,6 +781,33 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAccessibilityManager = accessibilityManager; } /** * Enable the support for rounded corner based on the SourceType * @param enabled true if is supported */ public void useRoundnessSourceTypes(boolean enabled) { mUseRoundnessSourceTypes = enabled; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) { for (SourceType sourceType : mOnDetachResetRoundness) { requestRoundnessReset(sourceType); } mOnDetachResetRoundness.clear(); } } /** * SourceType which should be reset when this View is detached * @param sourceType will be reset on View detached */ public void addOnDetachResetRoundness(SourceType sourceType) { mOnDetachResetRoundness.add(sourceType); } public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); Loading