Make clock carousel extend ViewModel

Make clock carousel view model has the benefits:
1. view model scope can be used, so that we know what to create a new
   view model
2. Initial state is applied when new view model created
3. by making allClockIds we avoid redundently create many listeners for
   the clock registry

Test: manually test it works as what it is before
Bug: 278784119
Change-Id: Ie805725eee32e390c4442ccaea22a691b83ac106
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index 479bca2..c0e4124 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -63,7 +63,9 @@
         wallpaperColorsViewModel: WallpaperColorsViewModel,
     ): ColorPickerViewModel.Factory
 
-    fun getClockCarouselViewModel(context: Context): ClockCarouselViewModel
+    fun getClockCarouselViewModelFactory(
+        interactor: ClockPickerInteractor,
+    ): ClockCarouselViewModel.Factory
 
     fun getClockViewFactory(activity: Activity): ClockViewFactory
 
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index da11d51..2d36db4 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -26,6 +26,7 @@
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.get
 import com.android.customization.model.color.ColorCustomizationManager
 import com.android.customization.model.color.ColorOptionsProvider
 import com.android.customization.model.grid.GridOptionsManager
@@ -102,7 +103,7 @@
     private var clockRegistry: ClockRegistry? = null
     private var clockPickerInteractor: ClockPickerInteractor? = null
     private var clockSectionViewModel: ClockSectionViewModel? = null
-    private var clockCarouselViewModel: ClockCarouselViewModel? = null
+    private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null
     private var clockViewFactory: ClockViewFactory? = null
     private var notificationsInteractor: NotificationsInteractor? = null
     private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
@@ -119,6 +120,14 @@
     private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null
 
     override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
+        val clockCarouselViewModel =
+            ViewModelProvider(
+                    activity,
+                    getClockCarouselViewModelFactory(
+                        getClockPickerInteractor(activity.applicationContext),
+                    ),
+                )
+                .get() as ClockCarouselViewModel
         return customizationSections
             ?: DefaultCustomizationSections(
                     getColorPickerViewModelFactory(
@@ -131,7 +140,7 @@
                         interactor = getNotificationsInteractor(activity),
                     ),
                     getFlags(),
-                    getClockCarouselViewModel(activity),
+                    clockCarouselViewModel,
                     getClockViewFactory(activity),
                     getDarkModeSnapshotRestorer(activity),
                     getThemedIconSnapshotRestorer(activity),
@@ -346,10 +355,12 @@
             }
     }
 
-    override fun getClockCarouselViewModel(context: Context): ClockCarouselViewModel {
-        return clockCarouselViewModel
-            ?: ClockCarouselViewModel(getClockPickerInteractor(context)).also {
-                clockCarouselViewModel = it
+    override fun getClockCarouselViewModelFactory(
+        interactor: ClockPickerInteractor,
+    ): ClockCarouselViewModel.Factory {
+        return clockCarouselViewModelFactory
+            ?: ClockCarouselViewModel.Factory(interactor).also {
+                clockCarouselViewModelFactory = it
             }
     }
 
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 8d5924e..641c2d6 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -47,14 +47,18 @@
 
                 launch {
                     viewModel.allClockIds.collect { allClockIds ->
-                        carouselView.setUpClockCarouselView(
-                            clockIds = allClockIds,
-                            onGetClockController = { clockId ->
-                                clockViewFactory.getController(clockId)
-                            },
-                            onClockSelected = { clockId -> viewModel.setSelectedClock(clockId) },
-                            getPreviewRatio = { clockViewFactory.getRatio() }
-                        )
+                        if (allClockIds.size > 1) {
+                            carouselView.setUpClockCarouselView(
+                                clockIds = allClockIds,
+                                onGetClockController = { clockId ->
+                                    clockViewFactory.getController(clockId)
+                                },
+                                onClockSelected = { clockId ->
+                                    viewModel.setSelectedClock(clockId)
+                                },
+                                getPreviewRatio = { clockViewFactory.getRatio() }
+                            )
+                        }
                     }
                 }
 
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 c01f56a..34ccdb6 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -15,30 +15,39 @@
  */
 package com.android.customization.picker.clock.ui.viewmodel
 
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Clock carousel view model that provides data for the carousel of clock previews. When there is
  * only one item, we should show a single clock preview instead of a carousel.
  */
-class ClockCarouselViewModel(
+class ClockCarouselViewModel
+constructor(
     private val interactor: ClockPickerInteractor,
-) {
+) : ViewModel() {
     @OptIn(ExperimentalCoroutinesApi::class)
-    val allClockIds: Flow<List<String>> =
-        interactor.allClocks.mapLatest { allClocks ->
-            // Delay to avoid the case that the full list of clocks is not initiated.
-            delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
-            allClocks.map { it.clockId }
-        }
+    val allClockIds: StateFlow<List<String>> =
+        interactor.allClocks
+            .mapLatest { allClocks ->
+                // Delay to avoid the case that the full list of clocks is not initiated.
+                delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+                allClocks.map { it.clockId }
+            }
+            .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
 
     val seedColor: Flow<Int?> = interactor.seedColor
 
@@ -72,6 +81,18 @@
         interactor.setSelectedClock(clockId)
     }
 
+    class Factory(
+        private val interactor: ClockPickerInteractor,
+    ) : ViewModelProvider.Factory {
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            @Suppress("UNCHECKED_CAST")
+            return ClockCarouselViewModel(
+                interactor = interactor,
+            )
+                as T
+        }
+    }
+
     companion object {
         const val CLOCKS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
     }