Merge "Allow different tick rates for some clock designs" into tm-qpr-dev

This commit is contained in:
Hawkwood Glazier
2023-01-31 20:36:11 +00:00
committed by Android (Google) Code Review
8 changed files with 127 additions and 18 deletions

View File

@@ -85,7 +85,8 @@ class DefaultClockController(
animations = DefaultClockAnimations(dozeFraction, foldFraction) animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources) events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault()) events.onTimeZoneChanged(TimeZone.getDefault())
events.onTimeTick() smallClock.events.onTimeTick()
largeClock.events.onTimeTick()
} }
open inner class DefaultClockFaceController( open inner class DefaultClockFaceController(
@@ -109,6 +110,8 @@ class DefaultClockController(
override val events = override val events =
object : ClockFaceEvents { object : ClockFaceEvents {
override fun onTimeTick() = view.refreshTime()
override fun onRegionDarknessChanged(isRegionDark: Boolean) { override fun onRegionDarknessChanged(isRegionDark: Boolean) {
this@DefaultClockFaceController.isRegionDark = isRegionDark this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor() updateColor()
@@ -169,8 +172,6 @@ class DefaultClockController(
} }
inner class DefaultClockEvents : ClockEvents { inner class DefaultClockEvents : ClockEvents {
override fun onTimeTick() = clocks.forEach { it.refreshTime() }
override fun onTimeFormatChanged(is24Hr: Boolean) = override fun onTimeFormatChanged(is24Hr: Boolean) =
clocks.forEach { it.refreshFormat(is24Hr) } clocks.forEach { it.refreshFormat(is24Hr) }

View File

@@ -66,7 +66,8 @@ interface ClockController {
events.onColorPaletteChanged(resources) events.onColorPaletteChanged(resources)
animations.doze(dozeFraction) animations.doze(dozeFraction)
animations.fold(foldFraction) animations.fold(foldFraction)
events.onTimeTick() smallClock.events.onTimeTick()
largeClock.events.onTimeTick()
} }
/** Optional method for dumping debug information */ /** Optional method for dumping debug information */
@@ -87,9 +88,6 @@ interface ClockFaceController {
/** Events that should call when various rendering parameters change */ /** Events that should call when various rendering parameters change */
interface ClockEvents { interface ClockEvents {
/** Call every time tick */
fun onTimeTick() {}
/** Call whenever timezone changes */ /** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) {} fun onTimeZoneChanged(timeZone: TimeZone) {}
@@ -131,6 +129,13 @@ interface ClockAnimations {
/** Events that have specific data about the related face */ /** Events that have specific data about the related face */
interface ClockFaceEvents { interface ClockFaceEvents {
/** Call every time tick */
fun onTimeTick() {}
/** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
val tickRate: ClockTickRate
get() = ClockTickRate.PER_MINUTE
/** Region Darkness specific to the clock face */ /** Region Darkness specific to the clock face */
fun onRegionDarknessChanged(isDark: Boolean) {} fun onRegionDarknessChanged(isDark: Boolean) {}
@@ -150,6 +155,13 @@ interface ClockFaceEvents {
fun onTargetRegionChanged(targetRegion: Rect?) {} fun onTargetRegionChanged(targetRegion: Rect?) {}
} }
/** Tick rates for clocks */
enum class ClockTickRate(val value: Int) {
PER_MINUTE(2), // Update the clock once per minute.
PER_SECOND(1), // Update the clock once per second.
PER_FRAME(0), // Update the clock every second.
}
/** Some data about a clock design */ /** Some data about a clock design */
data class ClockMetadata( data class ClockMetadata(
val clockId: ClockId, val clockId: ClockId,

View File

@@ -25,6 +25,7 @@ import android.text.format.DateFormat
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import android.view.ViewTreeObserver
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
@@ -42,12 +43,15 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.dagger.KeyguardSmallClockLog import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.log.dagger.KeyguardLargeClockLog import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -72,7 +76,7 @@ open class ClockEventController @Inject constructor(
private val configurationController: ConfigurationController, private val configurationController: ConfigurationController,
@Main private val resources: Resources, @Main private val resources: Resources,
private val context: Context, private val context: Context,
@Main private val mainExecutor: Executor, @Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor, @Background private val bgExecutor: Executor,
@KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?, @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
@KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?, @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
@@ -94,6 +98,7 @@ open class ClockEventController @Inject constructor(
clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener) clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
} }
updateFontSizes() updateFontSizes()
updateTimeListeners()
} }
} }
@@ -208,6 +213,10 @@ open class ClockEventController @Inject constructor(
} }
var regionSampler: RegionSampler? = null var regionSampler: RegionSampler? = null
var smallTimeListener: TimeListener? = null
var largeTimeListener: TimeListener? = null
val shouldTimeListenerRun: Boolean
get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
private var smallClockIsDark = true private var smallClockIsDark = true
private var largeClockIsDark = true private var largeClockIsDark = true
@@ -246,6 +255,9 @@ open class ClockEventController @Inject constructor(
clock?.animations?.doze(if (isDozing) 1f else 0f) clock?.animations?.doze(if (isDozing) 1f else 0f)
} }
} }
smallTimeListener?.update(shouldTimeListenerRun)
largeTimeListener?.update(shouldTimeListenerRun)
} }
override fun onTimeFormatChanged(timeFormat: String) { override fun onTimeFormatChanged(timeFormat: String) {
@@ -285,6 +297,8 @@ open class ClockEventController @Inject constructor(
} }
} }
} }
smallTimeListener?.update(shouldTimeListenerRun)
largeTimeListener?.update(shouldTimeListenerRun)
} }
fun unregisterListeners() { fun unregisterListeners() {
@@ -299,6 +313,25 @@ open class ClockEventController @Inject constructor(
batteryController.removeCallback(batteryCallback) batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
regionSampler?.stopRegionSampler() regionSampler?.stopRegionSampler()
smallTimeListener?.stop()
largeTimeListener?.stop()
}
private fun updateTimeListeners() {
smallTimeListener?.stop()
largeTimeListener?.stop()
smallTimeListener = null
largeTimeListener = null
clock?.smallClock?.let {
smallTimeListener = TimeListener(it, mainExecutor)
smallTimeListener?.update(shouldTimeListenerRun)
}
clock?.largeClock?.let {
largeTimeListener = TimeListener(it, mainExecutor)
largeTimeListener?.update(shouldTimeListenerRun)
}
} }
private fun updateFontSizes() { private fun updateFontSizes() {
@@ -308,12 +341,18 @@ open class ClockEventController @Inject constructor(
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()) resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
} }
private fun handleDoze(doze: Float) {
dozeAmount = doze
clock?.animations?.doze(dozeAmount)
smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
}
@VisibleForTesting @VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job { internal fun listenForDozeAmount(scope: CoroutineScope): Job {
return scope.launch { return scope.launch {
keyguardInteractor.dozeAmount.collect { keyguardInteractor.dozeAmount.collect {
dozeAmount = it handleDoze(it)
clock?.animations?.doze(dozeAmount)
} }
} }
} }
@@ -322,8 +361,7 @@ open class ClockEventController @Inject constructor(
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch { return scope.launch {
keyguardTransitionInteractor.dozeAmountTransition.collect { keyguardTransitionInteractor.dozeAmountTransition.collect {
dozeAmount = it.value handleDoze(it.value)
clock?.animations?.doze(dozeAmount)
} }
} }
} }
@@ -338,8 +376,7 @@ open class ClockEventController @Inject constructor(
keyguardTransitionInteractor.anyStateToAodTransition.filter { keyguardTransitionInteractor.anyStateToAodTransition.filter {
it.transitionState == TransitionState.FINISHED it.transitionState == TransitionState.FINISHED
}.collect { }.collect {
dozeAmount = 1f handleDoze(1f)
clock?.animations?.doze(dozeAmount)
} }
} }
} }
@@ -359,7 +396,54 @@ open class ClockEventController @Inject constructor(
} }
} }
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
val predrawListener = ViewTreeObserver.OnPreDrawListener {
clockFace.events.onTimeTick()
true
}
val secondsRunnable = object : Runnable {
override fun run() {
if (!isRunning) {
return
}
executor.executeDelayed(this, 990)
clockFace.events.onTimeTick()
}
}
var isRunning: Boolean = false
private set
fun start() {
if (isRunning) {
return
}
isRunning = true
when (clockFace.events.tickRate) {
ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
ClockTickRate.PER_FRAME -> {
clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
clockFace.view.invalidate()
}
}
}
fun stop() {
if (!isRunning) { return }
isRunning = false
clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
}
fun update(shouldRun: Boolean) = if (shouldRun) start() else stop()
}
companion object { companion object {
private val TAG = ClockEventController::class.simpleName!! private val TAG = ClockEventController::class.simpleName!!
private val DOZE_TICKRATE_THRESHOLD = 0.99f
} }
} }

