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

Commit 49f92f50 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Notif] Don't show text in status bar chip if < 50% of it fits.

See b/364653005#comment51 for screenshots

Bug: 364653005
Flag: com.android.systemui.status_bar_notification_chips

Test: Trigger chip with incredibly long shortCriticalText -> verify no
text shows
Test: Trigger chip with a bit of overflow shortCriticalText -> verify
text shows and fades out
Test: Trigger chip with very short shortCriticalText -> verify text
shows and chip width < max width
Test: atest ChipTextTruncationHelperTest

Change-Id: I2c7db5d7c49bea770ccff3fb8d9f4c2124b5e04b
parent ab3e9c5e
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ChipTextTruncationHelperTest : SysuiTestCase() {

    val underTest by lazy { ChipTextTruncationHelper(TextView(context)) }

    @Before
    fun setUp() {
        mContext.getOrCreateTestableResources().apply {
            this.addOverride(R.dimen.ongoing_activity_chip_max_text_width, MAX_WIDTH)
        }
    }

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

        assertThat(result).isTrue()
    }

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

        assertThat(result).isTrue()
    }

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

        assertThat(result).isFalse()
    }

    companion object {
        private const val MAX_WIDTH = 200
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -58,14 +58,14 @@
        />

        <!-- Shows generic text. -->
        <TextView
        <com.android.systemui.statusbar.chips.ui.view.ChipTextView
            android:id="@+id/ongoing_activity_chip_text"
            style="@style/StatusBar.Chip.Text.LimitedWidth"
            android:visibility="gone"
            />

        <!-- Shows a time delta in short form, like "15min" or "1hr". -->
        <android.widget.DateTimeView
        <com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
            android:id="@+id/ongoing_activity_chip_short_time_delta"
            style="@style/StatusBar.Chip.Text.LimitedWidth"
            android:visibility="gone"
+0 −1
Original line number Diff line number Diff line
@@ -91,7 +91,6 @@
        <item name="android:ellipsize">none</item>
        <item name="android:requiresFadingEdge">horizontal</item>
        <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item>
        <item name="android:maxWidth">@dimen/ongoing_activity_chip_max_text_width</item>
    </style>

    <style name="Chipbar" />
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.widget.DateTimeView

/** A [DateTimeView] for chips in the status bar. See also: [ChipTextView]. */
class ChipDateTimeView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
    DateTimeView(context, attrs) {
    private val textTruncationHelper = ChipTextTruncationHelper(this)

    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        textTruncationHelper.onConfigurationChanged()
    }

    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)

        if (textTruncationHelper.shouldShowText(desiredTextWidthPx = measuredWidth)) {
            // Show the text with the maximum width specified by the helper
            super.onMeasure(textTruncationHelper.maximumWidthMeasureSpec, heightMeasureSpec)
        } else {
            // Changing visibility ensures that the content description is not read aloud when the
            // text isn't displayed.
            visibility = GONE
            setMeasuredDimension(0, 0)
        }
    }
}
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

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) {
    private var maxWidth: Int = 0
        set(value) {
            field = value
            maximumWidthMeasureSpec = 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

    init {
        maxWidth = fetchMaxWidth()
    }

    fun onConfigurationChanged() {
        maxWidth = fetchMaxWidth()
    }

    /**
     * Returns true if this view should show the text because there's enough room for a substantial
     * amount of text, and returns false if this view should hide the text because the text is much
     * too long.
     *
     * @param desiredTextWidthPx should be calculated by having the view measure itself with
     *   [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.)
     */
    fun shouldShowText(desiredTextWidthPx: Int): Boolean {
        // Evaluate how wide the text *can* be based on the enforced constraints
        val enforcedTextWidth = resolveSize(desiredTextWidthPx, maximumWidthMeasureSpec)
        // 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
    }

    private fun fetchMaxWidth() =
        view.context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_max_text_width)
}
Loading