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

Commit 9ba88952 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge "[SB][Chips] Allow timer chips to count down to a time in the future." into main

parents 5e8ce62a d88a2b33
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -573,6 +573,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
            assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
            assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs)
                .isEqualTo(whenElapsed)
            assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture)
                .isFalse()
        }

    @Test
@@ -608,6 +610,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
            assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
            assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs)
                .isEqualTo(whenElapsed)
            assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture)
                .isTrue()
        }

    @Test
+172 −21
Original line number Diff line number Diff line
@@ -35,55 +35,153 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ChronometerStateTest : SysuiTestCase() {

    private lateinit var mockTimeSource: MutableTimeSource
    private lateinit var fakeTimeSource: MutableTimeSource

    @Before
    fun setup() {
        mockTimeSource = MutableTimeSource()
        fakeTimeSource = MutableTimeSource()
    }

    @Test
    fun initialText_isCorrect() = runTest {
        val state = ChronometerState(mockTimeSource, 0L)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(0))
    fun initialText_isEventInFutureFalse_timeIsNow() = runTest {
        fakeTimeSource.time = 3_000
        val state =
            ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = false)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0))
    }

    @Test
    fun textUpdates_withTime() = runTest {
        val startTime = 1000L
        val state = ChronometerState(mockTimeSource, startTime)
    fun initialText_isEventInFutureFalse_timeInPast() = runTest {
        fakeTimeSource.time = 3_000
        val state =
            ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = false)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2))
    }

    @Test
    fun initialText_isEventInFutureFalse_timeInFuture() = runTest {
        fakeTimeSource.time = 3_000
        val state =
            ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = false)
        // When isEventInFuture=false, eventTimeMillis needs to be in the past if we want text to
        // show
        assertThat(state.currentTimeText).isNull()
    }

    @Test
    fun initialText_isEventInFutureTrue_timeIsNow() = runTest {
        fakeTimeSource.time = 3_000
        val state =
            ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = true)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0))
    }

    @Test
    fun initialText_isEventInFutureTrue_timeInFuture() = runTest {
        fakeTimeSource.time = 3_000
        val state =
            ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = true)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2))
    }

    @Test
    fun initialText_isEventInFutureTrue_timeInPast() = runTest {
        fakeTimeSource.time = 3_000
        val state =
            ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = true)
        // When isEventInFuture=true, eventTimeMillis needs to be in the future if we want text to
        // show
        assertThat(state.currentTimeText).isNull()
    }

    @Test
    fun textUpdates_isEventInFutureFalse_timeInPast() = runTest {
        val eventTime = 1000L
        val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
        val job = launch { state.run() }

        val elapsedTime = 5000L
        mockTimeSource.time = startTime + elapsedTime
        fakeTimeSource.time = eventTime + elapsedTime
        advanceTimeBy(elapsedTime)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000))

        val additionalTime = 6000L
        fakeTimeSource.time += additionalTime
        advanceTimeBy(additionalTime)
        assertThat(state.currentTimeText)
            .isEqualTo(formatElapsedTime((elapsedTime + additionalTime) / 1000))

        job.cancelAndJoin()
    }

    @Test
    fun textUpdates_toLargerValue() = runTest {
        val startTime = 1000L
        val state = ChronometerState(mockTimeSource, startTime)
    fun textUpdates_isEventInFutureFalse_timeChangesFromFutureToPast() = runTest {
        val eventTime = 15_000L
        val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
        val job = launch { state.run() }

        val elapsedTime = 15000L
        mockTimeSource.time = startTime + elapsedTime
        advanceTimeBy(elapsedTime)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000))
        // WHEN the time is 5 but the eventTime is 15
        fakeTimeSource.time = 5_000L
        advanceTimeBy(5_000L)
        // THEN no text is shown
        assertThat(state.currentTimeText).isNull()

        // WHEN the time advances to 40
        fakeTimeSource.time = 40_000L
        advanceTimeBy(35_000)
        // THEN text is shown as 25 seconds (40 - 15)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25))

        job.cancelAndJoin()
    }

    @Test
    fun textUpdates_afterResettingBase() = runTest {
    fun textUpdates_isEventInFutureTrue_timeInFuture() = runTest {
        val eventTime = 15_000L
        val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
        val job = launch { state.run() }

        fakeTimeSource.time = 5_000L
        advanceTimeBy(5_000L)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))

        val additionalTime = 6000L
        fakeTimeSource.time += additionalTime
        advanceTimeBy(additionalTime)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 4))

        job.cancelAndJoin()
    }

    @Test
    fun textUpdates_isEventInFutureTrue_timeChangesFromFutureToPast() = runTest {
        val eventTime = 15_000L
        val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
        val job = launch { state.run() }

        // WHEN the time is 5 and the eventTime is 15
        fakeTimeSource.time = 5_000L
        advanceTimeBy(5_000L)
        // THEN 10 seconds is shown
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))

        // WHEN the time advances to 40 (past the event time)
        fakeTimeSource.time = 40_000L
        advanceTimeBy(35_000)
        // THEN no text is shown
        assertThat(state.currentTimeText).isNull()

        job.cancelAndJoin()
    }

    @Test
    fun textUpdates_afterResettingBase_isEventInFutureFalse() = runTest {
        val initialElapsedTime = 30000L
        val startTime = 50000L
        val state = ChronometerState(mockTimeSource, startTime)
        val state = ChronometerState(fakeTimeSource, startTime, isEventInFuture = false)
        val job = launch { state.run() }

        mockTimeSource.time = startTime + initialElapsedTime
        fakeTimeSource.time = startTime + initialElapsedTime
        advanceTimeBy(initialElapsedTime)
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(initialElapsedTime / 1000))

