Merge "Fix stale home wallpaper info (1/3)" into main
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 6b615cd..1d16bc1 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -43,6 +43,7 @@
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
import com.android.customization.picker.clock.ui.view.ClockViewFactory
+import com.android.customization.picker.clock.ui.view.ClockViewFactoryImpl
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
@@ -373,7 +374,7 @@
override fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory {
val activityHashCode = activity.hashCode()
return clockViewFactories[activityHashCode]
- ?: ClockViewFactory(
+ ?: ClockViewFactoryImpl(
activity.applicationContext,
ScreenSizeCalculator.getInstance()
.getScreenSize(activity.windowManager.defaultDisplay),
diff --git a/src/com/android/customization/module/logging/StatsLogUserEventLogger.kt b/src/com/android/customization/module/logging/StatsLogUserEventLogger.kt
index 90a1c6f..344a4f8 100644
--- a/src/com/android/customization/module/logging/StatsLogUserEventLogger.kt
+++ b/src/com/android/customization/module/logging/StatsLogUserEventLogger.kt
@@ -22,6 +22,7 @@
import com.android.customization.model.color.ColorOption
import com.android.customization.model.grid.GridOption
import com.android.customization.module.SysUiStatsLogger
+import com.android.customization.module.logging.ThemesUserEventLogger.ClockSize
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.wallpaper.module.WallpaperPersister.DEST_BOTH
import com.android.wallpaper.module.WallpaperPersister.DEST_HOME_SCREEN
@@ -37,18 +38,6 @@
class StatsLogUserEventLogger(private val preferences: WallpaperPreferences) :
NoOpUserEventLogger(), ThemesUserEventLogger {
- override fun logAppLaunched(launchSource: Intent) {
- SysUiStatsLogger(SysUiStatsLog.STYLE_UICHANGED__ACTION__APP_LAUNCHED)
- .setLaunchedPreference(getAppLaunchSource(launchSource))
- .log()
- }
-
- override fun logActionClicked(collectionId: String, actionLabelResId: Int) {
- SysUiStatsLogger(StyleEnums.WALLPAPER_EXPLORE)
- .setWallpaperCategoryHash(getIdHashCode(collectionId))
- .log()
- }
-
override fun logSnapshot() {
SysUiStatsLogger(StyleEnums.SNAPSHOT)
.setWallpaperCategoryHash(preferences.getHomeCategoryHash())
@@ -59,6 +48,12 @@
.log()
}
+ override fun logAppLaunched(launchSource: Intent) {
+ SysUiStatsLogger(StyleEnums.APP_LAUNCHED)
+ .setLaunchedPreference(launchSource.getAppLaunchSource())
+ .log()
+ }
+
override fun logWallpaperApplied(
collectionId: String?,
wallpaperId: String?,
@@ -114,20 +109,69 @@
.log()
}
- override fun logColorApplied(action: Int, colorOption: ColorOption) {
- SysUiStatsLogger(action)
+ override fun logResetApplied() {
+ SysUiStatsLogger(StyleEnums.RESET_APPLIED).log()
+ }
+
+ override fun logWallpaperExploreButtonClicked() {
+ SysUiStatsLogger(StyleEnums.WALLPAPER_EXPLORE).log()
+ }
+
+ override fun logThemeColorApplied(colorOption: ColorOption) {
+ SysUiStatsLogger(StyleEnums.THEME_COLOR_APPLIED)
.setColorPreference(colorOption.index)
.setColorVariant(colorOption.style.ordinal + 1)
.log()
}
override fun logGridApplied(grid: GridOption) {
- SysUiStatsLogger(StyleEnums.PICKER_APPLIED).setLauncherGrid(grid.cols).log()
+ SysUiStatsLogger(StyleEnums.GRID_APPLIED).setLauncherGrid(grid.getLauncherGridInt()).log()
}
- private fun getAppLaunchSource(launchSource: Intent): Int {
- return if (launchSource.hasExtra(LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE)) {
- when (launchSource.getStringExtra(LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE)) {
+ override fun logClockApplied(clockId: String) {
+ SysUiStatsLogger(StyleEnums.CLOCK_APPLIED).setClockPackageHash(getIdHashCode(clockId)).log()
+ }
+
+ override fun logClockColorApplied(seedColor: Int) {
+ SysUiStatsLogger(StyleEnums.CLOCK_COLOR_APPLIED).setSeedColor(seedColor).log()
+ }
+
+ override fun logClockSizeApplied(@ClockSize clockSize: Int) {
+ SysUiStatsLogger(StyleEnums.CLOCK_SIZE_APPLIED).setClockSize(clockSize).log()
+ }
+
+ override fun logThemedIconApplied(useThemeIcon: Boolean) {
+ SysUiStatsLogger(StyleEnums.THEMED_ICON_APPLIED).setToggleOn(useThemeIcon).log()
+ }
+
+ override fun logLockScreenNotificationApplied(showLockScreenNotifications: Boolean) {
+ SysUiStatsLogger(StyleEnums.LOCK_SCREEN_NOTIFICATION_APPLIED)
+ .setToggleOn(showLockScreenNotifications)
+ .log()
+ }
+
+ override fun logShortcutApplied(shortcut: String, shortcutSlotId: String) {
+ SysUiStatsLogger(StyleEnums.SHORTCUT_APPLIED)
+ .setShortcut(shortcut)
+ .setShortcutSlotId(shortcutSlotId)
+ .log()
+ }
+
+ override fun logDarkThemeApplied(useDarkTheme: Boolean) {
+ SysUiStatsLogger(StyleEnums.DARK_THEME_APPLIED).setToggleOn(useDarkTheme).log()
+ }
+
+ /**
+ * The grid integer depends on the column and row numbers. For example: 4x5 is 405 13x37 is 1337
+ * The upper limit for the column / row count is 99.
+ */
+ private fun GridOption.getLauncherGridInt(): Int {
+ return cols * 100 + rows
+ }
+
+ private fun Intent.getAppLaunchSource(): Int {
+ return if (hasExtra(LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE)) {
+ when (getStringExtra(LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE)) {
LaunchSourceUtils.LAUNCH_SOURCE_LAUNCHER ->
SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_LAUNCHER
LaunchSourceUtils.LAUNCH_SOURCE_SETTINGS ->
@@ -142,17 +186,11 @@
SysUiStatsLog
.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_PREFERENCE_UNSPECIFIED
}
- } else if (launchSource.hasExtra(LaunchSourceUtils.LAUNCH_SETTINGS_SEARCH)) {
+ } else if (hasExtra(LaunchSourceUtils.LAUNCH_SETTINGS_SEARCH)) {
SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_SETTINGS_SEARCH
- } else if (
- launchSource.action != null &&
- launchSource.action == WallpaperManager.ACTION_CROP_AND_SET_WALLPAPER
- ) {
+ } else if (action != null && action == WallpaperManager.ACTION_CROP_AND_SET_WALLPAPER) {
SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_CROP_AND_SET_ACTION
- } else if (
- launchSource.categories != null &&
- launchSource.categories.contains(Intent.CATEGORY_LAUNCHER)
- ) {
+ } else if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) {
SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_LAUNCH_ICON
} else {
SysUiStatsLog.STYLE_UICHANGED__LAUNCHED_PREFERENCE__LAUNCHED_PREFERENCE_UNSPECIFIED
diff --git a/src/com/android/customization/module/logging/ThemesUserEventLogger.kt b/src/com/android/customization/module/logging/ThemesUserEventLogger.kt
index 4fd5334..1210343 100644
--- a/src/com/android/customization/module/logging/ThemesUserEventLogger.kt
+++ b/src/com/android/customization/module/logging/ThemesUserEventLogger.kt
@@ -15,19 +15,38 @@
*/
package com.android.customization.module.logging
+import android.stats.style.StyleEnums
+import androidx.annotation.IntDef
import com.android.customization.model.color.ColorOption
import com.android.customization.model.grid.GridOption
import com.android.wallpaper.module.logging.UserEventLogger
/** Extension of [UserEventLogger] that adds ThemePicker specific events. */
interface ThemesUserEventLogger : UserEventLogger {
- /**
- * Logs the color usage while color is applied.
- *
- * @param action color applied action.
- * @param colorOption applied color option.
- */
- fun logColorApplied(action: Int, colorOption: ColorOption)
+
+ fun logThemeColorApplied(colorOption: ColorOption)
fun logGridApplied(grid: GridOption)
+
+ fun logClockApplied(clockId: String)
+
+ fun logClockColorApplied(seedColor: Int)
+
+ fun logClockSizeApplied(@ClockSize clockSize: Int)
+
+ fun logThemedIconApplied(useThemeIcon: Boolean)
+
+ fun logLockScreenNotificationApplied(showLockScreenNotifications: Boolean)
+
+ fun logShortcutApplied(shortcut: String, shortcutSlotId: String)
+
+ fun logDarkThemeApplied(useDarkTheme: Boolean)
+
+ @IntDef(
+ StyleEnums.CLOCK_SIZE_UNSPECIFIED,
+ StyleEnums.CLOCK_SIZE_DYNAMIC,
+ StyleEnums.CLOCK_SIZE_SMALL,
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class ClockSize
}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
index 3f6f423..1433e98 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
@@ -15,226 +15,38 @@
*/
package com.android.customization.picker.clock.ui.view
-import android.app.WallpaperColors
-import android.app.WallpaperManager
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Point
-import android.graphics.Rect
import android.view.View
-import android.widget.FrameLayout
import androidx.annotation.ColorInt
-import androidx.core.text.util.LocalePreferences
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.WeatherData
-import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.wallpaper.R
-import com.android.wallpaper.util.TimeUtils.TimeTicker
-import java.util.concurrent.ConcurrentHashMap
-/**
- * Provide reusable clock view and related util functions.
- *
- * @property screenSize The Activity or Fragment's window size.
- */
-class ClockViewFactory(
- private val appContext: Context,
- val screenSize: Point,
- private val wallpaperManager: WallpaperManager,
- private val registry: ClockRegistry,
-) {
- private val resources = appContext.resources
- private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap()
- private val clockControllers: HashMap<String, ClockController> = HashMap()
- private val smallClockFrames: HashMap<String, FrameLayout> = HashMap()
+interface ClockViewFactory {
- fun getController(clockId: String): ClockController {
- return clockControllers[clockId]
- ?: initClockController(clockId).also { clockControllers[clockId] = it }
- }
+ fun getController(clockId: String): ClockController
/**
* Reset the large view to its initial state when getting the view. This is because some view
* configs, e.g. animation state, might change during the reuse of the clock view in the app.
*/
- fun getLargeView(clockId: String): View {
- return getController(clockId).largeClock.let {
- it.animations.onPickerCarouselSwiping(1F)
- it.view
- }
- }
+ fun getLargeView(clockId: String): View
/**
* Reset the small view to its initial state when getting the view. This is because some view
* configs, e.g. translation X, might change during the reuse of the clock view in the app.
*/
- fun getSmallView(clockId: String): View {
- val smallClockFrame =
- smallClockFrames[clockId]
- ?: createSmallClockFrame().also {
- it.addView(getController(clockId).smallClock.view)
- smallClockFrames[clockId] = it
- }
- smallClockFrame.translationX = 0F
- smallClockFrame.translationY = 0F
- return smallClockFrame
- }
+ fun getSmallView(clockId: String): View
- private fun createSmallClockFrame(): FrameLayout {
- val smallClockFrame = FrameLayout(appContext)
- val layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- resources.getDimensionPixelSize(R.dimen.small_clock_height)
- )
- layoutParams.topMargin = getSmallClockTopMargin()
- layoutParams.marginStart = getSmallClockStartPadding()
- smallClockFrame.layoutParams = layoutParams
- smallClockFrame.clipChildren = false
- return smallClockFrame
- }
+ fun updateColorForAllClocks(@ColorInt seedColor: Int?)
- private fun getSmallClockTopMargin() =
- getStatusBarHeight(appContext.resources) +
- appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+ fun updateColor(clockId: String, @ColorInt seedColor: Int?)
- private fun getSmallClockStartPadding() =
- appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start)
+ fun updateRegionDarkness()
- fun updateColorForAllClocks(@ColorInt seedColor: Int?) {
- clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) }
- }
+ fun updateTimeFormat(clockId: String)
- fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
- clockControllers[clockId]?.events?.onSeedColorChanged(seedColor)
- }
+ fun registerTimeTicker(owner: LifecycleOwner)
- fun updateRegionDarkness() {
- val isRegionDark = isLockscreenWallpaperDark()
- clockControllers.values.forEach {
- it.largeClock.events.onRegionDarknessChanged(isRegionDark)
- it.smallClock.events.onRegionDarknessChanged(isRegionDark)
- }
- }
+ fun onDestroy()
- private fun isLockscreenWallpaperDark(): Boolean {
- val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK)
- return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
- }
-
- fun updateTimeFormat(clockId: String) {
- getController(clockId)
- .events
- .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
- }
-
- fun registerTimeTicker(owner: LifecycleOwner) {
- val hashCode = owner.hashCode()
- if (timeTickListeners.keys.contains(hashCode)) {
- return
- }
-
- timeTickListeners[hashCode] = TimeTicker.registerNewReceiver(appContext) { onTimeTick() }
- }
-
- fun onDestroy() {
- timeTickListeners.forEach { (_, timeTicker) -> appContext.unregisterReceiver(timeTicker) }
- timeTickListeners.clear()
- clockControllers.clear()
- smallClockFrames.clear()
- }
-
- private fun onTimeTick() {
- clockControllers.values.forEach {
- it.largeClock.events.onTimeTick()
- it.smallClock.events.onTimeTick()
- }
- }
-
- fun unregisterTimeTicker(owner: LifecycleOwner) {
- val hashCode = owner.hashCode()
- timeTickListeners[hashCode]?.let {
- appContext.unregisterReceiver(it)
- timeTickListeners.remove(hashCode)
- }
- }
-
- private fun initClockController(clockId: String): ClockController {
- val controller =
- registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) }
- checkNotNull(controller)
-
- val isWallpaperDark = isLockscreenWallpaperDark()
- // Initialize large clock
- controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark)
- controller.largeClock.events.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
-
- // Initialize small clock
- controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark)
- controller.smallClock.events.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion())
-
- // Use placeholder for weather clock preview in picker.
- // Use locale default temp unit since assistant default is not available in this context.
- val useCelsius =
- LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
- controller.events.onWeatherDataChanged(
- WeatherData(
- description = DESCRIPTION_PLACEHODLER,
- state = WEATHERICON_PLACEHOLDER,
- temperature =
- if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
- else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
- useCelsius = useCelsius,
- )
- )
- return controller
- }
-
- /**
- * Simulate the function of getLargeClockRegion in KeyguardClockSwitch so that we can get a
- * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
- * and position the clock view
- */
- private fun getLargeClockRegion(): Rect {
- val largeClockTopMargin =
- resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
- val targetHeight = resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2
- val top = (screenSize.y / 2 - targetHeight / 2 + largeClockTopMargin / 2)
- return Rect(0, top, screenSize.x, (top + targetHeight))
- }
-
- /**
- * Simulate the function of getSmallClockRegion in KeyguardClockSwitch so that we can get a
- * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
- * and position the clock view
- */
- private fun getSmallClockRegion(): Rect {
- val topMargin = getSmallClockTopMargin()
- val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height)
- return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight)
- }
-
- companion object {
- const val DESCRIPTION_PLACEHODLER = ""
- const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
- const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
- val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
- const val USE_CELSIUS_PLACEHODLER = false
-
- private fun getStatusBarHeight(resource: Resources): Int {
- var result = 0
- val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
- if (resourceId > 0) {
- result = resource.getDimensionPixelSize(resourceId)
- }
- return result
- }
- }
+ fun unregisterTimeTicker(owner: LifecycleOwner)
}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt
new file mode 100644
index 0000000..9116f3f
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.customization.picker.clock.ui.view
+
+import android.app.WallpaperColors
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.core.text.util.LocalePreferences
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.WeatherData
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.wallpaper.R
+import com.android.wallpaper.util.TimeUtils.TimeTicker
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Provide reusable clock view and related util functions.
+ *
+ * @property screenSize The Activity or Fragment's window size.
+ */
+class ClockViewFactoryImpl(
+ private val appContext: Context,
+ val screenSize: Point,
+ private val wallpaperManager: WallpaperManager,
+ private val registry: ClockRegistry,
+) : ClockViewFactory {
+ private val resources = appContext.resources
+ private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap()
+ private val clockControllers: HashMap<String, ClockController> = HashMap()
+ private val smallClockFrames: HashMap<String, FrameLayout> = HashMap()
+
+ override fun getController(clockId: String): ClockController {
+ return clockControllers[clockId]
+ ?: initClockController(clockId).also { clockControllers[clockId] = it }
+ }
+
+ /**
+ * Reset the large view to its initial state when getting the view. This is because some view
+ * configs, e.g. animation state, might change during the reuse of the clock view in the app.
+ */
+ override fun getLargeView(clockId: String): View {
+ return getController(clockId).largeClock.let {
+ it.animations.onPickerCarouselSwiping(1F)
+ it.view
+ }
+ }
+
+ /**
+ * Reset the small view to its initial state when getting the view. This is because some view
+ * configs, e.g. translation X, might change during the reuse of the clock view in the app.
+ */
+ override fun getSmallView(clockId: String): View {
+ val smallClockFrame =
+ smallClockFrames[clockId]
+ ?: createSmallClockFrame().also {
+ it.addView(getController(clockId).smallClock.view)
+ smallClockFrames[clockId] = it
+ }
+ smallClockFrame.translationX = 0F
+ smallClockFrame.translationY = 0F
+ return smallClockFrame
+ }
+
+ private fun createSmallClockFrame(): FrameLayout {
+ val smallClockFrame = FrameLayout(appContext)
+ val layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ resources.getDimensionPixelSize(R.dimen.small_clock_height)
+ )
+ layoutParams.topMargin = getSmallClockTopMargin()
+ layoutParams.marginStart = getSmallClockStartPadding()
+ smallClockFrame.layoutParams = layoutParams
+ smallClockFrame.clipChildren = false
+ return smallClockFrame
+ }
+
+ private fun getSmallClockTopMargin() =
+ getStatusBarHeight(appContext.resources) +
+ appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+
+ private fun getSmallClockStartPadding() =
+ appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start)
+
+ override fun updateColorForAllClocks(@ColorInt seedColor: Int?) {
+ clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) }
+ }
+
+ override fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
+ clockControllers[clockId]?.events?.onSeedColorChanged(seedColor)
+ }
+
+ override fun updateRegionDarkness() {
+ val isRegionDark = isLockscreenWallpaperDark()
+ clockControllers.values.forEach {
+ it.largeClock.events.onRegionDarknessChanged(isRegionDark)
+ it.smallClock.events.onRegionDarknessChanged(isRegionDark)
+ }
+ }
+
+ private fun isLockscreenWallpaperDark(): Boolean {
+ val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK)
+ return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+ }
+
+ override fun updateTimeFormat(clockId: String) {
+ getController(clockId)
+ .events
+ .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
+ }
+
+ override fun registerTimeTicker(owner: LifecycleOwner) {
+ val hashCode = owner.hashCode()
+ if (timeTickListeners.keys.contains(hashCode)) {
+ return
+ }
+
+ timeTickListeners[hashCode] = TimeTicker.registerNewReceiver(appContext) { onTimeTick() }
+ }
+
+ override fun onDestroy() {
+ timeTickListeners.forEach { (_, timeTicker) -> appContext.unregisterReceiver(timeTicker) }
+ timeTickListeners.clear()
+ clockControllers.clear()
+ smallClockFrames.clear()
+ }
+
+ private fun onTimeTick() {
+ clockControllers.values.forEach {
+ it.largeClock.events.onTimeTick()
+ it.smallClock.events.onTimeTick()
+ }
+ }
+
+ override fun unregisterTimeTicker(owner: LifecycleOwner) {
+ val hashCode = owner.hashCode()
+ timeTickListeners[hashCode]?.let {
+ appContext.unregisterReceiver(it)
+ timeTickListeners.remove(hashCode)
+ }
+ }
+
+ private fun initClockController(clockId: String): ClockController {
+ val controller =
+ registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) }
+ checkNotNull(controller)
+
+ val isWallpaperDark = isLockscreenWallpaperDark()
+ // Initialize large clock
+ controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark)
+ controller.largeClock.events.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
+ controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
+
+ // Initialize small clock
+ controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark)
+ controller.smallClock.events.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion())
+
+ // Use placeholder for weather clock preview in picker.
+ // Use locale default temp unit since assistant default is not available in this context.
+ val useCelsius =
+ LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+ controller.events.onWeatherDataChanged(
+ WeatherData(
+ description = DESCRIPTION_PLACEHODLER,
+ state = WEATHERICON_PLACEHOLDER,
+ temperature =
+ if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+ else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+ useCelsius = useCelsius,
+ )
+ )
+ return controller
+ }
+
+ /**
+ * Simulate the function of getLargeClockRegion in KeyguardClockSwitch so that we can get a
+ * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
+ * and position the clock view
+ */
+ private fun getLargeClockRegion(): Rect {
+ val largeClockTopMargin =
+ resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
+ val targetHeight = resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2
+ val top = (screenSize.y / 2 - targetHeight / 2 + largeClockTopMargin / 2)
+ return Rect(0, top, screenSize.x, (top + targetHeight))
+ }
+
+ /**
+ * Simulate the function of getSmallClockRegion in KeyguardClockSwitch so that we can get a
+ * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
+ * and position the clock view
+ */
+ private fun getSmallClockRegion(): Rect {
+ val topMargin = getSmallClockTopMargin()
+ val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height)
+ return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight)
+ }
+
+ companion object {
+ const val DESCRIPTION_PLACEHODLER = ""
+ const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+ const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+ val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+ const val USE_CELSIUS_PLACEHODLER = false
+
+ private fun getStatusBarHeight(resource: Resources): Int {
+ var result = 0
+ val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
+ if (resourceId > 0) {
+ result = resource.getDimensionPixelSize(resourceId)
+ }
+ return result
+ }
+ }
+}
diff --git a/tests/common/src/com/android/customization/testing/TestThemesUserEventLogger.kt b/tests/common/src/com/android/customization/testing/TestThemesUserEventLogger.kt
index 2236b62..8b14688 100644
--- a/tests/common/src/com/android/customization/testing/TestThemesUserEventLogger.kt
+++ b/tests/common/src/com/android/customization/testing/TestThemesUserEventLogger.kt
@@ -23,7 +23,21 @@
/** Test implementation of [ThemesUserEventLogger]. */
class TestThemesUserEventLogger : TestUserEventLogger(), ThemesUserEventLogger {
- override fun logColorApplied(action: Int, colorOption: ColorOption) {}
+ override fun logThemeColorApplied(colorOption: ColorOption) {}
override fun logGridApplied(grid: GridOption) {}
+
+ override fun logClockApplied(clockId: String) {}
+
+ override fun logClockColorApplied(seedColor: Int) {}
+
+ override fun logClockSizeApplied(clockSize: Int) {}
+
+ override fun logThemedIconApplied(useThemeIcon: Boolean) {}
+
+ override fun logLockScreenNotificationApplied(showLockScreenNotifications: Boolean) {}
+
+ override fun logShortcutApplied(shortcut: String, shortcutSlotId: String) {}
+
+ override fun logDarkThemeApplied(useDarkTheme: Boolean) {}
}
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
new file mode 100644
index 0000000..31999fb
--- /dev/null
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
@@ -0,0 +1,52 @@
+package com.android.customization.picker.clock.ui
+
+import android.view.View
+import androidx.lifecycle.LifecycleOwner
+import com.android.customization.picker.clock.ui.view.ClockViewFactory
+import com.android.systemui.plugins.ClockController
+
+/**
+ * This is a fake [ClockViewFactory]. Only implement the function if it's actually called in a test.
+ */
+class FakeClockViewFactory : ClockViewFactory {
+
+ override fun getController(clockId: String): ClockController {
+ TODO("Not yet implemented")
+ }
+
+ override fun getLargeView(clockId: String): View {
+ TODO("Not yet implemented")
+ }
+
+ override fun getSmallView(clockId: String): View {
+ TODO("Not yet implemented")
+ }
+
+ override fun updateColorForAllClocks(seedColor: Int?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun updateColor(clockId: String, seedColor: Int?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun updateRegionDarkness() {
+ TODO("Not yet implemented")
+ }
+
+ override fun updateTimeFormat(clockId: String) {
+ TODO("Not yet implemented")
+ }
+
+ override fun registerTimeTicker(owner: LifecycleOwner) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onDestroy() {
+ TODO("Not yet implemented")
+ }
+
+ override fun unregisterTimeTicker(owner: LifecycleOwner) {
+ TODO("Not yet implemented")
+ }
+}