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

Commit d201c411 authored by Lyn Han's avatar Lyn Han
Browse files

Increase vertical overlap between notification shelf and lock icon

Fixes: 214257536

Test: media player/group notification/shelf fits in space available
      with increased overlap between shelf and lock icon

Test: shelf-less stack does not overlap lock icon

Test: NotificationStackSizeCalculatorTest

Test: NotificationPanelViewControllerTest

Change-Id: I718a9d7e81b53277e19e588fbc85502cb2ed6b6c
parent 08a70e40
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -386,7 +386,7 @@

    <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen>

    <dimen name="shelf_and_lock_icon_overlap">5dp</dimen>
    <dimen name="shelf_and_lock_icon_overlap">@dimen/notification_shelf_height</dimen>

    <dimen name="notification_panel_margin_horizontal">0dp</dimen>

+88 −20
Original line number Diff line number Diff line
@@ -39,7 +39,11 @@ private const val TAG = "NotifStackSizeCalc"
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
private val SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)

/** Calculates number of notifications to display and the height of the notification stack. */
/**
 * Calculates number of notifications to display and the height of the notification stack.
 * "Notifications" refers to any ExpandableView that we show on lockscreen, which can include the
 * media player.
 */
@SysUISingleton
class NotificationStackSizeCalculator
@Inject
@@ -65,21 +69,60 @@ constructor(
    }

    /**
     * Given the [totalAvailableSpace] constraint, calculates how many notification to show.
     *
     * This number is only valid in keyguard.
     * Returns whether notifications and (shelf if visible) can fit in total space available.
     * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon.
     */
    private fun canStackFitInSpace(
        stackHeight: StackHeight,
        spaceForNotifications: Float,
        spaceForShelf: Float,
    ): Boolean {

        val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight
        var canFit: Boolean

        if (shelfHeightWithSpaceBefore == 0f) {
            canFit = notificationsHeight <= spaceForNotifications
            log {
                "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" +
                    " <= spaceForNotifications[$spaceForNotifications]"
            }
        } else {
            canFit =
                (notificationsHeight + shelfHeightWithSpaceBefore) <=
                    (spaceForNotifications + spaceForShelf)
            log {
                "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" +
                    " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
                    " <= (spaceForNotifications[$spaceForNotifications] " +
                    " + spaceForShelf[$spaceForShelf])"
            }
        }
        return canFit
    }

    /**
     * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many
     * notifications to show. This number is only valid in keyguard.
     *
     * @param totalAvailableSpace space for notifications. This includes the space for the shelf.
     */
    fun computeMaxKeyguardNotifications(
        stack: NotificationStackScrollLayout,
        totalAvailableSpace: Float,
        spaceForNotifications: Float,
        spaceForShelf: Float,
        shelfIntrinsicHeight: Float
    ): Int {
        log { "\n" }
        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)

        var maxNotifications =
            stackHeightSequence.lastIndexWhile { stackHeight -> stackHeight <= totalAvailableSpace }
            stackHeightSequence.lastIndexWhile { heightResult ->
                canStackFitInSpace(
                    heightResult,
                    spaceForNotifications = spaceForNotifications,
                    spaceForShelf = spaceForShelf)
            }

        if (onLockscreen()) {
            maxNotifications = min(maxKeyguardNotifications, maxNotifications)
@@ -90,7 +133,8 @@ constructor(
        log {
            val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else ""
            "computeMaxKeyguardNotifications(" +
                "availableSpace=$totalAvailableSpace" +
                " spaceForNotifications=$spaceForNotifications" +
                " spaceForShelf=$spaceForShelf" +
                " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence"
        }
        return maxNotifications
@@ -112,33 +156,51 @@ constructor(
        maxNotifications: Int,
        shelfIntrinsicHeight: Float
    ): Float {
        log { "\n" }
        val heightPerMaxNotifications =
            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
        val height =

        val (notificationsHeight, shelfHeightWithSpaceBefore) =
            heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
                heightPerMaxNotifications.last() // Height with all notifications visible.
            }
        log { "computeHeight(maxNotifications=$maxNotifications) -> $height" }
        return height
        log {
            "computeHeight(maxNotifications=$maxNotifications," +
                "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
                "${notificationsHeight + shelfHeightWithSpaceBefore}" +
                " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
        }
        return notificationsHeight + shelfHeightWithSpaceBefore
    }

    /** The ith result in the sequence is the height with ith max notifications. */
    private data class StackHeight(
        // Float height with ith max notifications (not including shelf)
        val notificationsHeight: Float,

        // Float height of shelf (0 if shelf is not showing), and space before the shelf that
        // changes during the lockscreen <=> full shade transition.
        val shelfHeightWithSpaceBefore: Float
    )

    private fun computeHeightPerNotificationLimit(
        stack: NotificationStackScrollLayout,
        shelfIntrinsicHeight: Float
    ): Sequence<Float> = sequence {
        shelfHeight: Float
    ): Sequence<StackHeight> = sequence {
        log { "computeHeightPerNotificationLimit" }

        val children = stack.showableChildren().toList()
        var height = 0f
        var notifications = 0f
        var previous: ExpandableView? = null
        val onLockscreen = onLockscreen()

        yield(dividerHeight + shelfIntrinsicHeight) // Only shelf.
        // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
        yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight))

        children.forEachIndexed { i, currentNotification ->
            height += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
            notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
            previous = currentNotification

            val shelfHeight =
            val shelfWithSpaceBefore =
                if (i == children.lastIndex) {
                    0f // No shelf needed.
                } else {
@@ -149,10 +211,16 @@ constructor(
                            previous = currentNotification,
                            current = children[firstViewInShelfIndex],
                            currentIndex = firstViewInShelfIndex)
                    spaceBeforeShelf + shelfIntrinsicHeight
                    spaceBeforeShelf + shelfHeight
                }

            yield(height + shelfHeight)
            log {
                "i=$i notificationsHeight=$notifications " +
                    "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
            }
            yield(
                StackHeight(
                    notificationsHeight = notifications,
                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore))
        }
    }