@@ -91,15 +189,68 @@ class ChronometerStateTest : SysuiTestCase() {

        val newElapsedTime = 5000L
        val newStartTime = 100000L
        val newState = ChronometerState(mockTimeSource, newStartTime)
        val newState = ChronometerState(fakeTimeSource, newStartTime, isEventInFuture = false)
        val newJob = launch { newState.run() }

        mockTimeSource.time = newStartTime + newElapsedTime
        fakeTimeSource.time = newStartTime + newElapsedTime
        advanceTimeBy(newElapsedTime)
        assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(newElapsedTime / 1000))

        newJob.cancelAndJoin()
    }

    @Test
    fun textUpdates_afterResettingBase_isEventInFutureTrue() = runTest {
        val initialElapsedTime = 40_000L
        val eventTime = 50_000L
        val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
        val job = launch { state.run() }

        fakeTimeSource.time = initialElapsedTime
        advanceTimeBy(initialElapsedTime)
        // Time should be 50 - 40 = 10
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))

        job.cancelAndJoin()

        val newElapsedTime = 75_000L
        val newEventTime = 100_000L
        val newState = ChronometerState(fakeTimeSource, newEventTime, isEventInFuture = true)
        val newJob = launch { newState.run() }

        fakeTimeSource.time = newElapsedTime
        advanceTimeBy(newElapsedTime - initialElapsedTime)
        // Time should be 100 - 75 = 25
        assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25))

        newJob.cancelAndJoin()
    }

    @Test
    fun textUpdates_afterResettingisEventInFuture() = runTest {
        val initialElapsedTime = 40_000L
        val eventTime = 50_000L
        val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
        val job = launch { state.run() }

        fakeTimeSource.time = initialElapsedTime
        advanceTimeBy(initialElapsedTime)
        // Time should be 50 - 40 = 10
        assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))

        job.cancelAndJoin()

        val newElapsedTime = 70_000L
        val newState = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
        val newJob = launch { newState.run() }

        fakeTimeSource.time = newElapsedTime
        advanceTimeBy(newElapsedTime - initialElapsedTime)
        // Time should be 70 - 50 = 20
        assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 20))

        newJob.cancelAndJoin()
    }
}

/** A fake implementation of [TimeSource] that allows the caller to set the current time */
+2 −2
Original line number Diff line number Diff line
@@ -141,7 +141,7 @@ constructor(
            // When we're promoting notifications automatically, the `when` time set on the
            // notification will likely just be set to the current time, which would cause the chip
            // to always show "now". We don't want early testers to get that experience since it's
            // not what will happen at launch, so just don't show any time.
            // not what will happen at launch, so just don't show any time.onometerstate
            return OngoingActivityChipModel.Active.IconOnly(
                this.key,
                icon,
@@ -194,12 +194,12 @@ constructor(
                }
            }
            is PromotedNotificationContentModel.When.Chronometer -> {
                // TODO(b/364653005): Check isCountDown and support CountDown.
                return OngoingActivityChipModel.Active.Timer(
                    this.key,
                    icon,
                    colors,
                    startTimeMs = this.promotedContent.time.elapsedRealtimeMillis,
                    isEventInFuture = this.promotedContent.time.isCountDown,
                    onClickListenerLegacy,
                    clickBehavior,
                )
+4 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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

import android.annotation.ElapsedRealtimeLong
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer

object ChipChronometerBinder {
@@ -25,9 +26,11 @@ object ChipChronometerBinder {
     * @param startTimeMs the time this event started, relative to
     *   [com.android.systemui.util.time.SystemClock.elapsedRealtime]. See
     *   [android.widget.Chronometer.setBase].
     * @param isCountDown see [android.widget.Chronometer.setCountDown].
     */
    fun bind(startTimeMs: Long, view: ChipChronometer) {
    fun bind(@ElapsedRealtimeLong startTimeMs: Long, isCountDown: Boolean, view: ChipChronometer) {
        view.base = startTimeMs
        view.isCountDown = isCountDown
        view.start()
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -315,7 +315,11 @@ object OngoingActivityChipBinder {
                chipShortTimeDeltaView.visibility = View.GONE
            }
            is OngoingActivityChipModel.Active.Timer -> {
                ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
                ChipChronometerBinder.bind(
                    chipModel.startTimeMs,
                    chipModel.isEventInFuture,
                    chipTimeView,
                )
                chipTimeView.visibility = View.VISIBLE

                chipTextView.visibility = View.GONE
Loading