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

Commit 986c5797 authored by Andre Le's avatar Andre Le Committed by Android (Google) Code Review
Browse files

Merge "ComposeClock: Add basic clock format for the composable clock" into main

parents ec65f9ac 518fb2a8
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.advanceTimeBy
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.collectValues
import com.android.systemui.kosmos.runCurrent
@@ -33,7 +32,7 @@ import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback
import com.android.systemui.statusbar.policy.nextAlarmController
import com.android.systemui.testKosmos
import com.android.systemui.util.time.systemClock
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Date
import kotlin.time.Duration
@@ -110,7 +109,7 @@ class ClockInteractorTest : SysuiTestCase() {
    fun currentTime_initialTime() =
        kosmos.runTest {
            assertThat(underTest.currentTime.value)
                .isEqualTo(Date(kosmos.systemClock.currentTimeMillis()))
                .isEqualTo(Date(fakeSystemClock.currentTimeMillis()))
        }

    @Test
@@ -121,7 +120,7 @@ class ClockInteractorTest : SysuiTestCase() {
            sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED)
            val earlierTime = checkNotNull(currentTime)

            advanceTimeBy(3.seconds)
            fakeSystemClock.advanceTime(3.seconds.inWholeMilliseconds)
            runCurrent()

            sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED)
@@ -138,7 +137,7 @@ class ClockInteractorTest : SysuiTestCase() {
            sendIntentActionBroadcast(Intent.ACTION_TIME_TICK)
            val earlierTime = checkNotNull(currentTime)

            advanceTimeBy(7.seconds)
            fakeSystemClock.advanceTime(7.seconds.inWholeMilliseconds)
            runCurrent()

            sendIntentActionBroadcast(Intent.ACTION_TIME_TICK)
+88 −21
Original line number Diff line number Diff line
@@ -21,21 +21,24 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.advanceTimeBy
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
import com.android.systemui.util.time.systemClock
import com.android.systemui.util.time.dateFormatUtil
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Date
import kotlin.time.Duration.Companion.seconds
import java.time.ZoneId
import java.util.Locale
import java.util.TimeZone
import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.whenever

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -44,51 +47,115 @@ class ClockViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest by lazy { kosmos.clockViewModel }

    @Before
    fun setUp() {
        underTest.activateIn(kosmos.testScope)
    private val defaultLocale: Locale = Locale.getDefault()
    private val defaultTimeZone: TimeZone = TimeZone.getDefault()

    @After
    fun tearDown() {
        Locale.setDefault(defaultLocale)
        TimeZone.setDefault(defaultTimeZone)
    }

    @Test
    fun clockText_equalsCurrentTime() =
    fun is24HourFormatTrue_clockText_equalsCurrentTime() =
        kosmos.runTest {
            assertThat(underTest.clockText)
                .isEqualTo(Date(systemClock.currentTimeMillis()).toString())
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(true)
            underTest.activateIn(testScope)

            assertThat(underTest.clockText).isEqualTo("23:12")
        }

    @Test
    fun is24HourFormatFalse_clockText_equalsCurrentTime() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(false)
            underTest.activateIn(testScope)

            assertThat(underTest.clockText).isEqualTo("11:12\u202FPM")
        }

    @Test
    fun clockText_updatesWhenTimeTick() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(true)
            underTest.activateIn(testScope)
            val earlierTime = underTest.clockText
            assertThat(earlierTime).isEqualTo(Date(systemClock.currentTimeMillis()).toString())

            advanceTimeBy(7.seconds)
            assertThat(earlierTime).isEqualTo("23:12")

            fakeSystemClock.advanceTime(2.minutes.inWholeMilliseconds)
            broadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(Intent.ACTION_TIME_TICK),
            )
            runCurrent()

            assertThat(underTest.clockText)
                .isEqualTo(Date(systemClock.currentTimeMillis()).toString())
            assertThat(underTest.clockText).isNotEqualTo(earlierTime)
            assertThat(underTest.clockText).isEqualTo("23:14")
        }

    @Test
    fun clockText_updatesWhenTimeChanged() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(true)
            underTest.activateIn(testScope)
            val earlierTime = underTest.clockText
            assertThat(earlierTime).isEqualTo(Date(systemClock.currentTimeMillis()).toString())

            advanceTimeBy(10.seconds)
            assertThat(earlierTime).isEqualTo("23:12")

            fakeSystemClock.advanceTime(2.minutes.inWholeMilliseconds)
            broadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(Intent.ACTION_TIME_CHANGED),
            )
            runCurrent()

            assertThat(underTest.clockText)
                .isEqualTo(Date(systemClock.currentTimeMillis()).toString())
            assertThat(underTest.clockText).isNotEqualTo(earlierTime)
            assertThat(underTest.clockText).isEqualTo("23:14")
        }

    @Test
    fun clockText_updatesWhenTimezoneChanged() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(true)
            underTest.activateIn(testScope)
            val earlierTime = underTest.clockText

            assertThat(earlierTime).isEqualTo("23:12")

            TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("Asia/Tokyo")))
            broadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(Intent.ACTION_TIMEZONE_CHANGED),
            )
            runCurrent()

            assertThat(underTest.clockText).isEqualTo("08:12")
        }

    @Test
    fun clockText_updatesWhenLocaleChanged() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            underTest.activateIn(testScope)
            val earlierTime = underTest.clockText

            assertThat(earlierTime).isEqualTo("11:12\u202FPM")

            Locale.setDefault(Locale.TRADITIONAL_CHINESE)
            broadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(Intent.ACTION_LOCALE_CHANGED),
            )
            runCurrent()

            assertThat(underTest.clockText).isEqualTo("下午11:12")
        }

    companion object {
        private const val CURRENT_TIME_MILLIS = 16641673939408L
    }
}
+32 −2
Original line number Diff line number Diff line
@@ -16,29 +16,49 @@

