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

Commit a3f96c47 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Notif] Also use chip's normal width constraints for text truncation

Commit I2c7db5d7c49bea770ccff3fb8d9f4c2124b5e04b added logic to see how
much of the status bar chip's text could fit into 96dp, and hid the text
if too much was hidden.

That works for the first chip, which will always have at least 96dp
available. But that doesn't work for the second chip, which could have <
96dp if the first chip is wide.

This CL updates the text truncation helper to also take into account the
chip's normal width constraints when deciding how to render the text.

Before/after shown in b/364653005#comment52.

Bug: 364653005
Flag: com.android.systemui.status_bar_notification_chips
Test: manual: trigger two chips -> verify second chip text gets cropped
correctly
Test: atest ChipTextTruncationHelperTest

Change-Id: I4073682bf9527783252af241d8d76686b3cc3c64
parent 49f92f50
Loading
Loading
Loading
Loading
+58 −3
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.chips.ui.view

import android.view.View
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -41,26 +42,80 @@ class ChipTextTruncationHelperTest : SysuiTestCase() {

    @Test
    fun shouldShowText_desiredLessThanMax_true() {
        val result = underTest.shouldShowText(desiredTextWidthPx = MAX_WIDTH / 2)
        val result =
            underTest.shouldShowText(
                desiredTextWidthPx = MAX_WIDTH / 2,
                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
            )

        assertThat(result).isTrue()
    }

    @Test
    fun shouldShowText_desiredSlightlyLargerThanMax_true() {
        val result = underTest.shouldShowText(desiredTextWidthPx = (MAX_WIDTH * 1.1).toInt())
        val result =
            underTest.shouldShowText(
                desiredTextWidthPx = (MAX_WIDTH * 1.1).toInt(),
                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
            )

        assertThat(result).isTrue()
    }

    @Test
    fun shouldShowText_desiredMoreThanTwiceMax_false() {
        val result = underTest.shouldShowText(desiredTextWidthPx = (MAX_WIDTH * 2.2).toInt())
        val result =
            underTest.shouldShowText(
                desiredTextWidthPx = (MAX_WIDTH * 2.2).toInt(),
                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
            )

        assertThat(result).isFalse()
    }

    @Test
    fun shouldShowText_widthSpecLessThanMax_usesWidthSpec() {
        val smallerWidthSpec =
            SysuiMeasureSpec(
                View.MeasureSpec.makeMeasureSpec(MAX_WIDTH / 2, View.MeasureSpec.AT_MOST)
            )

        // WHEN desired is more than twice the smallerWidthSpec
        val desiredWidth = (MAX_WIDTH * 1.1).toInt()

        val result =
            underTest.shouldShowText(
                desiredTextWidthPx = desiredWidth,
                widthMeasureSpec = smallerWidthSpec,
            )

        // THEN returns false because smallerWidthSpec is used as the requirement
        assertThat(result).isFalse()
    }

    @Test
    fun shouldShowText_maxLessThanWidthSpec_usesMax() {
        val largerWidthSpec =
            SysuiMeasureSpec(
                View.MeasureSpec.makeMeasureSpec(MAX_WIDTH * 3, View.MeasureSpec.AT_MOST)
            )

        // WHEN desired is more than twice the max
        val desiredWidth = (MAX_WIDTH * 2.2).toInt()

        val result =
            underTest.shouldShowText(
                desiredTextWidthPx = desiredWidth,
                widthMeasureSpec = largerWidthSpec,
            )

        // THEN returns false because the max is used as the requirement
        assertThat(result).isFalse()
    }

    companion object {
        private const val MAX_WIDTH = 200
        private val UNLIMITED_WIDTH_SPEC =
            SysuiMeasureSpec(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
    }
}
+9 −4
Original line number Diff line number Diff line
@@ -34,11 +34,16 @@ class ChipDateTimeView @JvmOverloads constructor(context: Context, attrs: Attrib
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // Evaluate how wide the text *wants* to be if it had unlimited space. This is needed so
        // that [textTruncationHelper.shouldShowText] works correctly.
        super.onMeasure(textTruncationHelper.unlimitedWidthMeasureSpec, heightMeasureSpec)
        super.onMeasure(textTruncationHelper.unlimitedWidthMeasureSpec.specInt, heightMeasureSpec)

        if (textTruncationHelper.shouldShowText(desiredTextWidthPx = measuredWidth)) {
            // Show the text with the maximum width specified by the helper
            super.onMeasure(textTruncationHelper.maximumWidthMeasureSpec, heightMeasureSpec)
        if (
            textTruncationHelper.shouldShowText(
                desiredTextWidthPx = measuredWidth,
                widthMeasureSpec = SysuiMeasureSpec(widthMeasureSpec),
            )
        ) {
            // Show the text with the width spec specified by the helper
            super.onMeasure(textTruncationHelper.widthMeasureSpec.specInt, heightMeasureSpec)
        } else {
            // Changing visibility ensures that the content description is not read aloud when the
            // text isn't displayed.
+35 −10
Original line number Diff line number Diff line
@@ -20,24 +20,27 @@ import android.view.View
import android.view.View.MeasureSpec
import android.widget.TextView.resolveSize
import com.android.systemui.res.R
import kotlin.properties.Delegates

/**
 * Helper class to determine when a status bar chip's text should be hidden because it's too long.
 */
class ChipTextTruncationHelper(private val view: View) {
    /** A measure spec for the status bar chip text with an unlimited width. */
    val unlimitedWidthMeasureSpec =
        SysuiMeasureSpec(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))

    /** The [MeasureSpec] that the view should actually use win [onMeasure]. */
    lateinit var widthMeasureSpec: SysuiMeasureSpec

    private var maxWidth: Int = 0
        set(value) {
            field = value
            maximumWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST)
            maximumWidthMeasureSpec =
                SysuiMeasureSpec(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST))
        }

    /** A measure spec for the status bar chip text with an unlimited width. */
    val unlimitedWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)

    /** A measure spec for the status bar chip text with the correct maximum width. */
    var maximumWidthMeasureSpec by Delegates.notNull<Int>()
        private set
    private lateinit var maximumWidthMeasureSpec: SysuiMeasureSpec

    init {
        maxWidth = fetchMaxWidth()
@@ -56,10 +59,29 @@ class ChipTextTruncationHelper(private val view: View) {
     *   [unlimitedWidthMeasureSpec] and then sending its `measuredWidth` to this method. (This
     *   class can't compute [desiredTextWidthPx] directly because [View.onMeasure] can only be
     *   called by the view itself.)
     * @param widthMeasureSpec the view's current and unmodified width spec
     */
    fun shouldShowText(desiredTextWidthPx: Int): Boolean {
        // Evaluate how wide the text *can* be based on the enforced constraints
        val enforcedTextWidth = resolveSize(desiredTextWidthPx, maximumWidthMeasureSpec)
    fun shouldShowText(desiredTextWidthPx: Int, widthMeasureSpec: SysuiMeasureSpec): Boolean {
        // Evaluate how wide the text *can* be based on:
        // #1: The maximum width encoded by [maxWidth]
        val maxWidthBasedOnDimension =
            resolveSize(desiredTextWidthPx, maximumWidthMeasureSpec.specInt)
        // #2: The width the view is allowed to take up (If there's 2 chips, the second chip likely
        // has < [maxWidth] room available)
        val maxWidthBasedOnViewSpaceAvailable =
            resolveSize(desiredTextWidthPx, widthMeasureSpec.specInt)

        val enforcedTextWidth: Int
        if (maxWidthBasedOnViewSpaceAvailable < maxWidthBasedOnDimension) {
            // View space available takes priority
            this.widthMeasureSpec = widthMeasureSpec
            enforcedTextWidth = maxWidthBasedOnViewSpaceAvailable
        } else {
            // Enforce the maximum width
            this.widthMeasureSpec = maximumWidthMeasureSpec
            enforcedTextWidth = maxWidthBasedOnDimension
        }

        // Only show the text if at least 50% of it can show. (Assume that if < 50% of the text will
        // be visible, the text will be more confusing than helpful.)
        return desiredTextWidthPx <= enforcedTextWidth * 2
@@ -68,3 +90,6 @@ class ChipTextTruncationHelper(private val view: View) {
    private fun fetchMaxWidth() =
        view.context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_max_text_width)
}

/** A typed class for [MeasureSpec] ints. */
data class SysuiMeasureSpec(val specInt: Int)
+9 −4
Original line number Diff line number Diff line
@@ -36,11 +36,16 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // Evaluate how wide the text *wants* to be if it had unlimited space. This is needed so
        // that [textTruncationHelper.shouldShowText] works correctly.
        super.onMeasure(textTruncationHelper.unlimitedWidthMeasureSpec, heightMeasureSpec)
        super.onMeasure(textTruncationHelper.unlimitedWidthMeasureSpec.specInt, heightMeasureSpec)

        if (textTruncationHelper.shouldShowText(desiredTextWidthPx = measuredWidth)) {
            // Show the text with the maximum width specified by the helper
            super.onMeasure(textTruncationHelper.maximumWidthMeasureSpec, heightMeasureSpec)
        if (
            textTruncationHelper.shouldShowText(
                desiredTextWidthPx = measuredWidth,
                widthMeasureSpec = SysuiMeasureSpec(widthMeasureSpec),
            )
        ) {
            // Show the text with the width spec specified by the helper
            super.onMeasure(textTruncationHelper.widthMeasureSpec.specInt, heightMeasureSpec)
        } else {
            // Changing visibility ensures that the content description is not read aloud when the
            // text isn't displayed.