View File

@@ -344,7 +344,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
} }
ClockController clock = getClock(); ClockController clock = getClock();
if (clock != null) { if (clock != null) {
clock.getEvents().onTimeTick(); clock.getSmallClock().getEvents().onTimeTick();
clock.getLargeClock().getEvents().onTimeTick();
} }
} }

View File

@@ -177,7 +177,8 @@ constructor(
val receiver = val receiver =
object : BroadcastReceiver() { object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
clockController.clock?.events?.onTimeTick() clockController.clock?.smallClock?.events?.onTimeTick()
clockController.clock?.largeClock?.events?.onTimeTick()
} }
} }
broadcastDispatcher.registerReceiver( broadcastDispatcher.registerReceiver(

View File

@@ -32,10 +32,12 @@ import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.capture
@@ -72,7 +74,7 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var animations: ClockAnimations @Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents @Mock private lateinit var events: ClockEvents
@Mock private lateinit var clock: ClockController @Mock private lateinit var clock: ClockController
@Mock private lateinit var mainExecutor: Executor @Mock private lateinit var mainExecutor: DelayableExecutor
@Mock private lateinit var bgExecutor: Executor @Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var smallClockController: ClockFaceController @Mock private lateinit var smallClockController: ClockFaceController
@@ -97,6 +99,8 @@ class ClockEventControllerTest : SysuiTestCase() {
whenever(largeClockController.events).thenReturn(largeClockEvents) whenever(largeClockController.events).thenReturn(largeClockEvents)
whenever(clock.events).thenReturn(events) whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations) whenever(clock.animations).thenReturn(animations)
whenever(smallClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
whenever(largeClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
repository = FakeKeyguardRepository() repository = FakeKeyguardRepository()

View File

@@ -48,6 +48,7 @@ import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController; import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents; import com.android.systemui.plugins.ClockEvents;
import com.android.systemui.plugins.ClockFaceController; import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.plugins.ClockFaceEvents;
import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -100,6 +101,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock @Mock
private ClockEvents mClockEvents; private ClockEvents mClockEvents;
@Mock @Mock
private ClockFaceEvents mClockFaceEvents;
@Mock
DumpManager mDumpManager; DumpManager mDumpManager;
@Mock @Mock
ClockEventController mClockEventController; ClockEventController mClockEventController;
@@ -176,6 +179,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mClockController.getLargeClock()).thenReturn(mLargeClockController); when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
when(mClockController.getSmallClock()).thenReturn(mSmallClockController); when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
when(mClockController.getEvents()).thenReturn(mClockEvents); when(mClockController.getEvents()).thenReturn(mClockEvents);
when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
when(mClockController.getAnimations()).thenReturn(mClockAnimations); when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController); when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController); when(mClockEventController.getClock()).thenReturn(mClockController);

View File

@@ -114,7 +114,8 @@ class DefaultClockProviderTest : SysuiTestCase() {
@Test @Test
fun defaultClock_events_onTimeTick() { fun defaultClock_events_onTimeTick() {
val clock = provider.createClock(DEFAULT_CLOCK_ID) val clock = provider.createClock(DEFAULT_CLOCK_ID)
clock.events.onTimeTick() clock.smallClock.events.onTimeTick()
clock.largeClock.events.onTimeTick()
verify(mockSmallClockView).refreshTime() verify(mockSmallClockView).refreshTime()
verify(mockLargeClockView).refreshTime() verify(mockLargeClockView).refreshTime()