package com.android.systemui.clock.ui.viewmodel

import android.icu.text.DateTimePatternGenerator
import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.clock.domain.interactor.ClockInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.util.time.DateFormatUtil
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.text.SimpleDateFormat
import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapLatest

/** Models UI state for the clock. */
@OptIn(ExperimentalCoroutinesApi::class)
class ClockViewModel @AssistedInject constructor(clockInteractor: ClockInteractor) :
class ClockViewModel
@AssistedInject
constructor(clockInteractor: ClockInteractor, private val dateFormatUtil: DateFormatUtil) :
    ExclusiveActivatable() {
    private val hydrator = Hydrator("ClockViewModel.hydrator")
    private lateinit var dateTimePatternGenerator: DateTimePatternGenerator

    private val formatString: Flow<String> =
        clockInteractor.onTimezoneOrLocaleChanged.mapLatest { getFormatString() }

    private val clockFormat: Flow<SimpleDateFormat> =
        formatString.mapLatest { format -> SimpleDateFormat(format) }

    private val _clockText: Flow<String> =
        combine(clockFormat, clockInteractor.currentTime) { clockFormat, time ->
            clockFormat.format(time)
        }

    val clockText: String by
        hydrator.hydratedStateOf(
            traceName = "clockText",
            initialValue = clockInteractor.currentTime.value.toString(),
            source = clockInteractor.currentTime.mapLatest { time -> time.toString() },
            source = _clockText,
        )

    override suspend fun onActivated(): Nothing {
@@ -53,4 +73,14 @@ class ClockViewModel @AssistedInject constructor(clockInteractor: ClockInteracto
    interface Factory {
        fun create(): ClockViewModel
    }

    private fun getFormatString(): String {
        dateTimePatternGenerator = DateTimePatternGenerator.getInstance(Locale.getDefault())

        // TODO(b/390204943): use different value depending on if the system want to show seconds.
        val formatSkeleton = if (dateFormatUtil.is24HourFormat) "Hm" else "hm"

        // TODO(b/390204943): handle ContentDescriptionFormat and AM/PM style
        return dateTimePatternGenerator.getBestPattern(formatSkeleton)
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import com.android.systemui.clock.data.repository.clockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.time.systemClock
import com.android.systemui.util.time.fakeSystemClock

var Kosmos.clockInteractor: ClockInteractor by
    Kosmos.Fixture {
@@ -29,7 +29,7 @@ var Kosmos.clockInteractor: ClockInteractor by
            repository = clockRepository,
            activityStarter = activityStarter,
            broadcastDispatcher = broadcastDispatcher,
            systemClock = systemClock,
            systemClock = fakeSystemClock,
            coroutineScope = backgroundScope,
        )
    }
+4 −1
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ package com.android.systemui.clock.ui.viewmodel

import com.android.systemui.clock.domain.interactor.clockInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.time.dateFormatUtil

val Kosmos.clockViewModel: ClockViewModel by
    Kosmos.Fixture { ClockViewModel(clockInteractor = clockInteractor) }
    Kosmos.Fixture {
        ClockViewModel(clockInteractor = clockInteractor, dateFormatUtil = dateFormatUtil)
    }
Loading