1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.keyguard 17 18 import android.app.NotificationManager.zenModeFromInterruptionFilter 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.content.res.Resources 24 import android.os.Trace 25 import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS 26 import android.provider.Settings.Global.ZEN_MODE_OFF 27 import android.text.format.DateFormat 28 import android.util.Log 29 import android.util.TypedValue 30 import android.view.View 31 import android.view.View.OnAttachStateChangeListener 32 import android.view.ViewGroup 33 import android.view.ViewTreeObserver 34 import android.view.ViewTreeObserver.OnGlobalLayoutListener 35 import androidx.annotation.VisibleForTesting 36 import androidx.lifecycle.Lifecycle 37 import androidx.lifecycle.repeatOnLifecycle 38 import com.android.app.tracing.coroutines.launchTraced as launch 39 import com.android.systemui.broadcast.BroadcastDispatcher 40 import com.android.systemui.customization.R 41 import com.android.systemui.dagger.qualifiers.Background 42 import com.android.systemui.dagger.qualifiers.DisplaySpecific 43 import com.android.systemui.dagger.qualifiers.Main 44 import com.android.systemui.flags.FeatureFlagsClassic 45 import com.android.systemui.flags.Flags.REGION_SAMPLING 46 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 47 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 48 import com.android.systemui.keyguard.shared.model.Edge 49 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD 50 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING 51 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 52 import com.android.systemui.keyguard.shared.model.TransitionState 53 import com.android.systemui.lifecycle.repeatWhenAttached 54 import com.android.systemui.log.core.Logger 55 import com.android.systemui.modes.shared.ModesUi 56 import com.android.systemui.plugins.clocks.AlarmData 57 import com.android.systemui.plugins.clocks.ClockController 58 import com.android.systemui.plugins.clocks.ClockEventListener 59 import com.android.systemui.plugins.clocks.ClockFaceController 60 import com.android.systemui.plugins.clocks.ClockMessageBuffers 61 import com.android.systemui.plugins.clocks.ClockTickRate 62 import com.android.systemui.plugins.clocks.VRectF 63 import com.android.systemui.plugins.clocks.WeatherData 64 import com.android.systemui.plugins.clocks.ZenData 65 import com.android.systemui.plugins.clocks.ZenData.ZenMode 66 import com.android.systemui.res.R as SysuiR 67 import com.android.systemui.scene.shared.flag.SceneContainerFlag 68 import com.android.systemui.settings.UserTracker 69 import com.android.systemui.shared.regionsampling.RegionSampler 70 import com.android.systemui.statusbar.policy.BatteryController 71 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback 72 import com.android.systemui.statusbar.policy.ConfigurationController 73 import com.android.systemui.statusbar.policy.ZenModeController 74 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor 75 import com.android.systemui.util.annotations.DeprecatedSysuiVisibleForTesting 76 import com.android.systemui.util.concurrency.DelayableExecutor 77 import java.util.Locale 78 import java.util.TimeZone 79 import java.util.concurrent.Executor 80 import javax.inject.Inject 81 import kotlinx.coroutines.CoroutineScope 82 import kotlinx.coroutines.DisposableHandle 83 import kotlinx.coroutines.Job 84 import kotlinx.coroutines.flow.MutableStateFlow 85 import kotlinx.coroutines.flow.filter 86 import kotlinx.coroutines.flow.map 87 import kotlinx.coroutines.flow.merge 88 89 /** 90 * Controller for a Clock provided by the registry and used on the keyguard. Functionality is forked 91 * from [AnimatableClockController]. 92 */ 93 open class ClockEventController 94 @Inject 95 constructor( 96 private val keyguardInteractor: KeyguardInteractor, 97 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 98 private val broadcastDispatcher: BroadcastDispatcher, 99 private val batteryController: BatteryController, 100 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 101 // TODO b/362719719 - We should use the configuration controller associated with the display. 102 private val configurationController: ConfigurationController, 103 @DisplaySpecific private val resources: Resources, 104 @DisplaySpecific val context: Context, 105 @Main private val mainExecutor: DelayableExecutor, 106 @Background private val bgExecutor: Executor, 107 private val clockBuffers: ClockMessageBuffers, 108 private val featureFlags: FeatureFlagsClassic, 109 private val zenModeController: ZenModeController, 110 private val zenModeInteractor: ZenModeInteractor, 111 private val userTracker: UserTracker, 112 ) { 113 var loggers = 114 listOf( 115 clockBuffers.infraMessageBuffer, 116 clockBuffers.smallClockMessageBuffer, 117 clockBuffers.largeClockMessageBuffer, 118 ) 119 .map { Logger(it, TAG) } 120 121 var clock: ClockController? = null 122 get() = field 123 set(value) { 124 disconnectClock(field) 125 field = value 126 connectClock(value) 127 } 128 129 private fun is24HourFormat(userId: Int? = null): Boolean { 130 return DateFormat.is24HourFormat(context, userId ?: userTracker.userId) 131 } 132 133 private fun disconnectClock(clock: ClockController?) { 134 if (clock == null) { 135 return 136 } 137 smallClockOnAttachStateChangeListener?.let { 138 clock.smallClock.view.removeOnAttachStateChangeListener(it) 139 smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) 140 } 141 largeClockOnAttachStateChangeListener?.let { 142 clock.largeClock.view.removeOnAttachStateChangeListener(it) 143 } 144 } 145 146 private fun connectClock(clock: ClockController?) { 147 if (clock == null) { 148 return 149 } 150 val clockStr = clock.toString() 151 loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } } 152 153 clock.initialize(isDarkTheme(), dozeAmount.value, 0f, clockListener) 154 155 if (!regionSamplingEnabled) { 156 updateColors() 157 } else { 158 smallRegionSampler = 159 createRegionSampler( 160 clock.smallClock.view, 161 mainExecutor, 162 bgExecutor, 163 regionSamplingEnabled, 164 isLockscreen = true, 165 ::updateColors, 166 ) 167 .apply { startRegionSampler() } 168 169 largeRegionSampler = 170 createRegionSampler( 171 clock.largeClock.view, 172 mainExecutor, 173 bgExecutor, 174 regionSamplingEnabled, 175 isLockscreen = true, 176 ::updateColors, 177 ) 178 .apply { startRegionSampler() } 179 180 updateColors() 181 } 182 updateFontSizes() 183 updateTimeListeners() 184 185 weatherData?.let { 186 if (WeatherData.DEBUG) { 187 Log.i(TAG, "Pushing cached weather data to new clock: $it") 188 } 189 clock.events.onWeatherDataChanged(it) 190 } 191 zenData?.let { clock.events.onZenDataChanged(it) } 192 alarmData?.let { clock.events.onAlarmDataChanged(it) } 193 194 smallClockOnAttachStateChangeListener = 195 object : OnAttachStateChangeListener { 196 var pastVisibility: Int? = null 197 198 override fun onViewAttachedToWindow(view: View) { 199 clock.events.onTimeFormatChanged(is24HourFormat()) 200 // Match the asing for view.parent's layout classes. 201 smallClockFrame = 202 (view.parent as ViewGroup)?.also { frame -> 203 pastVisibility = frame.visibility 204 onGlobalLayoutListener = OnGlobalLayoutListener { 205 val currentVisibility = frame.visibility 206 if (pastVisibility != currentVisibility) { 207 pastVisibility = currentVisibility 208 // when small clock is visible, 209 // recalculate bounds and sample 210 if (currentVisibility == View.VISIBLE) { 211 smallRegionSampler?.stopRegionSampler() 212 smallRegionSampler?.startRegionSampler() 213 } 214 } 215 } 216 frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener) 217 } 218 } 219 220 override fun onViewDetachedFromWindow(p0: View) { 221 smallClockFrame 222 ?.viewTreeObserver 223 ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) 224 } 225 } 226 clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) 227 228 largeClockOnAttachStateChangeListener = 229 object : OnAttachStateChangeListener { 230 override fun onViewAttachedToWindow(p0: View) { 231 clock.events.onTimeFormatChanged(is24HourFormat()) 232 } 233 234 override fun onViewDetachedFromWindow(p0: View) {} 235 } 236 clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) 237 } 238 239 @VisibleForTesting 240 var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null 241 @VisibleForTesting 242 var largeClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null 243 private var smallClockFrame: ViewGroup? = null 244 private var onGlobalLayoutListener: OnGlobalLayoutListener? = null 245 246 private var isCharging = false 247 private var isKeyguardVisible = false 248 private var isRegistered = false 249 private var disposableHandle: DisposableHandle? = null 250 private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) 251 private var largeClockOnSecondaryDisplay = false 252 253 val dozeAmount = MutableStateFlow(0f) 254 val onClockBoundsChanged = MutableStateFlow<VRectF>(VRectF.ZERO) 255 256 private fun isDarkTheme(): Boolean { 257 val isLightTheme = TypedValue() 258 context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true) 259 return isLightTheme.data == 0 260 } 261 262 private fun updateColors() { 263 val isDarkTheme = isDarkTheme() 264 if (regionSamplingEnabled) { 265 clock?.smallClock?.run { 266 val isDark = smallRegionSampler?.currentRegionDarkness()?.isDark ?: isDarkTheme 267 events.onThemeChanged(theme.copy(isDarkTheme = isDark)) 268 } 269 clock?.largeClock?.run { 270 val isDark = largeRegionSampler?.currentRegionDarkness()?.isDark ?: isDarkTheme 271 events.onThemeChanged(theme.copy(isDarkTheme = isDark)) 272 } 273 return 274 } 275 276 clock?.run { 277 Log.i(TAG, "isThemeDark: $isDarkTheme") 278 smallClock.events.onThemeChanged(smallClock.theme.copy(isDarkTheme = isDarkTheme)) 279 largeClock.events.onThemeChanged(largeClock.theme.copy(isDarkTheme = isDarkTheme)) 280 } 281 } 282 283 protected open fun createRegionSampler( 284 sampledView: View, 285 mainExecutor: Executor?, 286 bgExecutor: Executor?, 287 regionSamplingEnabled: Boolean, 288 isLockscreen: Boolean, 289 updateColors: () -> Unit, 290 ): RegionSampler { 291 return RegionSampler( 292 sampledView, 293 mainExecutor, 294 bgExecutor, 295 regionSamplingEnabled, 296 isLockscreen, 297 ) { 298 updateColors() 299 } 300 } 301 302 var smallRegionSampler: RegionSampler? = null 303 private set 304 305 var largeRegionSampler: RegionSampler? = null 306 private set 307 308 var smallTimeListener: TimeListener? = null 309 var largeTimeListener: TimeListener? = null 310 val shouldTimeListenerRun: Boolean 311 get() = isKeyguardVisible && dozeAmount.value < DOZE_TICKRATE_THRESHOLD 312 313 private var weatherData: WeatherData? = null 314 private var zenData: ZenData? = null 315 private var alarmData: AlarmData? = null 316 317 private val clockListener = 318 object : ClockEventListener { 319 override fun onBoundsChanged(bounds: VRectF) { 320 onClockBoundsChanged.value = bounds 321 } 322 } 323 324 private val configListener = 325 object : ConfigurationController.ConfigurationListener { 326 override fun onThemeChanged() { 327 updateColors() 328 } 329 330 override fun onDensityOrFontScaleChanged() { 331 updateFontSizes() 332 } 333 } 334 335 private val batteryCallback = 336 object : BatteryStateChangeCallback { 337 override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { 338 if (isKeyguardVisible && !isCharging && charging) { 339 clock?.run { 340 smallClock.animations.charge() 341 largeClock.animations.charge() 342 } 343 } 344 isCharging = charging 345 } 346 } 347 348 private val localeBroadcastReceiver = 349 object : BroadcastReceiver() { 350 override fun onReceive(context: Context, intent: Intent) { 351 clock?.run { events.onLocaleChanged(Locale.getDefault()) } 352 } 353 } 354 355 private val keyguardUpdateMonitorCallback = 356 object : KeyguardUpdateMonitorCallback() { 357 override fun onKeyguardVisibilityChanged(visible: Boolean) { 358 isKeyguardVisible = visible 359 360 if (visible) { 361 refreshTime() 362 } 363 364 smallTimeListener?.update(shouldTimeListenerRun) 365 largeTimeListener?.update(shouldTimeListenerRun) 366 } 367 368 override fun onTimeFormatChanged(timeFormat: String?) { 369 clock?.run { events.onTimeFormatChanged(is24HourFormat()) } 370 } 371 372 override fun onTimeZoneChanged(timeZone: TimeZone) { 373 clock?.run { events.onTimeZoneChanged(timeZone) } 374 } 375 376 override fun onUserSwitchComplete(userId: Int) { 377 clock?.run { events.onTimeFormatChanged(is24HourFormat(userId)) } 378 zenModeCallback.onNextAlarmChanged() 379 } 380 381 override fun onWeatherDataChanged(data: WeatherData) { 382 weatherData = data 383 clock?.run { events.onWeatherDataChanged(data) } 384 } 385 386 override fun onTimeChanged() { 387 refreshTime() 388 } 389 390 private fun refreshTime() { 391 clock?.smallClock?.events?.onTimeTick() 392 clock?.largeClock?.events?.onTimeTick() 393 } 394 } 395 396 @DeprecatedSysuiVisibleForTesting 397 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 398 fun listenForDnd(scope: CoroutineScope): Job { 399 ModesUi.unsafeAssertInNewMode() 400 return scope.launch { 401 zenModeInteractor.dndMode.collect { 402 val zenMode = 403 if (it != null && it.isActive) 404 zenModeFromInterruptionFilter( 405 it.interruptionFilter, 406 ZEN_MODE_IMPORTANT_INTERRUPTIONS, 407 ) 408 else ZEN_MODE_OFF 409 410 handleZenMode(zenMode) 411 } 412 } 413 } 414 415 private val zenModeCallback = 416 object : ZenModeController.Callback { 417 override fun onZenChanged(zen: Int) { 418 if (!ModesUi.isEnabled) { 419 handleZenMode(zen) 420 } 421 } 422 423 override fun onNextAlarmChanged() { 424 val nextAlarmMillis = zenModeController.getNextAlarm() 425 alarmData = 426 AlarmData( 427 if (nextAlarmMillis > 0) nextAlarmMillis else null, 428 SysuiR.string::status_bar_alarm.name, 429 ) 430 .also { data -> 431 mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } } 432 } 433 } 434 } 435 436 private fun handleZenMode(zen: Int) { 437 val mode = ZenMode.fromInt(zen) 438 if (mode == null) { 439 Log.e(TAG, "Failed to get zen mode from int: $zen") 440 return 441 } 442 443 zenData = 444 ZenData( 445 mode, 446 if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name 447 else SysuiR.string::dnd_is_on.name, 448 ) 449 .also { data -> 450 mainExecutor.execute { clock?.run { events.onZenDataChanged(data) } } 451 } 452 } 453 454 fun registerListeners(parent: View) { 455 if (isRegistered) { 456 return 457 } 458 isRegistered = true 459 broadcastDispatcher.registerReceiver( 460 localeBroadcastReceiver, 461 IntentFilter(Intent.ACTION_LOCALE_CHANGED), 462 ) 463 configurationController.addCallback(configListener) 464 batteryController.addCallback(batteryCallback) 465 keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) 466 zenModeController.addCallback(zenModeCallback) 467 if (SceneContainerFlag.isEnabled) { 468 handleDoze( 469 when (AOD) { 470 keyguardTransitionInteractor.getCurrentState() -> 1f 471 keyguardTransitionInteractor.getStartedState() -> 1f 472 else -> 0f 473 } 474 ) 475 } 476 disposableHandle = 477 parent.repeatWhenAttached { 478 repeatOnLifecycle(Lifecycle.State.CREATED) { 479 if (ModesUi.isEnabled) { 480 listenForDnd(this) 481 } 482 listenForDozeAmountTransition(this) 483 listenForAnyStateToAodTransition(this) 484 listenForAnyStateToLockscreenTransition(this) 485 listenForAnyStateToDozingTransition(this) 486 } 487 } 488 smallTimeListener?.update(shouldTimeListenerRun) 489 largeTimeListener?.update(shouldTimeListenerRun) 490 491 bgExecutor.execute { 492 // Query ZenMode data 493 if (!ModesUi.isEnabled) { 494 zenModeCallback.onZenChanged(zenModeController.zen) 495 } 496 zenModeCallback.onNextAlarmChanged() 497 } 498 } 499 500 fun unregisterListeners() { 501 if (!isRegistered) { 502 return 503 } 504 isRegistered = false 505 506 disposableHandle?.dispose() 507 broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver) 508 configurationController.removeCallback(configListener) 509 batteryController.removeCallback(batteryCallback) 510 keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) 511 zenModeController.removeCallback(zenModeCallback) 512 smallRegionSampler?.stopRegionSampler() 513 largeRegionSampler?.stopRegionSampler() 514 smallTimeListener?.stop() 515 largeTimeListener?.stop() 516 clock?.run { 517 smallClock.view.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) 518 largeClock.view.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) 519 } 520 smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) 521 } 522 523 fun setFallbackWeatherData(data: WeatherData) { 524 if (weatherData != null) return 525 weatherData = data 526 clock?.run { events.onWeatherDataChanged(data) } 527 } 528 529 /** 530 * Sets this clock as showing in a secondary display. 531 * 532 * Not that this is not necessarily needed, as we could get the displayId from [Context] 533 * directly and infere [largeClockOnSecondaryDisplay] from the id being different than the 534 * default display one. However, if we do so, current screenshot tests would not work, as they 535 * pass an activity context always from the default display. 536 */ 537 fun setLargeClockOnSecondaryDisplay(onSecondaryDisplay: Boolean) { 538 largeClockOnSecondaryDisplay = onSecondaryDisplay 539 updateFontSizes() 540 } 541 542 private fun updateTimeListeners() { 543 smallTimeListener?.stop() 544 largeTimeListener?.stop() 545 546 smallTimeListener = null 547 largeTimeListener = null 548 549 clock?.let { 550 smallTimeListener = 551 TimeListener(it.smallClock, mainExecutor).apply { update(shouldTimeListenerRun) } 552 largeTimeListener = 553 TimeListener(it.largeClock, mainExecutor).apply { update(shouldTimeListenerRun) } 554 } 555 } 556 557 fun updateFontSizes() { 558 clock?.run { 559 smallClock.events.onFontSettingChanged(getSmallClockSizePx()) 560 largeClock.events.onFontSettingChanged(getLargeClockSizePx()) 561 } 562 } 563 564 private fun getSmallClockSizePx(): Float { 565 return resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() 566 } 567 568 private fun getLargeClockSizePx(): Float { 569 return if (largeClockOnSecondaryDisplay) { 570 resources.getDimensionPixelSize(R.dimen.presentation_clock_text_size).toFloat() 571 } else { 572 resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() 573 } 574 } 575 576 fun handleFidgetTap(x: Float, y: Float) { 577 clock?.run { 578 smallClock.animations.onFidgetTap(x, y) 579 largeClock.animations.onFidgetTap(x, y) 580 } 581 } 582 583 private fun handleDoze(doze: Float) { 584 clock?.run { 585 Trace.beginSection("$TAG#smallClock.animations.doze") 586 smallClock.animations.doze(doze) 587 Trace.endSection() 588 Trace.beginSection("$TAG#largeClock.animations.doze") 589 largeClock.animations.doze(doze) 590 Trace.endSection() 591 } 592 smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD) 593 largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD) 594 dozeAmount.value = doze 595 } 596 597 @DeprecatedSysuiVisibleForTesting 598 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 599 fun listenForDozeAmountTransition(scope: CoroutineScope): Job { 600 return scope.launch { 601 merge( 602 keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)).map { 603 it.copy(value = 1f - it.value) 604 }, 605 keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)), 606 ) 607 .filter { it.transitionState != TransitionState.FINISHED } 608 .collect { handleDoze(it.value) } 609 } 610 } 611 612 /** 613 * When keyguard is displayed again after being gone, the clock must be reset to full dozing. 614 */ 615 @DeprecatedSysuiVisibleForTesting 616 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 617 fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { 618 return scope.launch { 619 keyguardTransitionInteractor 620 .transition(Edge.create(to = AOD)) 621 .filter { it.transitionState == TransitionState.STARTED } 622 .filter { it.from != LOCKSCREEN } 623 .collect { handleDoze(1f) } 624 } 625 } 626 627 @DeprecatedSysuiVisibleForTesting 628 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 629 fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { 630 return scope.launch { 631 keyguardTransitionInteractor 632 .transition(Edge.create(to = LOCKSCREEN)) 633 .filter { it.transitionState == TransitionState.STARTED } 634 .filter { it.from != AOD } 635 .collect { handleDoze(0f) } 636 } 637 } 638 639 /** 640 * When keyguard is displayed due to pulsing notifications when AOD is off, we should make sure 641 * clock is in dozing state instead of LS state 642 */ 643 @DeprecatedSysuiVisibleForTesting 644 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 645 fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job { 646 return scope.launch { 647 keyguardTransitionInteractor 648 .transition(Edge.create(to = DOZING)) 649 .filter { it.transitionState == TransitionState.FINISHED } 650 .collect { handleDoze(1f) } 651 } 652 } 653 654 class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) { 655 val predrawListener = 656 ViewTreeObserver.OnPreDrawListener { 657 clockFace.events.onTimeTick() 658 true 659 } 660 661 val secondsRunnable = 662 object : Runnable { 663 override fun run() { 664 if (!isRunning) { 665 return 666 } 667 668 executor.executeDelayed(this, 990) 669 clockFace.events.onTimeTick() 670 } 671 } 672 673 var isRunning: Boolean = false 674 private set 675 676 fun start() { 677 if (isRunning) { 678 return 679 } 680 681 isRunning = true 682 when (clockFace.config.tickRate) { 683 ClockTickRate.PER_MINUTE -> { 684 // Handled by KeyguardUpdateMonitorCallback#onTimeChanged. 685 } 686 ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable) 687 ClockTickRate.PER_FRAME -> { 688 clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener) 689 clockFace.view.invalidate() 690 } 691 } 692 } 693 694 fun stop() { 695 if (!isRunning) { 696 return 697 } 698 699 isRunning = false 700 clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener) 701 } 702 703 fun update(shouldRun: Boolean) = if (shouldRun) start() else stop() 704 } 705 706 companion object { 707 private const val TAG = "ClockEventController" 708 private const val DOZE_TICKRATE_THRESHOLD = 0.99f 709 } 710 } 711