Merge "Modify null clock handling in picker" into main
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 27bc42c..dccef6e 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
@@ -467,15 +467,16 @@
                 it.pivotY = it.height / 2F
             }
 
-            val controller = clockViewFactory.getController(clockId)
-            if (isMiddleView) {
-                clockScaleView.scaleX = 1f
-                clockScaleView.scaleY = 1f
-                controller.largeClock.animations.onPickerCarouselSwiping(1F)
-            } else {
-                clockScaleView.scaleX = clockViewScale
-                clockScaleView.scaleY = clockViewScale
-                controller.largeClock.animations.onPickerCarouselSwiping(0F)
+            clockViewFactory.getController(clockId)?.let { controller ->
+                if (isMiddleView) {
+                    clockScaleView.scaleX = 1f
+                    clockScaleView.scaleY = 1f
+                    controller.largeClock.animations.onPickerCarouselSwiping(1F)
+                } else {
+                    clockScaleView.scaleX = clockViewScale
+                    clockScaleView.scaleY = clockViewScale
+                    controller.largeClock.animations.onPickerCarouselSwiping(0F)
+                }
             }
         }
 
diff --git a/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
index 73ebb0f..c48d810 100644
--- a/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
@@ -43,7 +43,7 @@
 class ThemePickerClockViewFactory
 @Inject
 constructor(
-    activity: Activity,
+    private val activity: Activity,
     private val wallpaperManager: WallpaperManager,
     private val registry: ClockRegistry,
 ) : ClockViewFactory {
@@ -55,9 +55,9 @@
     private val clockControllers: ConcurrentHashMap<String, ClockController> = ConcurrentHashMap()
     private val smallClockFrames: HashMap<String, FrameLayout> = HashMap()
 
-    override fun getController(clockId: String): ClockController {
+    override fun getController(clockId: String): ClockController? {
         return clockControllers[clockId]
-            ?: initClockController(clockId).also { clockControllers[clockId] = it }
+            ?: initClockController(clockId)?.also { clockControllers[clockId] = it }
     }
 
     /**
@@ -66,10 +66,10 @@
      */
     override fun getLargeView(clockId: String): View {
         assert(!Flags.newCustomizationPickerUi())
-        return getController(clockId).largeClock.let {
+        return getController(clockId)?.largeClock?.let {
             it.animations.onPickerCarouselSwiping(1F)
             it.view
-        }
+        } ?: FrameLayout(activity)
     }
 
     /**
@@ -83,9 +83,9 @@
                 (layoutParams as FrameLayout.LayoutParams).topMargin = getSmallClockTopMargin()
                 (layoutParams as FrameLayout.LayoutParams).marginStart = getSmallClockStartPadding()
             }
-                ?: createSmallClockFrame().also {
-                    it.addView(getController(clockId).smallClock.view)
-                    smallClockFrames[clockId] = it
+                ?: createSmallClockFrame().also { frame ->
+                    getController(clockId)?.let { frame.addView(it.smallClock.view) }
+                    smallClockFrames[clockId] = frame
                 }
         smallClockFrame.translationX = 0F
         smallClockFrame.translationY = 0F
@@ -130,14 +130,14 @@
     }
 
     override fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
-        getController(clockId).let {
+        getController(clockId)?.let {
             it.largeClock.run { events.onThemeChanged(theme.copy(seedColor = seedColor)) }
             it.smallClock.run { events.onThemeChanged(theme.copy(seedColor = seedColor)) }
         }
     }
 
     override fun updateFontAxes(clockId: String, settings: List<ClockFontAxisSetting>) {
-        getController(clockId).let { it.events.onFontAxesChanged(settings) }
+        getController(clockId)?.let { it.events.onFontAxesChanged(settings) }
     }
 
     override fun updateRegionDarkness() {
@@ -155,8 +155,8 @@
 
     override fun updateTimeFormat(clockId: String) {
         getController(clockId)
-            .events
-            .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
+            ?.events
+            ?.onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
     }
 
     override fun registerTimeTicker(owner: LifecycleOwner) {
@@ -190,33 +190,32 @@
         }
     }
 
-    private fun initClockController(clockId: String): ClockController {
+    private fun initClockController(clockId: String): ClockController? {
         val isWallpaperDark = isLockscreenWallpaperDark()
-        val controller =
-            registry.createExampleClock(clockId).also { it?.initialize(isWallpaperDark, 0f, 0f) }
-        checkNotNull(controller)
+        return registry.createExampleClock(clockId)?.also { controller ->
+            controller.initialize(isWallpaperDark, 0f, 0f)
 
-        // Initialize large clock
-        controller.largeClock.events.onFontSettingChanged(
-            resources
-                .getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.large_clock_text_size
-                )
-                .toFloat()
-        )
-        controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
+            // Initialize large clock
+            controller.largeClock.events.onFontSettingChanged(
+                resources
+                    .getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.large_clock_text_size
+                    )
+                    .toFloat()
+            )
+            controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
 
-        // Initialize small clock
-        controller.smallClock.events.onFontSettingChanged(
-            resources
-                .getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_text_size
-                )
-                .toFloat()
-        )
-        controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion())
-        controller.events.onWeatherDataChanged(WeatherData.getPlaceholderWeatherData())
-        return controller
+            // Initialize small clock
+            controller.smallClock.events.onFontSettingChanged(
+                resources
+                    .getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_text_size
+                    )
+                    .toFloat()
+            )
+            controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion())
+            controller.events.onWeatherDataChanged(WeatherData.getPlaceholderWeatherData())
+        }
     }
 
     /**
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 28f58c4..874b49d 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -57,14 +58,22 @@
             .mapLatest { allClocks ->
                 // Delay to avoid the case that the full list of clocks is not initiated.
                 delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
-                allClocks.map {
-                    val contentDescription =
-                        resources.getString(
-                            R.string.select_clock_action_description,
-                            clockViewFactory.getController(it.clockId).config.description
-                        )
-                    ClockCarouselItemViewModel(it.clockId, it.isSelected, contentDescription)
-                }
+                allClocks
+                    .map { model ->
+                        clockViewFactory.getController(model.clockId)?.let { clock ->
+                            val contentDescription =
+                                resources.getString(
+                                    R.string.select_clock_action_description,
+                                    clock.config.description,
+                                )
+                            ClockCarouselItemViewModel(
+                                model.clockId,
+                                model.isSelected,
+                                contentDescription,
+                            )
+                        }
+                    }
+                    .filterNotNull()
             }
             // makes sure that the operations above this statement are executed on I/O dispatcher
             // while parallelism limits the number of threads this can run on which makes sure that
@@ -126,6 +135,7 @@
             .mapNotNull { it }
 
     private var setSelectedClockJob: Job? = null
+
     fun setSelectedClock(clockId: String) {
         setSelectedClockJob?.cancel()
         setSelectedClockJob =
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index b44b152..9ce7b61 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -418,7 +418,7 @@
                             clockHostView.removeAllViews()
                             // For new customization picker, we should get views from clocklayout
                             if (Flags.newCustomizationPickerUi()) {
-                                clockViewFactory.getController(clock.clockId).let { clockController
+                                clockViewFactory.getController(clock.clockId)?.let { clockController
                                     ->
                                     val udfpsTop =
                                         clockPickerViewModel.getUdfpsLocation()?.let {
diff --git a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
index 9fc59c7..1f2a0f3 100644
--- a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
+++ b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
@@ -146,14 +146,14 @@
                             )
                             .collect { (previewingClock, previewingClockSize) ->
                                 val hideSmartspace =
-                                    clockViewFactory.getController(previewingClock.clockId).let {
+                                    clockViewFactory.getController(previewingClock.clockId)?.let {
                                         when (previewingClockSize) {
                                             ClockSize.DYNAMIC ->
                                                 it.largeClock.config.hasCustomWeatherDataDisplay
                                             ClockSize.SMALL ->
                                                 it.smallClock.config.hasCustomWeatherDataDisplay
                                         }
-                                    }
+                                    } ?: false
                                 workspaceCallback.sendMessage(
                                     MESSAGE_ID_HIDE_SMART_SPACE,
                                     Bundle().apply {
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
index 418b439..d6286a4 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
@@ -35,7 +35,7 @@
         override fun dump(pw: PrintWriter) = TODO("Not yet implemented")
     }
 
-    override fun getController(clockId: String): ClockController = clockControllers[clockId]!!
+    override fun getController(clockId: String): ClockController? = clockControllers[clockId]
 
     override fun getLargeView(clockId: String): View {
         TODO("Not yet implemented")