+59 −36
Original line number Diff line number Diff line
@@ -1520,50 +1520,77 @@ public class NotificationPanelViewController extends PanelViewController {
        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight;
    }

    /**
     * @return Space available to show notifications on lockscreen.
     */
    @VisibleForTesting
    float getSpaceForLockscreenNotifications() {
        float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
                // getMinStackScrollerPadding is from the top of the screen,
                // but we need it from the top of the NSSL.
                - mNotificationStackScrollLayoutController.getTop();

        // Space between bottom of notifications and top of lock icon or udfps background.
        float lockIconPadding = mLockIconViewController.getTop();
        if (mLockIconViewController.getTop() != 0) {
    /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */
    private float getLockIconPadding() {
        float lockIconPadding = 0f;
        if (mLockIconViewController.getTop() != 0f) {
            lockIconPadding = mNotificationStackScrollLayoutController.getBottom()
                    - mLockIconViewController.getTop()
                    - mShelfAndLockIconOverlap;
                    - mLockIconViewController.getTop();
        }
        return lockIconPadding;
    }

    /** Returns space available to show notifications on lockscreen. */
    @VisibleForTesting
    float getVerticalSpaceForLockscreenNotifications() {
        final float lockIconPadding = getLockIconPadding();

        float bottomPadding = Math.max(lockIconPadding,
                Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding));

        mKeyguardNotificationBottomPadding = bottomPadding;

        float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
                // getMinStackScrollerPadding is from the top of the screen,
                // but we need it from the top of the NSSL.
                - mNotificationStackScrollLayoutController.getTop();
        mKeyguardNotificationTopPadding = staticTopPadding;

        // To debug the available space, enable debug lines in this class. If you change how the
        // available space is calculated, please also update those lines.
        float availableSpace =
        final float verticalSpace =
                mNotificationStackScrollLayoutController.getHeight()
                        - staticTopPadding
                        - bottomPadding;

        if (SPEW_LOGCAT) {
            Log.d(TAG, "getSpaceForLockscreenNotifications()"
                    + " availableSpace=" + availableSpace
                    + " NSSL.height=" + mNotificationStackScrollLayoutController.getHeight()
                    + " NSSL.top=" + mNotificationStackScrollLayoutController.getTop()
                    + " staticTopPadding=" + staticTopPadding
                    + " bottomPadding=" + bottomPadding
                    + " lockIconPadding=" + lockIconPadding
                    + " mIndicationBottomPadding=" + mIndicationBottomPadding
                    + " mAmbientIndicationBottomPadding=" + mAmbientIndicationBottomPadding
            Log.i(TAG, "\n");
            Log.i(TAG, "staticTopPadding[" + staticTopPadding
                    + "] = Clock.padding["
                    + mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
                    + "] - NSSLC.top[" + mNotificationStackScrollLayoutController.getTop()
                    + "]"
            );
            Log.i(TAG, "bottomPadding[" + bottomPadding
                    + "] = max(ambientIndicationBottomPadding[" + mAmbientIndicationBottomPadding
                    + "], mIndicationBottomPadding[" + mIndicationBottomPadding
                    + "], lockIconPadding[" + lockIconPadding
                    + "])"
            );
            Log.i(TAG, "verticalSpaceForNotifications[" + verticalSpace
                    + "] = NSSL.height[" + mNotificationStackScrollLayoutController.getHeight()
                    + "] - staticTopPadding[" + staticTopPadding
                    + "] - bottomPadding[" + bottomPadding
                    + "]"
            );
        }
        return availableSpace;
        return verticalSpace;
    }

    /** Returns extra space available to show the shelf on lockscreen */
    @VisibleForTesting
    float getVerticalSpaceForLockscreenShelf() {
        final float lockIconPadding = getLockIconPadding();

        final float noShelfOverlapBottomPadding =
                Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);

        final float extraSpaceForShelf = lockIconPadding - noShelfOverlapBottomPadding;

        if (extraSpaceForShelf > 0f) {
            return Math.min(mNotificationShelfController.getIntrinsicHeight(),
                    extraSpaceForShelf);
        }
        return 0f;
    }

    /**
@@ -1579,16 +1606,12 @@ public class NotificationPanelViewController extends PanelViewController {
            }
            return mMaxAllowedKeyguardNotifications;
        }

        final float shelfIntrinsicHeight =
                mNotificationShelfController.getVisibility() == View.GONE
                        ? 0
                        : mNotificationShelfController.getIntrinsicHeight();

        return mNotificationStackSizeCalculator.computeMaxKeyguardNotifications(
                mNotificationStackScrollLayoutController.getView(),
                getSpaceForLockscreenNotifications(),
                shelfIntrinsicHeight);
                getVerticalSpaceForLockscreenNotifications(),
                getVerticalSpaceForLockscreenShelf(),
                mNotificationShelfController.getIntrinsicHeight()
        );
    }

    private void updateClock() {
@@ -3812,7 +3835,7 @@ public class NotificationPanelViewController extends PanelViewController {
    public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) {
        int ambientIndicationBottomPadding = 0;
        if (ambientTextVisible) {
            int stackBottom = mNotificationStackScrollLayoutController.getView().getBottom();
            int stackBottom = mNotificationStackScrollLayoutController.getBottom();
            ambientIndicationBottomPadding = stackBottom - ambientIndicationTop;
        }
        if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
+58 −30
Original line number Diff line number Diff line
@@ -46,7 +46,8 @@ import org.mockito.MockitoAnnotations
class NotificationStackSizeCalculatorTest : SysuiTestCase() {

    @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
    @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
    @Mock
    private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
    @Mock private lateinit var stackLayout: NotificationStackScrollLayout

    private val testableResources = mContext.orCreateTestableResources
@@ -74,7 +75,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
        val rows = listOf(createMockRow(height = rowHeight))

        val maxNotifications =
            computeMaxKeyguardNotifications(rows, availableSpace = 0f, shelfHeight = 0f)
            computeMaxKeyguardNotifications(
                rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f)

        assertThat(maxNotifications).isEqualTo(0)
    }
@@ -84,7 +86,12 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
        val numberOfRows = 30
        val rows = createLockscreenRows(numberOfRows)

        val maxNotifications = computeMaxKeyguardNotifications(rows, Float.MAX_VALUE)
        val maxNotifications =
            computeMaxKeyguardNotifications(
                rows,
                spaceForNotifications = Float.MAX_VALUE,
                spaceForShelf = Float.MAX_VALUE,
                shelfHeight)

        assertThat(maxNotifications).isEqualTo(numberOfRows)
    }
@@ -93,11 +100,12 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
    fun computeMaxKeyguardNotifications_spaceForOneAndShelf_returnsOne() {
        setGapHeight(gapHeight)
        val shelfHeight = rowHeight / 2 // Shelf absence won't leave room for another row.
        val availableSpace =
            listOf(rowHeight + dividerHeight, gapHeight + dividerHeight + shelfHeight).sum()
        val spaceForNotifications = rowHeight + dividerHeight
        val spaceForShelf = gapHeight + dividerHeight + shelfHeight
        val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight))

        val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
        val maxNotifications =
            computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)

        assertThat(maxNotifications).isEqualTo(1)
    }
@@ -106,16 +114,19 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
    fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
        setGapHeight(gapHeight)
        val shelfHeight = shelfHeight + dividerHeight
        val availableSpace =
        val spaceForNotifications =
            listOf(
                    rowHeight + dividerHeight,
                    gapHeight + rowHeight + dividerHeight,
                    gapHeight + dividerHeight + shelfHeight)
                )
                .sum()
        val spaceForShelf = gapHeight + dividerHeight + shelfHeight
        val rows =
            listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))

        val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
        val maxNotifications =
            computeMaxKeyguardNotifications(
                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)

        assertThat(maxNotifications).isEqualTo(2)
    }
@@ -124,19 +135,25 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
    fun computeHeight_gapBeforeShelf_returnsSpaceUsed() {
        // Each row in separate section.
        setGapHeight(gapHeight)
        val spaceUsed =
            listOf(rowHeight,

        val spaceForNotifications =
            listOf(
                    rowHeight,
                    dividerHeight + gapHeight + rowHeight,
                    dividerHeight + gapHeight + shelfHeight)
                )
                .sum()
        val availableSpace = spaceUsed + 1;

        val spaceForShelf = dividerHeight + gapHeight + shelfHeight
        val spaceUsed = spaceForNotifications + spaceForShelf
        val rows =
            listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))

        val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
        val maxNotifications =
            computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)
        assertThat(maxNotifications).isEqualTo(2)

        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
        val height =
            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
        assertThat(height).isEqualTo(spaceUsed)
    }

@@ -145,19 +162,19 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
        // Both rows are in the same section.
        setGapHeight(0f)

        val rowHeight = rowHeight
        val shelfHeight = shelfHeight
        val spaceUsed =
            listOf(rowHeight,
                    dividerHeight + shelfHeight)
                .sum()
        val availableSpace = spaceUsed + 1
        val spaceForNotifications = rowHeight
        val spaceForShelf = dividerHeight + shelfHeight
        val spaceUsed = spaceForNotifications + spaceForShelf
        val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight))

        val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
        // test that we only use space required
        val maxNotifications =
            computeMaxKeyguardNotifications(
                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
        assertThat(maxNotifications).isEqualTo(1)

        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
        val height =
            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
        assertThat(height).isEqualTo(spaceUsed)
    }

@@ -191,8 +208,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
        whenever(expandableView.getMinHeight(any())).thenReturn(5)
        whenever(expandableView.intrinsicHeight).thenReturn(10)

        val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0,
                previousView = null, stack = stackLayout, onLockscreen = true)
        val space =
            sizeCalculator.spaceNeeded(
                expandableView,
                visibleIndex = 0,
                previousView = null,
                stack = stackLayout,
                onLockscreen = true)
        assertThat(space).isEqualTo(5)
    }

@@ -205,19 +227,25 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
        whenever(expandableView.getMinHeight(any())).thenReturn(5)
        whenever(expandableView.intrinsicHeight).thenReturn(10)

        val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0,
                previousView = null, stack = stackLayout, onLockscreen = false)
        val space =
            sizeCalculator.spaceNeeded(
                expandableView,
                visibleIndex = 0,
                previousView = null,
                stack = stackLayout,
                onLockscreen = false)
        assertThat(space).isEqualTo(10)
    }

    private fun computeMaxKeyguardNotifications(
        rows: List<ExpandableView>,
        availableSpace: Float,
        spaceForNotifications: Float,
        spaceForShelf: Float,
        shelfHeight: Float = this.shelfHeight
    ): Int {
        setupChildren(rows)
        return sizeCalculator.computeMaxKeyguardNotifications(
            stackLayout, availableSpace, shelfHeight)
            stackLayout, spaceForNotifications, spaceForShelf, shelfHeight)
    }

    private fun setupChildren(children: List<ExpandableView>) {
+93 −16

File changed.

Preview size limit exceeded, changes collapsed.