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

Commit 37b0266c authored by Andreas Miko's avatar Andreas Miko
Browse files

Introduce new gap height between buckets and fix bundle gaps

In Figma the gaps between buckets are 4dp but so far we had no
mechanism that creates such gaps. This introduces gaps between buckets
to be 4dp and fixes the gaps between bundles being too large (16dp).

Bug: b/416474210
Test: Verified in hsv that gaps between buckets (and bundles) are now
      4dp
Flag: com.android.systemui.notification_bundle_ui
Change-Id: I0d8b443cf9083f8b70ced152b196aa9a624c8dc1
parent 4cafee42
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.data.repository.HeadsUpReposi
import com.android.systemui.statusbar.notification.headsup.AvalancheController
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -48,9 +49,10 @@ private const val MAX_PULSE_HEIGHT = 100000f
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
class AmbientStateTest(flags: FlagsParameterization) : SysuiTestCase() {
    private val kosmos = testKosmos()

    private val dumpManager = mock<DumpManager>()
    private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false }
    private val sectionProvider = kosmos.stackScrollAlgorithmSectionProvider
    private val bypassController = StackScrollAlgorithm.BypassController { false }
    private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
    private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
@@ -395,17 +397,18 @@ class AmbientStateTest(flags: FlagsParameterization) : SysuiTestCase() {

        assertThat(sut.isClosing).isFalse()
    }

    // endregion

    // region isPulsing
    @Test
    @EnableFlags(NotificationBundleUi.FLAG_NAME)
    fun isPulsing_key() {
        whenever(headsupRepository.isHeadsUpEntry(anyString())).thenReturn(true);
        whenever(headsupRepository.isHeadsUpEntry(anyString())).thenReturn(true)
        sut.isPulsing = true
        assertThat(sut.isPulsing("key")).isTrue()

        whenever(headsupRepository.isHeadsUpEntry(anyString())).thenReturn(false);
        whenever(headsupRepository.isHeadsUpEntry(anyString())).thenReturn(false)
        sut.isPulsing = true
        assertThat(sut.isPulsing("key")).isFalse()
    }
