• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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