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