+273 −1
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import android.annotation.DimenRes
import android.content.pm.PackageManager
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -36,6 +37,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
import com.android.systemui.surfaceeffects.utils.MathUtils
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
@@ -79,11 +81,12 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
            layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
        }
    private val footerView = FooterView(context, /* attrs= */ null)

    private val ambientState =
        AmbientState(
            context,
            dumpManager,
            /* sectionProvider */ { _, _ -> false },
            kosmos.stackScrollAlgorithmSectionProvider,
            /* bypassController */ { false },
            mStatusBarKeyguardViewManager,
            largeScreenShadeInterpolator,
@@ -102,6 +105,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
        testableResources.resources.getDimensionPixelSize(id).toFloat()

    private val notifSectionDividerGap = px(R.dimen.notification_section_divider_height)
    private val bundleGap = px(R.dimen.bundle_divider_height)
    private val scrimPadding = px(R.dimen.notification_side_paddings)
    private val baseZ by lazy { ambientState.baseZHeight }
    private val headsUpZ = px(R.dimen.heads_up_pinned_elevation)
@@ -153,6 +157,274 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
        return context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
    }

    @Test
    @EnableFlags(NotificationBundleUi.FLAG_NAME)
    fun getGapHeightForChild_returnsBundleGapHeight_whenChildIsBundle() {
        // Assemble
        val child = mock<ExpandableNotificationRow>()
        val previousChild = mock<View>()
        whenever(child.isBundle()).thenReturn(true)
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(any(), any()))
            .thenReturn(false)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0.5f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(bundleGap)
    }

    @Test
    fun getGapHeightForChild_returnsBundleGapHeight_whenPreviousChildIsBundle() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<ExpandableNotificationRow>()
        whenever(previousChild.isBundle()).thenReturn(true) // Previous child is a bundle
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(any(), any()))
            .thenReturn(false)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0.5f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(bundleGap)
    }

    @Test
    fun getGapHeightForChild_returnsBigGap_whenChildNeedsGapHeightAndNotOnKeyguard() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<View>()

        // Trigger childNeedsGapHeight
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(child, previousChild))
            .thenReturn(true)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(bigGap)
    }

    @Test
    fun getGapHeightForChild_returnsSmallGap_whenChildNeedsGapHeightAndOnKeyguard() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<View>()

        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(child, previousChild))
            .thenReturn(true)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                true,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(smallGap)
    }

    @Test
    fun getGapHeightForChild_returnsLerpedGap_whenChildNeedsGapHeightAndFractionToShade() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<View>()

        // Trigger childNeedsGapHeight
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(child, previousChild))
            .thenReturn(true)
        val fractionToShade = 0.75f
        val expectedGap = MathUtils.lerp(smallGap, bigGap, fractionToShade)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1, // visibleIndex > 0
                child,
                previousChild,
                fractionToShade,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(expectedGap)
    }

    @Test
    fun getGapHeightForChild_returnsZero_whenNoGapConditionMet() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<View>()
        // Ensure no section gap
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(any(), any()))
            .thenReturn(false)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(0f)
    }

    @Test
    fun getGapHeightForChild_returnsZero_whenPreviousChildIsSectionHeaderViewForBundle() {
        // Assemble
        val child = mock<ExpandableNotificationRow>()
        val previousChild = mock<SectionHeaderView>()
        whenever(child.isBundle()).thenReturn(true)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(0f) // childNeedsBundleGap should return false
    }

    @Test
    fun getGapHeightForChild_returnsZero_whenChildIsFooterViewForBundle() {
        // Assemble
        val child = mock<FooterView>() // Specific type
        val previousChild = mock<ExpandableNotificationRow>()
        whenever(previousChild.isBundle()).thenReturn(true) // previousChild is a bundle
        // Set child to FooterView to trigger isNotFirstOrLastView returning false
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(any(), any()))
            .thenReturn(false) // ensure no section gap

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(0f) // childNeedsBundleGap should return false
    }

    @Test
    fun getGapHeightForChild_returnsZero_whenPreviousChildIsSectionHeaderViewForSectionGap() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<SectionHeaderView>() // Specific type
        // Set section provider to return true, but isNotFirstOrLastView will make it false
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(child, previousChild))
            .thenReturn(true)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(0f) // childNeedsGapHeight should return false
    }

    @Test
    fun getGapHeightForChild_returnsZero_whenChildIsFooterViewForSectionGap() {
        // Assemble
        val child = mock<FooterView>() // Specific type
        val previousChild = mock<View>()
        // Set section provider to return true, but isNotFirstOrLastView will make it false
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(child, previousChild))
            .thenReturn(true)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                1,
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(0f) // childNeedsGapHeight should return false
    }

    @Test
    fun getGapHeightForChild_returnsZero_whenVisibleIndexIsZeroForSectionGap() {
        // Assemble
        val child = mock<View>()
        val previousChild = mock<View>()
        // Set section provider to return true, but visibleIndex = 0 will make it false
        whenever(kosmos.stackScrollAlgorithmSectionProvider.beginsSection(child, previousChild))
            .thenReturn(true)

        // Act
        val gapHeight =
            stackScrollAlgorithm.getGapHeightForChild(
                kosmos.stackScrollAlgorithmSectionProvider,
                0, // visibleIndex = 0
                child,
                previousChild,
                0f,
                false,
            )

        // Assert
        assertThat(gapHeight).isEqualTo(0f) // childNeedsGapHeight should return false
    }

    @Test
    @EnableSceneContainer
    fun resetViewStates_childPositionedAtStackTop() {
+3 −0
Original line number Diff line number Diff line
@@ -811,6 +811,9 @@
    <!-- The height of the divider between the individual notifications. -->
    <dimen name="notification_divider_height">2dp</dimen>

    <!-- The *additional* height of the divider between bundles. -->
    <dimen name="bundle_divider_height">2dp</dimen>

    <!-- The min distance the notifications should be from the lock icon on the lock screen. -->
    <dimen name="min_lock_icon_padding">48dp</dimen>

+1 −5
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import com.android.systemui.statusbar.notification.dagger.SocialHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.foldToSparseArray
@@ -120,10 +119,7 @@ internal constructor(
    }

    fun createSectionsForBuckets(): Array<NotificationSection> =
        PriorityBucket
            .getAllInOrder()
            .map { NotificationSection(it) }
            .toTypedArray()
        PriorityBucket.getAllInOrder().map { NotificationSection(it) }.toTypedArray()

    /** Reinflates the entire notification header, including all decoration views. */
    fun reinflateViews() {
+45 −15
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ public class StackScrollAlgorithm {
    private final HeadsUpAnimator mHeadsUpAnimator;

    private float mPaddingBetweenElements;
    private float mBundleGapHeight;
    private float mGapHeight;
    private float mGapHeightOnLockscreen;
    private int mCollapsedSize;
@@ -116,6 +117,8 @@ public class StackScrollAlgorithm {
        mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
        mGapHeightOnLockscreen = res.getDimensionPixelSize(
                R.dimen.notification_section_divider_height_lockscreen);
        mBundleGapHeight = res.getDimensionPixelSize(
                R.dimen.bundle_divider_height);
        mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
        mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
        mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(context);
@@ -516,6 +519,11 @@ public class StackScrollAlgorithm {
        for (int i = 0; i < state.visibleChildren.size(); i++) {
            final ExpandableView view = state.visibleChildren.get(i);

            if (NotificationBundleUi.isEnabled()) {
                currentY += getGapHeightForChild(ambientState.getSectionProvider(), i, view,
                        getPreviousView(i, state), ambientState.getFractionToShade(),
                        ambientState.isOnKeyguard());
            } else {
                final boolean applyGapHeight = childNeedsGapHeight(
                        ambientState.getSectionProvider(), i,
                        view, getPreviousView(i, state));
@@ -523,6 +531,7 @@ public class StackScrollAlgorithm {
                    currentY += getGapForLocation(
                            ambientState.getFractionToShade(), ambientState.isOnKeyguard());
                }
            }

            if (ambientState.getShelf() != null) {
                final float shelfStart = ambientState.getStackEndHeight()
@@ -675,6 +684,14 @@ public class StackScrollAlgorithm {
                algorithmState, ambientState);

        // Add gap between sections.
        if (NotificationBundleUi.isEnabled()) {
            final float gap = getGapHeightForChild(ambientState.getSectionProvider(), i, view,
                    getPreviousView(i, algorithmState), ambientState.getFractionToShade(),
                    ambientState.isOnKeyguard());

            algorithmState.mCurrentYPosition += expansionFraction * gap;
            algorithmState.mCurrentExpandedYPosition += gap;
        } else {
            final boolean applyGapHeight =
                    childNeedsGapHeight(
                            ambientState.getSectionProvider(), i,
@@ -685,6 +702,7 @@ public class StackScrollAlgorithm {
                algorithmState.mCurrentYPosition += expansionFraction * gap;
                algorithmState.mCurrentExpandedYPosition += gap;
            }
        }

        // Must set viewState.yTranslation _before_ use.
        // Incoming views have yTranslation=0 by default.
@@ -807,14 +825,26 @@ public class StackScrollAlgorithm {
            float fractionToShade,
            boolean onKeyguard) {

        if (childNeedsGapHeight(sectionProvider, visibleIndex, child,
                previousChild)) {
        if (NotificationBundleUi.isEnabled() && childNeedsBundleGap(child, previousChild))  {
            return mBundleGapHeight;
        } else if (childNeedsGapHeight(sectionProvider, visibleIndex, child, previousChild)) {
            return getGapForLocation(fractionToShade, onKeyguard);
        } else {
            return 0;
        }
    }

    private boolean childNeedsBundleGap(View child, View previousChild) {
        return (isBundle(child) || isBundle(previousChild))
                && !(previousChild instanceof SectionHeaderView)
                && !(child instanceof FooterView);
    }

    private boolean isBundle(View view) {
        return view instanceof ExpandableNotificationRow
                && ((ExpandableNotificationRow) view).isBundle();
    }

    @VisibleForTesting
    float getGapForLocation(float fractionToShade, boolean onKeyguard) {
        if (fractionToShade > 0f) {