Merge "[TP] Make all clocks a flow" into tm-qpr-dev
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index df92556..9a58d70 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -318,7 +318,6 @@
secureSettingsRepository = getSecureSettingsRepository(context),
registry = clockRegistry,
scope = GlobalScope,
- backgroundDispatcher = Dispatchers.IO,
),
)
.also { clockPickerInteractor = it }
@@ -339,9 +338,8 @@
clockRegistry: ClockRegistry
): ClockCarouselViewModel {
return clockCarouselViewModel
- ?: ClockCarouselViewModel(getClockPickerInteractor(context, clockRegistry)).also {
- clockCarouselViewModel = it
- }
+ ?: ClockCarouselViewModel(getClockPickerInteractor(context, clockRegistry))
+ .also { clockCarouselViewModel = it }
}
override fun getClockViewFactory(
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
index 690b649..66793cd 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -25,7 +25,7 @@
* clocks.
*/
interface ClockPickerRepository {
- val allClocks: Array<ClockMetadataModel>
+ val allClocks: Flow<List<ClockMetadataModel>>
val selectedClock: Flow<ClockMetadataModel>
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index 6c845f9..a360b6c 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -17,64 +17,84 @@
package com.android.customization.picker.clock.data.repository
import android.provider.Settings
-import android.util.Log
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/** Implementation of [ClockPickerRepository], using [ClockRegistry]. */
class ClockPickerRepositoryImpl(
private val secureSettingsRepository: SecureSettingsRepository,
private val registry: ClockRegistry,
- private val scope: CoroutineScope,
- private val backgroundDispatcher: CoroutineDispatcher,
+ scope: CoroutineScope,
) : ClockPickerRepository {
- override val allClocks: Array<ClockMetadataModel> =
- registry
- .getClocks()
- .filter { "NOT_IN_USE" !in it.clockId }
- .map { it.toModel(null) }
- .toTypedArray()
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override val allClocks: Flow<List<ClockMetadataModel>> =
+ callbackFlow {
+ fun send() {
+ val allClocks =
+ registry
+ .getClocks()
+ .filter { "NOT_IN_USE" !in it.clockId }
+ .map { it.toModel(null) }
+ trySend(allClocks)
+ }
+
+ val listener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onAvailableClocksChanged() {
+ send()
+ }
+ }
+ registry.registerClockChangeListener(listener)
+ send()
+ awaitClose { registry.unregisterClockChangeListener(listener) }
+ }
+ .mapLatest { allClocks ->
+ // Loading list of clock plugins can cause many consecutive calls of
+ // onAvailableClocksChanged(). We only care about the final fully-initiated clock
+ // list. Delay to avoid unnecessary too many emits.
+ delay(100)
+ allClocks
+ }
/** The currently-selected clock. */
override val selectedClock: Flow<ClockMetadataModel> =
callbackFlow {
- suspend fun send() {
- val currentClockId =
- withContext(backgroundDispatcher) { registry.currentClockId }
+ fun send() {
+ val currentClockId = registry.currentClockId
+ // It is possible that the model can be null since the full clock list is not
+ // initiated.
val model =
registry
.getClocks()
.find { clockMetadata -> clockMetadata.clockId == currentClockId }
?.toModel(registry.seedColor)
- if (model == null) {
- Log.w(
- TAG,
- "Clock with ID \"$currentClockId\" not found!",
- )
- }
trySend(model)
}
val listener =
object : ClockRegistry.ClockChangeListener {
override fun onCurrentClockChanged() {
- scope.launch { send() }
+ send()
+ }
+
+ override fun onAvailableClocksChanged() {
+ send()
}
}
registry.registerClockChangeListener(listener)
@@ -105,19 +125,13 @@
)
override suspend fun setClockSize(size: ClockSize) {
- withContext(backgroundDispatcher) {
- secureSettingsRepository.set(
- name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
- value = if (size == ClockSize.DYNAMIC) 1 else 0,
- )
- }
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
+ value = if (size == ClockSize.DYNAMIC) 1 else 0,
+ )
}
private fun ClockMetadata.toModel(color: Int?): ClockMetadataModel {
return ClockMetadataModel(clockId = clockId, name = name, color = color)
}
-
- companion object {
- private const val TAG = "ClockPickerRepositoryImpl"
- }
}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
index c12778b..677fcfa 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -28,7 +28,8 @@
* clocks.
*/
class ClockPickerInteractor(private val repository: ClockPickerRepository) {
- val allClocks: Array<ClockMetadataModel> = repository.allClocks
+
+ val allClocks: Flow<List<ClockMetadataModel>> = repository.allClocks
val selectedClock: Flow<ClockMetadataModel> = repository.selectedClock
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
index 841c411..35a78cb 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -23,6 +23,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.customization.picker.clock.ui.view.ClockCarouselView
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
object ClockCarouselViewBinder {
@@ -42,14 +43,22 @@
clockViewFactory: (clockId: String) -> View,
lifecycleOwner: LifecycleOwner,
): Binding {
- view.setUpImageCarouselView(
- clockIds = viewModel.allClockIds,
- onGetClockPreview = clockViewFactory,
- onClockSelected = { clockId -> viewModel.setSelectedClock(clockId) }
- )
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch { viewModel.selectedClockId.collect { view.setSelectedClockId(it) } }
+ launch {
+ viewModel.allClockIds.collect { allClockIds ->
+ view.setUpClockCarouselView(
+ clockIds = allClockIds,
+ onGetClockPreview = clockViewFactory,
+ onClockSelected = { clockId -> viewModel.setSelectedClock(clockId) },
+ )
+ }
+ }
+ launch {
+ viewModel.selectedIndex.collect { selectedIndex ->
+ view.setSelectedClockIndex(selectedIndex)
+ }
+ }
}
}
return object : Binding {
diff --git a/src/com/android/customization/picker/clock/ui/fragment/ClockCarouselDemoFragment.kt b/src/com/android/customization/picker/clock/ui/fragment/ClockCarouselDemoFragment.kt
index a4b1c1e..d0186b2 100644
--- a/src/com/android/customization/picker/clock/ui/fragment/ClockCarouselDemoFragment.kt
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockCarouselDemoFragment.kt
@@ -49,10 +49,9 @@
}
ClockCarouselViewBinder.bind(
view = carouselView,
- viewModel =
- ClockCarouselViewModel(
- injector.getClockPickerInteractor(requireContext(), registry)
- ),
+ viewModel = ClockCarouselViewModel(
+ injector.getClockPickerInteractor(requireContext(), registry),
+ ),
clockViewFactory = { clockId ->
registry.createExampleClock(clockId)?.largeClock?.view!!
},
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
index e16492f..90d7c42 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
@@ -42,23 +42,24 @@
carousel = view.requireViewById(R.id.carousel)
}
- fun setUpImageCarouselView(
- clockIds: Array<String>,
+ fun setUpClockCarouselView(
+ clockIds: List<String>,
onGetClockPreview: (clockId: String) -> View,
onClockSelected: (clockId: String) -> Unit,
) {
adapter = ClockCarouselAdapter(clockIds, onGetClockPreview, onClockSelected)
carousel.setAdapter(adapter)
+ carousel.refresh()
}
- fun setSelectedClockId(
- selectedClockId: String,
+ fun setSelectedClockIndex(
+ index: Int,
) {
- carousel.jumpToIndex(adapter.clockIds.indexOf(selectedClockId))
+ carousel.jumpToIndex(index)
}
class ClockCarouselAdapter(
- val clockIds: Array<String>,
+ val clockIds: List<String>,
val onGetClockPreview: (clockId: String) -> View,
val onClockSelected: (clockId: String) -> Unit,
) : Carousel.Adapter {
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index b126a73..669c047 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -16,15 +16,49 @@
package com.android.customization.picker.clock.ui.viewmodel
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.mapNotNull
-class ClockCarouselViewModel(private val interactor: ClockPickerInteractor) {
- val selectedClockId: Flow<String> = interactor.selectedClock.map { it.clockId }
+class ClockCarouselViewModel(
+ private val interactor: ClockPickerInteractor,
+) {
- val allClockIds: Array<String> = interactor.allClocks.map { it.clockId }.toTypedArray()
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val allClockIds: Flow<List<String>> =
+ interactor.allClocks
+ .mapLatest { clockArray ->
+ // Delay to avoid the case that the full list of clocks is not initiated.
+ delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+ clockArray.map { it.clockId }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val selectedIndex: Flow<Int> =
+ allClockIds
+ .flatMapLatest { allClockIds ->
+ interactor.selectedClock.map { selectedClock ->
+ val index = allClockIds.indexOf(selectedClock.clockId)
+ if (index >= 0) {
+ index
+ } else {
+ null
+ }
+ }
+ }
+ .mapNotNull { it }
fun setSelectedClock(clockId: String) {
interactor.setSelectedClock(clockId)
}
+
+ companion object {
+ const val CLOCKS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
+ }
}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
index 1ffb7b8..c15bc67 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -232,7 +232,7 @@
arrayOf(144f, 0.65f, 0.74f).toFloatArray(),
)
- val COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
+ const val COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
fun getSelectedColorPosition(selectedColorHsl: FloatArray): Int {
return COLOR_LIST_HSL.withIndex().minBy { abs(it.value[0] - selectedColorHsl[0]) }.index
diff --git a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index 1614c61..d2a9efc 100644
--- a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -23,14 +23,14 @@
import kotlinx.coroutines.flow.combine
class FakeClockPickerRepository : ClockPickerRepository {
+ override val allClocks: Flow<List<ClockMetadataModel>> =
+ MutableStateFlow(fakeClocks).asStateFlow()
- override val allClocks: Array<ClockMetadataModel> = fakeClocks
-
- private val _selectedClockId = MutableStateFlow(fakeClocks[0].clockId)
- private val _clockColor = MutableStateFlow<Int?>(null)
+ private val selectedClockId = MutableStateFlow(fakeClocks[0].clockId)
+ private val clockColor = MutableStateFlow<Int?>(null)
override val selectedClock: Flow<ClockMetadataModel> =
- combine(_selectedClockId, _clockColor) { selectedClockId, clockColor ->
- val selectedClock = allClocks.find { clock -> clock.clockId == selectedClockId }
+ combine(selectedClockId, clockColor) { selectedClockId, clockColor ->
+ val selectedClock = fakeClocks.find { clock -> clock.clockId == selectedClockId }
checkNotNull(selectedClock)
ClockMetadataModel(selectedClock.clockId, selectedClock.name, clockColor)
}
@@ -39,11 +39,11 @@
override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
override fun setSelectedClock(clockId: String) {
- _selectedClockId.value = clockId
+ selectedClockId.value = clockId
}
override fun setClockColor(color: Int?) {
- _clockColor.value = color
+ clockColor.value = color
}
override suspend fun setClockSize(size: ClockSize) {
@@ -52,7 +52,7 @@
companion object {
val fakeClocks =
- arrayOf(
+ listOf(
ClockMetadataModel("clock0", "clock0", null),
ClockMetadataModel("clock1", "clock1", null),
ClockMetadataModel("clock2", "clock2", null),
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
index 776663e..0ce9714 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
@@ -23,6 +23,7 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
@@ -43,7 +44,8 @@
fun setUp() {
val testDispatcher = StandardTestDispatcher()
Dispatchers.setMain(testDispatcher)
- underTest = ClockCarouselViewModel(ClockPickerInteractor(FakeClockPickerRepository()))
+ underTest =
+ ClockCarouselViewModel(ClockPickerInteractor(FakeClockPickerRepository()))
}
@After
@@ -53,10 +55,9 @@
@Test
fun setSelectedClock() = runTest {
- val observedSelectedClock = collectLastValue(underTest.selectedClockId)
-
+ val observedSelectedIndex = collectLastValue(underTest.selectedIndex)
+ advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
underTest.setSelectedClock(FakeClockPickerRepository.fakeClocks[2].clockId)
- assertThat(observedSelectedClock())
- .isEqualTo(FakeClockPickerRepository.fakeClocks[2].clockId)
+ assertThat(observedSelectedIndex()).isEqualTo(2)
}
}
diff --git a/tests/src/com/android/customization/testing/TestCustomizationInjector.kt b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
index 0aac5cc..2fe3309 100644
--- a/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
+++ b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
@@ -195,9 +195,8 @@
clockRegistry: ClockRegistry
): ClockCarouselViewModel {
return clockCarouselViewModel
- ?: ClockCarouselViewModel(getClockPickerInteractor(context, clockRegistry)).also {
- clockCarouselViewModel = it
- }
+ ?: ClockCarouselViewModel(getClockPickerInteractor(context, clockRegistry))
+ .also { clockCarouselViewModel = it }
}
override fun getClockViewFactory(