Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5022740b authored by omarmt's avatar omarmt
Browse files

Define clear ownership for roundness SourceType

Test: atest RoundableTest
Test: manual test
Bug: 257000427
Change-Id: Ib9f2f35cc67f93da818830b807573bc54b075b8f
parent b18b7e97
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -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
+86 −64
Original line number Diff line number Diff line
@@ -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;
@@ -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];
@@ -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();
    }

@@ -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() {
@@ -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();
@@ -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();
@@ -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);
    }

    /**
@@ -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,
@@ -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();
@@ -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) {
@@ -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 {
+8 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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) {
@@ -88,7 +93,7 @@ public class NotificationShelfController {

    public @View.Visibility int getVisibility() {
        return mView.getVisibility();
    };
    }

    public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
        mView.setCollapsedIcons(notificationIcons);
+156 −8
Original line number Diff line number Diff line
@@ -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
@@ -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].
     *
@@ -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
@@ -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()
    }

@@ -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)
    }
@@ -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()
@@ -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,
@@ -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,
@@ -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,
+35 −2
Original line number Diff line number Diff line
@@ -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
@@ -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;
@@ -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);
@@ -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
@@ -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