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.content.BroadcastReceiver 19 import android.content.Context 20 import android.content.Intent 21 import android.content.IntentFilter 22 import android.content.res.Resources 23 import android.text.format.DateFormat 24 import android.util.Log 25 import android.util.TypedValue 26 import android.view.View 27 import android.view.View.OnAttachStateChangeListener 28 import android.view.ViewTreeObserver 29 import android.view.ViewTreeObserver.OnGlobalLayoutListener 30 import android.widget.FrameLayout 31 import androidx.annotation.VisibleForTesting 32 import androidx.lifecycle.Lifecycle 33 import androidx.lifecycle.repeatOnLifecycle 34 import com.android.systemui.R 35 import com.android.systemui.broadcast.BroadcastDispatcher 36 import com.android.systemui.dagger.qualifiers.Background 37 import com.android.systemui.dagger.qualifiers.Main 38 import com.android.systemui.flags.FeatureFlags 39 import com.android.systemui.flags.Flags.DOZING_MIGRATION_1 40 import com.android.systemui.flags.Flags.REGION_SAMPLING 41 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 42 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 43 import com.android.systemui.keyguard.shared.model.TransitionState 44 import com.android.systemui.lifecycle.repeatWhenAttached 45 import com.android.systemui.log.LogBuffer 46 import com.android.systemui.log.core.LogLevel.DEBUG 47 import com.android.systemui.log.dagger.KeyguardLargeClockLog 48 import com.android.systemui.log.dagger.KeyguardSmallClockLog 49 import com.android.systemui.plugins.ClockController 50 import com.android.systemui.plugins.ClockFaceController 51 import com.android.systemui.plugins.ClockTickRate 52 import com.android.systemui.plugins.WeatherData 53 import com.android.systemui.shared.regionsampling.RegionSampler 54 import com.android.systemui.statusbar.policy.BatteryController 55 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback 56 import com.android.systemui.statusbar.policy.ConfigurationController 57 import com.android.systemui.util.concurrency.DelayableExecutor 58 import kotlinx.coroutines.CoroutineScope 59 import kotlinx.coroutines.DisposableHandle 60 import kotlinx.coroutines.Job 61 import kotlinx.coroutines.flow.combine 62 import kotlinx.coroutines.flow.filter 63 import kotlinx.coroutines.launch 64 import java.util.Locale 65 import java.util.TimeZone 66 import java.util.concurrent.Executor 67 import javax.inject.Inject 68 69 /** 70 * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by 71 * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. 72 */ 73 open class ClockEventController 74 @Inject 75 constructor( 76 private val keyguardInteractor: KeyguardInteractor, 77 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 78 private val broadcastDispatcher: BroadcastDispatcher, 79 private val batteryController: BatteryController, 80 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 81 private val configurationController: ConfigurationController, 82 @Main private val resources: Resources, 83 private val context: Context, 84 @Main private val mainExecutor: DelayableExecutor, 85 @Background private val bgExecutor: Executor, 86 @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?, 87 @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?, 88 private val featureFlags: FeatureFlags 89 ) { 90 var clock: ClockController? = null 91 set(value) { 92 smallClockOnAttachStateChangeListener?.let { 93 field?.smallClock?.view?.removeOnAttachStateChangeListener(it) 94 smallClockFrame?.viewTreeObserver 95 ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) 96 } 97 largeClockOnAttachStateChangeListener?.let { 98 field?.largeClock?.view?.removeOnAttachStateChangeListener(it) 99 } 100 101 field = value 102 if (value != null) { 103 smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) 104 value.smallClock.messageBuffer = smallLogBuffer 105 largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) 106 value.largeClock.messageBuffer = largeLogBuffer 107 108 value.initialize(resources, dozeAmount, 0f) 109 110 if (!regionSamplingEnabled) { 111 updateColors() 112 } else { 113 clock?.let { 114 smallRegionSampler = createRegionSampler( 115 it.smallClock.view, 116 mainExecutor, 117 bgExecutor, 118 regionSamplingEnabled, 119 isLockscreen = true, 120 ::updateColors 121 )?.apply { startRegionSampler() } 122 123 largeRegionSampler = createRegionSampler( 124 it.largeClock.view, 125 mainExecutor, 126 bgExecutor, 127 regionSamplingEnabled, 128 isLockscreen = true, 129 ::updateColors 130 )?.apply { startRegionSampler() } 131 132 updateColors() 133 } 134 } 135 updateFontSizes() 136 updateTimeListeners() 137 cachedWeatherData?.let { 138 if (WeatherData.DEBUG) { 139 Log.i(TAG, "Pushing cached weather data to new clock: $it") 140 } 141 value.events.onWeatherDataChanged(it) 142 } 143 144 smallClockOnAttachStateChangeListener = 145 object : OnAttachStateChangeListener { 146 var pastVisibility: Int? = null 147 override fun onViewAttachedToWindow(view: View) { 148 value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) 149 if (view != null) { 150 smallClockFrame = view.parent as FrameLayout 151 smallClockFrame?.let {frame -> 152 pastVisibility = frame.visibility 153 onGlobalLayoutListener = OnGlobalLayoutListener { 154 val currentVisibility = frame.visibility 155 if (pastVisibility != currentVisibility) { 156 pastVisibility = currentVisibility 157 // when small clock visible, 158 // recalculate bounds and sample 159 if (currentVisibility == View.VISIBLE) { 160 smallRegionSampler?.stopRegionSampler() 161 smallRegionSampler?.startRegionSampler() 162 } 163 } 164 } 165 frame.viewTreeObserver 166 .addOnGlobalLayoutListener(onGlobalLayoutListener) 167 } 168 } 169 } 170 171 override fun onViewDetachedFromWindow(p0: View) { 172 smallClockFrame?.viewTreeObserver 173 ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) 174 } 175 } 176 value.smallClock.view 177 .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) 178 179 largeClockOnAttachStateChangeListener = 180 object : OnAttachStateChangeListener { 181 override fun onViewAttachedToWindow(p0: View) { 182 value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) 183 } 184 override fun onViewDetachedFromWindow(p0: View) { 185 } 186 } 187 value.largeClock.view 188 .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) 189 } 190 } 191 192 @VisibleForTesting 193 var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null 194 @VisibleForTesting 195 var largeClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null 196 private var smallClockFrame: FrameLayout? = null 197 private var onGlobalLayoutListener: OnGlobalLayoutListener? = null 198 199 private var isDozing = false 200 private set 201 202 private var isCharging = false 203 private var dozeAmount = 0f 204 private var isKeyguardVisible = false 205 private var isRegistered = false 206 private var disposableHandle: DisposableHandle? = null 207 private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) 208 209 210 private fun updateColors() { 211 if (regionSamplingEnabled) { 212 clock?.let { clock -> 213 smallRegionSampler?.let { 214 smallClockIsDark = it.currentRegionDarkness().isDark 215 clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark) 216 } 217 218 largeRegionSampler?.let { 219 largeClockIsDark = it.currentRegionDarkness().isDark 220 clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark) 221 } 222 } 223 return 224 } 225 226 val isLightTheme = TypedValue() 227 context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true) 228 smallClockIsDark = isLightTheme.data == 0 229 largeClockIsDark = isLightTheme.data == 0 230 231 clock?.run { 232 smallClock.events.onRegionDarknessChanged(smallClockIsDark) 233 largeClock.events.onRegionDarknessChanged(largeClockIsDark) 234 } 235 } 236 protected open fun createRegionSampler( 237 sampledView: View, 238 mainExecutor: Executor?, 239 bgExecutor: Executor?, 240 regionSamplingEnabled: Boolean, 241 isLockscreen: Boolean, 242 updateColors: () -> Unit 243 ): RegionSampler? { 244 return RegionSampler( 245 sampledView, 246 mainExecutor, 247 bgExecutor, 248 regionSamplingEnabled, 249 isLockscreen, 250 ) { updateColors() } 251 } 252 253 var smallRegionSampler: RegionSampler? = null 254 private set 255 var largeRegionSampler: RegionSampler? = null 256 private set 257 var smallTimeListener: TimeListener? = null 258 var largeTimeListener: TimeListener? = null 259 val shouldTimeListenerRun: Boolean 260 get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD 261 private var cachedWeatherData: WeatherData? = null 262 263 private var smallClockIsDark = true 264 private var largeClockIsDark = true 265 266 private val configListener = 267 object : ConfigurationController.ConfigurationListener { 268 override fun onThemeChanged() { 269 clock?.run { events.onColorPaletteChanged(resources) } 270 updateColors() 271 } 272 273 override fun onDensityOrFontScaleChanged() { 274 updateFontSizes() 275 } 276 } 277 278 private val batteryCallback = 279 object : BatteryStateChangeCallback { 280 override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { 281 if (isKeyguardVisible && !isCharging && charging) { 282 clock?.run { 283 smallClock.animations.charge() 284 largeClock.animations.charge() 285 } 286 } 287 isCharging = charging 288 } 289 } 290 291 private val localeBroadcastReceiver = 292 object : BroadcastReceiver() { 293 override fun onReceive(context: Context, intent: Intent) { 294 clock?.run { events.onLocaleChanged(Locale.getDefault()) } 295 } 296 } 297 298 private val keyguardUpdateMonitorCallback = 299 object : KeyguardUpdateMonitorCallback() { 300 override fun onKeyguardVisibilityChanged(visible: Boolean) { 301 isKeyguardVisible = visible 302 if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { 303 if (!isKeyguardVisible) { 304 clock?.run { 305 smallClock.animations.doze(if (isDozing) 1f else 0f) 306 largeClock.animations.doze(if (isDozing) 1f else 0f) 307 } 308 } 309 } 310 311 smallTimeListener?.update(shouldTimeListenerRun) 312 largeTimeListener?.update(shouldTimeListenerRun) 313 } 314 315 override fun onTimeFormatChanged(timeFormat: String?) { 316 clock?.run { events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } 317 } 318 319 override fun onTimeZoneChanged(timeZone: TimeZone) { 320 clock?.run { events.onTimeZoneChanged(timeZone) } 321 } 322 323 override fun onUserSwitchComplete(userId: Int) { 324 clock?.run { events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } 325 } 326 327 override fun onWeatherDataChanged(data: WeatherData) { 328 cachedWeatherData = data 329 clock?.run { events.onWeatherDataChanged(data) } 330 } 331 } 332 333 fun registerListeners(parent: View) { 334 if (isRegistered) { 335 return 336 } 337 isRegistered = true 338 broadcastDispatcher.registerReceiver( 339 localeBroadcastReceiver, 340 IntentFilter(Intent.ACTION_LOCALE_CHANGED) 341 ) 342 configurationController.addCallback(configListener) 343 batteryController.addCallback(batteryCallback) 344 keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) 345 disposableHandle = 346 parent.repeatWhenAttached { 347 repeatOnLifecycle(Lifecycle.State.STARTED) { 348 listenForDozing(this) 349 if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { 350 listenForDozeAmountTransition(this) 351 listenForAnyStateToAodTransition(this) 352 } else { 353 listenForDozeAmount(this) 354 } 355 } 356 } 357 smallTimeListener?.update(shouldTimeListenerRun) 358 largeTimeListener?.update(shouldTimeListenerRun) 359 } 360 361 fun unregisterListeners() { 362 if (!isRegistered) { 363 return 364 } 365 isRegistered = false 366 367 disposableHandle?.dispose() 368 broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver) 369 configurationController.removeCallback(configListener) 370 batteryController.removeCallback(batteryCallback) 371 keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) 372 smallRegionSampler?.stopRegionSampler() 373 largeRegionSampler?.stopRegionSampler() 374 smallTimeListener?.stop() 375 largeTimeListener?.stop() 376 clock?.smallClock?.view 377 ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) 378 smallClockFrame?.viewTreeObserver 379 ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) 380 clock?.largeClock?.view 381 ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) 382 } 383 384 private fun updateTimeListeners() { 385 smallTimeListener?.stop() 386 largeTimeListener?.stop() 387 388 smallTimeListener = null 389 largeTimeListener = null 390 391 clock?.let { 392 smallTimeListener = TimeListener(it.smallClock, mainExecutor).apply { 393 update(shouldTimeListenerRun) 394 } 395 largeTimeListener = TimeListener(it.largeClock, mainExecutor).apply { 396 update(shouldTimeListenerRun) 397 } 398 } 399 } 400 401 private fun updateFontSizes() { 402 clock?.run { 403 smallClock.events.onFontSettingChanged( 404 resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() 405 ) 406 largeClock.events.onFontSettingChanged( 407 resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() 408 ) 409 } 410 } 411 412 private fun handleDoze(doze: Float) { 413 dozeAmount = doze 414 clock?.run { 415 smallClock.animations.doze(dozeAmount) 416 largeClock.animations.doze(dozeAmount) 417 } 418 smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD) 419 largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD) 420 } 421 422 @VisibleForTesting 423 internal fun listenForDozeAmount(scope: CoroutineScope): Job { 424 return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } } 425 } 426 427 @VisibleForTesting 428 internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { 429 return scope.launch { 430 keyguardTransitionInteractor.dozeAmountTransition.collect { handleDoze(it.value) } 431 } 432 } 433 434 /** 435 * When keyguard is displayed again after being gone, the clock must be reset to full dozing. 436 */ 437 @VisibleForTesting 438 internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { 439 return scope.launch { 440 keyguardTransitionInteractor.anyStateToAodTransition 441 .filter { it.transitionState == TransitionState.FINISHED } 442 .collect { handleDoze(1f) } 443 } 444 } 445 446 @VisibleForTesting 447 internal fun listenForDozing(scope: CoroutineScope): Job { 448 return scope.launch { 449 combine( 450 keyguardInteractor.dozeAmount, 451 keyguardInteractor.isDozing, 452 ) { localDozeAmount, localIsDozing -> 453 localDozeAmount > dozeAmount || localIsDozing 454 } 455 .collect { localIsDozing -> isDozing = localIsDozing } 456 } 457 } 458 459 class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) { 460 val predrawListener = 461 ViewTreeObserver.OnPreDrawListener { 462 clockFace.events.onTimeTick() 463 true 464 } 465 466 val secondsRunnable = 467 object : Runnable { 468 override fun run() { 469 if (!isRunning) { 470 return 471 } 472 473 executor.executeDelayed(this, 990) 474 clockFace.events.onTimeTick() 475 } 476 } 477 478 var isRunning: Boolean = false 479 private set 480 481 fun start() { 482 if (isRunning) { 483 return 484 } 485 486 isRunning = true 487 when (clockFace.config.tickRate) { 488 ClockTickRate.PER_MINUTE -> { 489 /* Handled by KeyguardClockSwitchController */ 490 } 491 ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable) 492 ClockTickRate.PER_FRAME -> { 493 clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener) 494 clockFace.view.invalidate() 495 } 496 } 497 } 498 499 fun stop() { 500 if (!isRunning) { 501 return 502 } 503 504 isRunning = false 505 clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener) 506 } 507 508 fun update(shouldRun: Boolean) = if (shouldRun) start() else stop() 509 } 510 511 companion object { 512 private val TAG = ClockEventController::class.simpleName!! 513 private val DOZE_TICKRATE_THRESHOLD = 0.99f 514 } 515 } 516