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

Commit 33db676d authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB] Reset chip width calculations on rotation or unfold.

A user may not have enough room for 2 chips in portrait, but does have
room for 2 chips in landscape or when unfolded. This CL updates the
secondary chip to re-calculate if it has enough space whenever the screen
bounds change, which happens on rotation or unfold.

It also updates the chip text to re-calculate if it has enough space.

Fixes: 385740234
Fixes: 385740683
Fixes: 351069457

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

Test: Trigger 2 chips where only 1 chip fits in portrait, then rotate to
landscape -> verify 2nd chip shows. Rotate back to portrait -> verify
2nd chip hides again
Test: Show timer chip on large font size then decrease font size or
rotate to landscape -> verify timer shows again
Test: With small font size, trigger 2 chips and see 2 chips. Increase
font size -> verify 2nd chip shrinks and disappears at largest font
size. Decrease font size -> verify 2nd chip re-shows and shows with text
if there's enough room

Change-Id: I21f6ed401c8f882f96611f164bf55500699c8be6
parent 80dbd94b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@ class FakeHomeStatusBarViewModel(

    override val iconBlockList: MutableStateFlow<List<String>> = MutableStateFlow(listOf())

    override val contentArea = MutableStateFlow(Rect(0, 0, 1, 1))

    val darkRegions = mutableListOf<Rect>()

    var darkIconTint = Color.BLACK
+7 −0
Original line number Diff line number Diff line
@@ -26,10 +26,13 @@ import android.content.testableContext
import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.fake
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -85,6 +88,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before
import org.junit.Test
@@ -104,6 +108,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
        setUpPackageManagerForMediaProjection(kosmos)
    }

    @Before
    fun addDisplays() = runBlocking { kosmos.displayRepository.fake.addDisplay(DEFAULT_DISPLAY) }

    @Test
    fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
        kosmos.runTest {
+93 −13
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.widget.DateTimeView
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.UiThread
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -38,24 +39,24 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.Notificati
/** Binder for ongoing activity chip views. */
object OngoingActivityChipBinder {
    /** Binds the given [chipModel] data to the given [chipView]. */
    fun bind(chipModel: OngoingActivityChipModel, chipView: View, iconViewStore: IconViewStore?) {
        val chipContext = chipView.context
        val chipDefaultIconView: ImageView =
            chipView.requireViewById(R.id.ongoing_activity_chip_icon)
        val chipTimeView: ChipChronometer =
            chipView.requireViewById(R.id.ongoing_activity_chip_time)
        val chipTextView: TextView = chipView.requireViewById(R.id.ongoing_activity_chip_text)
        val chipShortTimeDeltaView: DateTimeView =
            chipView.requireViewById(R.id.ongoing_activity_chip_short_time_delta)
        val chipBackgroundView: ChipBackgroundContainer =
            chipView.requireViewById(R.id.ongoing_activity_chip_background)
    fun bind(
        chipModel: OngoingActivityChipModel,
        viewBinding: OngoingActivityChipViewBinding,
        iconViewStore: IconViewStore?,
    ) {
        val chipContext = viewBinding.rootView.context
        val chipDefaultIconView = viewBinding.defaultIconView
        val chipTimeView = viewBinding.timeView
        val chipTextView = viewBinding.textView
        val chipShortTimeDeltaView = viewBinding.shortTimeDeltaView
        val chipBackgroundView = viewBinding.backgroundView

        when (chipModel) {
            is OngoingActivityChipModel.Shown -> {
                // Data
                setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
                setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
                chipView.setOnClickListener(chipModel.onClickListener)
                viewBinding.rootView.setOnClickListener(chipModel.onClickListener)
                updateChipPadding(
                    chipModel,
                    chipBackgroundView,
@@ -65,7 +66,7 @@ object OngoingActivityChipBinder {
                )

                // Accessibility
                setChipAccessibility(chipModel, chipView, chipBackgroundView)
                setChipAccessibility(chipModel, viewBinding.rootView, chipBackgroundView)

                // Colors
                val textColor = chipModel.colors.text(chipContext)
@@ -83,6 +84,85 @@ object OngoingActivityChipBinder {
        }
    }

    /** Stores [rootView] and relevant child views in an object for easy reference. */
    fun createBinding(rootView: View): OngoingActivityChipViewBinding {
        return OngoingActivityChipViewBinding(
            rootView = rootView,
            timeView = rootView.requireViewById(R.id.ongoing_activity_chip_time),
            textView = rootView.requireViewById(R.id.ongoing_activity_chip_text),
            shortTimeDeltaView =
                rootView.requireViewById(R.id.ongoing_activity_chip_short_time_delta),
            defaultIconView = rootView.requireViewById(R.id.ongoing_activity_chip_icon),
            backgroundView = rootView.requireViewById(R.id.ongoing_activity_chip_background),
        )
    }

    /**
     * Resets any width restrictions that were placed on the primary chip's contents.
     *
     * Should be used when the user's screen bounds changed because there may now be more room in
     * the status bar to show additional content.
     */
    fun resetPrimaryChipWidthRestrictions(
        primaryChipViewBinding: OngoingActivityChipViewBinding,
        currentPrimaryChipViewModel: OngoingActivityChipModel,
    ) {
        if (currentPrimaryChipViewModel is OngoingActivityChipModel.Hidden) {
            return
        }
        resetChipMainContentWidthRestrictions(
            primaryChipViewBinding,
            currentPrimaryChipViewModel as OngoingActivityChipModel.Shown,
        )
    }

    /**
     * Resets any width restrictions that were placed on the secondary chip and its contents.
     *
     * Should be used when the user's screen bounds changed because there may now be more room in
     * the status bar to show additional content.
     */
    fun resetSecondaryChipWidthRestrictions(
        secondaryChipViewBinding: OngoingActivityChipViewBinding,
        currentSecondaryChipModel: OngoingActivityChipModel,
    ) {
        if (currentSecondaryChipModel is OngoingActivityChipModel.Hidden) {
            return
        }
        secondaryChipViewBinding.rootView.resetWidthRestriction()
        resetChipMainContentWidthRestrictions(
            secondaryChipViewBinding,
            currentSecondaryChipModel as OngoingActivityChipModel.Shown,
        )
    }

    private fun resetChipMainContentWidthRestrictions(
        viewBinding: OngoingActivityChipViewBinding,
        model: OngoingActivityChipModel.Shown,
    ) {
        when (model) {
            is OngoingActivityChipModel.Shown.Text -> viewBinding.textView.resetWidthRestriction()
            is OngoingActivityChipModel.Shown.Timer -> viewBinding.timeView.resetWidthRestriction()
            is OngoingActivityChipModel.Shown.ShortTimeDelta ->
                viewBinding.shortTimeDeltaView.resetWidthRestriction()
            is OngoingActivityChipModel.Shown.IconOnly,
            is OngoingActivityChipModel.Shown.Countdown -> {}
        }
    }

    /**
     * Resets any width restrictions that were placed on the given view.
     *
     * Should be used when the user's screen bounds changed because there may now be more room in
     * the status bar to show additional content.
     */
    @UiThread
    fun View.resetWidthRestriction() {
        // View needs to be visible in order to be re-measured
        visibility = View.VISIBLE
        forceLayout()
    }

    private fun setChipIcon(
        chipModel: OngoingActivityChipModel.Shown,
        backgroundView: ChipBackgroundContainer,
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.binder

import android.view.View
import android.widget.ImageView
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
import com.android.systemui.statusbar.chips.ui.view.ChipTextView

/** Stores bound views for a given chip. */
data class OngoingActivityChipViewBinding(
    val rootView: View,
    val timeView: ChipChronometer,
    val textView: ChipTextView,
    val shortTimeDeltaView: ChipDateTimeView,
    val defaultIconView: ImageView,
    val backgroundView: ChipBackgroundContainer,
)
+16 −7
Original line number Diff line number Diff line
@@ -33,10 +33,8 @@ import androidx.annotation.UiThread
 *    that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
 *    1:00:00), but never smaller.
 * 2) Hiding the text if the time gets too long for the space available. Once the text has been
 *    hidden, it remains hidden for the duration of the activity.
 *
 * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
 * text will also be hidden in landscape (even if there is enough space for it in landscape).
 *    hidden, it remains hidden for the duration of the activity (or until [resetWidthRestriction]
 *    is called).
 */
class ChipChronometer
@JvmOverloads
@@ -51,12 +49,23 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    private var shouldHideText: Boolean = false

    override fun setBase(base: Long) {
        // These variables may have changed during the previous activity, so re-set them before the
        // new activity starts.
        resetWidthRestriction()
        super.setBase(base)
    }

    /**
     * Resets any width restrictions that were placed on the chronometer.
     *
     * Should be used when the user's screen bounds changed because there may now be more room in
     * the status bar to show additional content.
     */
    @UiThread
    fun resetWidthRestriction() {
        minimumTextWidth = 0
        shouldHideText = false
        // View needs to be visible in order to be re-measured
        visibility = VISIBLE
        super.setBase(base)
        forceLayout()
    }

    /** Sets whether this view should hide its text or not. */
Loading