Introduce small clock on the settings screen
This is to introduce the small clock on the settings preview.
Test: Manually tested that the small clock shows correctly. See bug
Bug: 274927017
Change-Id: I6b7a741c06e0da2d97dd57d7d6c263cb69fd20b6
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1ef739b..16b5de7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -176,6 +176,10 @@
copied from sysui resources
-->
<dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+ <!-- Dimension for the clock view, copied from sysui resources. -->
+ <dimen name="small_clock_height">114dp</dimen>
+ <dimen name="small_clock_padding_top">28dp</dimen>
+ <dimen name="clock_padding_start">28dp</dimen>
<dimen name="tab_touch_delegate_height_padding">8dp</dimen>
</resources>
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index c0e4124..dbcff27 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -15,8 +15,8 @@
*/
package com.android.customization.module
-import android.app.Activity
import android.content.Context
+import androidx.activity.ComponentActivity
import androidx.fragment.app.FragmentActivity
import com.android.customization.model.theme.OverlayManagerCompat
import com.android.customization.model.theme.ThemeBundleProvider
@@ -67,7 +67,7 @@
interactor: ClockPickerInteractor,
): ClockCarouselViewModel.Factory
- fun getClockViewFactory(activity: Activity): ClockViewFactory
+ fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory
fun getClockSettingsViewModelFactory(
context: Context,
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 7f27650..93856b6 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -15,7 +15,6 @@
*/
package com.android.customization.module
-import android.app.Activity
import android.app.UiModeManager
import android.content.Context
import android.content.Intent
@@ -25,6 +24,8 @@
import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
import com.android.customization.model.color.ColorCustomizationManager
@@ -80,6 +81,7 @@
import com.android.wallpaper.picker.customization.data.repository.WallpaperRepository
import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import com.android.wallpaper.util.ScreenSizeCalculator
import kotlinx.coroutines.Dispatchers
open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInjector {
@@ -101,7 +103,7 @@
private var clockPickerInteractor: ClockPickerInteractor? = null
private var clockSectionViewModel: ClockSectionViewModel? = null
private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null
- private var clockViewFactory: ClockViewFactory? = null
+ private var clockViewFactories: MutableMap<Int, ClockViewFactory> = HashMap()
private var notificationsInteractor: NotificationsInteractor? = null
private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
private var colorPickerInteractor: ColorPickerInteractor? = null
@@ -356,9 +358,29 @@
}
}
- override fun getClockViewFactory(activity: Activity): ClockViewFactory {
- return clockViewFactory
- ?: ClockViewFactory(activity, getClockRegistry(activity)).also { clockViewFactory = it }
+ override fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory {
+ val activityHashCode = activity.hashCode()
+ return clockViewFactories[activityHashCode]
+ ?: ClockViewFactory(
+ activity.applicationContext,
+ ScreenSizeCalculator.getInstance()
+ .getScreenSize(activity.windowManager.defaultDisplay),
+ getClockRegistry(
+ activity.applicationContext,
+ ),
+ )
+ .also {
+ clockViewFactories[activityHashCode] = it
+ activity.lifecycle.addObserver(
+ object : DefaultLifecycleObserver {
+ override fun onDestroy(owner: LifecycleOwner) {
+ super.onDestroy(owner)
+ clockViewFactories[activityHashCode]?.onDestroy()
+ clockViewFactories.remove(activityHashCode)
+ }
+ }
+ )
+ }
}
private fun getNotificationsInteractor(
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 c95561c..5323cf4 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -91,7 +91,7 @@
launch {
viewModel.clockId.collect { clockId ->
singleClockHostView.removeAllViews()
- val clockView = clockViewFactory.getView(clockId)
+ val clockView = clockViewFactory.getLargeView(clockId)
// The clock view might still be attached to an existing parent. Detach
// before adding to another parent.
(clockView.parent as? ViewGroup)?.removeView(clockView)
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
index 4a8ebeb..671a7ae 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -21,6 +21,7 @@
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.SeekBar
+import androidx.core.view.doOnPreDraw
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -40,6 +41,7 @@
import com.android.customization.picker.common.ui.view.ItemSpacing
import com.android.wallpaper.R
import com.android.wallpaper.picker.option.ui.binder.OptionItemBinder
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -91,17 +93,6 @@
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.selectedClockId
- .mapNotNull { it }
- .collect { clockId ->
- val clockView = clockViewFactory.getView(clockId)
- (clockView.parent as? ViewGroup)?.removeView(clockView)
- clockHostView.removeAllViews()
- clockHostView.addView(clockView)
- }
- }
-
- launch {
viewModel.seedColor.collect { seedColor ->
viewModel.selectedClockId.value?.let { selectedClockId ->
clockViewFactory.updateColor(selectedClockId, seedColor)
@@ -176,18 +167,41 @@
}
launch {
- viewModel.selectedClockSize.collect { size ->
- when (size) {
- ClockSize.DYNAMIC -> {
- sizeOptions.radioButtonDynamic.isChecked = true
- sizeOptions.radioButtonSmall.isChecked = false
- }
- ClockSize.SMALL -> {
- sizeOptions.radioButtonDynamic.isChecked = false
- sizeOptions.radioButtonSmall.isChecked = true
+ combine(
+ viewModel.selectedClockId.mapNotNull { it },
+ viewModel.selectedClockSize,
+ ::Pair,
+ )
+ .collect { (clockId, size) ->
+ val clockView =
+ if (size == ClockSize.DYNAMIC) {
+ clockViewFactory.getLargeView(clockId)
+ } else {
+ clockViewFactory.getSmallView(clockId)
+ }
+ (clockView.parent as? ViewGroup)?.removeView(clockView)
+ clockHostView.removeAllViews()
+ clockHostView.addView(clockView)
+
+ when (size) {
+ ClockSize.DYNAMIC -> {
+ sizeOptions.radioButtonDynamic.isChecked = true
+ sizeOptions.radioButtonSmall.isChecked = false
+ clockHostView.doOnPreDraw {
+ it.pivotX = (it.width / 2).toFloat()
+ it.pivotY = (it.height / 2).toFloat()
+ }
+ }
+ ClockSize.SMALL -> {
+ sizeOptions.radioButtonDynamic.isChecked = false
+ sizeOptions.radioButtonSmall.isChecked = true
+ clockHostView.doOnPreDraw {
+ it.pivotX = 0F
+ it.pivotY = 0F
+ }
+ }
}
}
- }
}
launch {
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 d085b7b..cfc05dc 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
@@ -15,35 +15,76 @@
*/
package com.android.customization.picker.clock.ui.view
-import android.app.Activity
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
import android.graphics.Rect
import android.util.TypedValue
import android.view.View
+import android.widget.FrameLayout
import androidx.annotation.ColorInt
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.ScreenSizeCalculator
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 activity: Activity,
+ private val appContext: Context,
+ val screenSize: Point,
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()
fun getController(clockId: String): ClockController {
- return clockControllers[clockId] ?: initClockController(clockId)
+ return clockControllers[clockId]
+ ?: initClockController(clockId).also { clockControllers[clockId] = it }
}
- fun getView(clockId: String): View {
+ fun getLargeView(clockId: String): View {
return getController(clockId).largeClock.view
}
+ fun getSmallView(clockId: String): View {
+ return smallClockFrames[clockId]
+ ?: createSmallClockFrame().also {
+ it.addView(getController(clockId).smallClock.view)
+ smallClockFrames[clockId] = it
+ }
+ }
+
+ 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 =
+ getStatusBarHeight(resources) +
+ resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+ smallClockFrame.layoutParams = layoutParams
+
+ smallClockFrame.setPaddingRelative(
+ resources.getDimensionPixelSize(R.dimen.clock_padding_start),
+ 0,
+ 0,
+ 0
+ )
+ smallClockFrame.clipChildren = false
+ return smallClockFrame
+ }
+
fun updateColorForAllClocks(@ColorInt seedColor: Int?) {
clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) }
}
@@ -57,7 +98,7 @@
fun updateTimeFormat(clockId: String) {
getController(clockId)
.events
- .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(activity))
+ .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
}
fun registerTimeTicker(owner: LifecycleOwner) {
@@ -66,37 +107,53 @@
return
}
- timeTickListeners[hashCode] =
- TimeTicker.registerNewReceiver(activity.applicationContext) { onTimeTick() }
+ 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() }
+ clockControllers.values.forEach {
+ it.largeClock.events.onTimeTick()
+ it.smallClock.events.onTimeTick()
+ }
}
fun unregisterTimeTicker(owner: LifecycleOwner) {
val hashCode = owner.hashCode()
timeTickListeners[hashCode]?.let {
- activity.applicationContext.unregisterReceiver(it)
+ appContext.unregisterReceiver(it)
timeTickListeners.remove(hashCode)
}
}
private fun initClockController(clockId: String): ClockController {
val controller =
- registry.createExampleClock(clockId).also { it?.initialize(activity.resources, 0f, 0f) }
+ registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) }
checkNotNull(controller)
// Configure light/dark theme
val isLightTheme = TypedValue()
- activity.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
+ appContext.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
val isRegionDark = isLightTheme.data == 0
controller.largeClock.events.onRegionDarknessChanged(isRegionDark)
// Configure font size
controller.largeClock.events.onFontSettingChanged(
- activity.resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
)
controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
+
+ controller.smallClock.events.onRegionDarknessChanged(isRegionDark)
+ 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
controller.events.onWeatherDataChanged(
WeatherData(
@@ -106,7 +163,6 @@
useCelsius = USE_CELSIUS_PLACEHODLER,
)
)
- clockControllers[clockId] = controller
return controller
}
@@ -115,21 +171,41 @@
* proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
* and position the clock view
*/
- fun getLargeClockRegion(): Rect {
- val screenSizeCalculator = ScreenSizeCalculator.getInstance()
- val screenSize = screenSizeCalculator.getScreenSize(activity.windowManager.defaultDisplay)
+ private fun getLargeClockRegion(): Rect {
val largeClockTopMargin =
- activity.resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
- val targetHeight =
- activity.resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2
+ 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 =
+ getStatusBarHeight(resources) +
+ resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+ val start = resources.getDimensionPixelSize(R.dimen.clock_padding_start)
+ val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height)
+ return Rect(start, topMargin, screenSize.x, topMargin + targetHeight)
+ }
+
companion object {
- val DESCRIPTION_PLACEHODLER = ""
- val TEMPERATURE_PLACEHOLDER = 58
+ const val DESCRIPTION_PLACEHODLER = ""
+ const val TEMPERATURE_PLACEHOLDER = 58
val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
- val USE_CELSIUS_PLACEHODLER = false
+ 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
+ }
}
}