• 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.systemui.keyguard.domain.interactor
17 
18 import android.app.StatusBarManager
19 import android.graphics.Point
20 import android.util.Log
21 import android.util.MathUtils
22 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
23 import com.android.systemui.common.shared.model.NotificationContainerBounds
24 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
25 import com.android.systemui.dagger.SysUISingleton
26 import com.android.systemui.dagger.qualifiers.Application
27 import com.android.systemui.keyguard.data.repository.KeyguardRepository
28 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
29 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
30 import com.android.systemui.keyguard.shared.model.CameraLaunchType
31 import com.android.systemui.keyguard.shared.model.DozeStateModel
32 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
33 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
34 import com.android.systemui.keyguard.shared.model.Edge
35 import com.android.systemui.keyguard.shared.model.KeyguardState
36 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
37 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
38 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
39 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
40 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
41 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
42 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
43 import com.android.systemui.keyguard.shared.model.StatusBarState
44 import com.android.systemui.log.table.TableLogBuffer
45 import com.android.systemui.log.table.logDiffsForTable
46 import com.android.systemui.res.R
47 import com.android.systemui.scene.domain.interactor.SceneInteractor
48 import com.android.systemui.scene.shared.flag.SceneContainerFlag
49 import com.android.systemui.scene.shared.model.Scenes
50 import com.android.systemui.shade.ShadeDisplayAware
51 import com.android.systemui.shade.data.repository.ShadeRepository
52 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
53 import com.android.systemui.util.kotlin.sample
54 import com.android.systemui.wallpapers.data.repository.WallpaperFocalAreaRepository
55 import javax.inject.Inject
56 import javax.inject.Provider
57 import kotlinx.coroutines.CoroutineScope
58 import kotlinx.coroutines.FlowPreview
59 import kotlinx.coroutines.delay
60 import kotlinx.coroutines.flow.Flow
61 import kotlinx.coroutines.flow.MutableStateFlow
62 import kotlinx.coroutines.flow.SharingStarted
63 import kotlinx.coroutines.flow.StateFlow
64 import kotlinx.coroutines.flow.asStateFlow
65 import kotlinx.coroutines.flow.collect
66 import kotlinx.coroutines.flow.combine
67 import kotlinx.coroutines.flow.combineTransform
68 import kotlinx.coroutines.flow.debounce
69 import kotlinx.coroutines.flow.distinctUntilChanged
70 import kotlinx.coroutines.flow.filter
71 import kotlinx.coroutines.flow.flatMapLatest
72 import kotlinx.coroutines.flow.flowOf
73 import kotlinx.coroutines.flow.map
74 import kotlinx.coroutines.flow.merge
75 import kotlinx.coroutines.flow.onStart
76 import kotlinx.coroutines.flow.stateIn
77 import kotlinx.coroutines.flow.transform
78 
79 /**
80  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
81  */
82 @SysUISingleton
83 class KeyguardInteractor
84 @Inject
85 constructor(
86     private val repository: KeyguardRepository,
87     bouncerRepository: KeyguardBouncerRepository,
88     private val wallpaperFocalAreaRepository: WallpaperFocalAreaRepository,
89     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
90     shadeRepository: ShadeRepository,
91     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
92     sceneInteractorProvider: Provider<SceneInteractor>,
93     private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
94     private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>,
95     private val fromOccludedTransitionInteractor: Provider<FromOccludedTransitionInteractor>,
96     private val fromAlternateBouncerTransitionInteractor:
97         Provider<FromAlternateBouncerTransitionInteractor>,
98     @Application applicationScope: CoroutineScope,
99 ) {
100     // TODO(b/296118689): move to a repository
101     private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
102 
103     /** Bounds of the notification container. */
104     val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
105         SceneContainerFlag.assertInLegacyMode()
106         combineTransform(
107                 _notificationPlaceholderBounds,
108                 keyguardTransitionInteractor.isInTransition(
109                     edge = Edge.create(from = LOCKSCREEN, to = AOD)
110                 ),
111                 shadeRepository.isShadeLayoutWide,
112                 configurationInteractor.dimensionPixelSize(R.dimen.keyguard_split_shade_top_margin),
113             ) { bounds, isTransitioningToAod, useSplitShade, keyguardSplitShadeTopMargin ->
114                 if (isTransitioningToAod) {
115                     // Keep bounds stable during this transition, to prevent cases like smartspace
116                     // popping in and adjusting the bounds. A prime example would be media playing,
117                     // which then updates smartspace on transition to AOD.
118                     return@combineTransform
119                 }
120 
121                 // We offset the placeholder bounds by the configured top margin to account for
122                 // legacy placement behavior within notifications for splitshade.
123                 emit(
124                     if (useSplitShade) {
125                         bounds.copy(bottom = bounds.bottom - keyguardSplitShadeTopMargin)
126                     } else {
127                         bounds
128                     }
129                 )
130             }
131             .stateIn(
132                 scope = applicationScope,
133                 started = SharingStarted.WhileSubscribed(),
134                 initialValue = NotificationContainerBounds(),
135             )
136     }
137 
138     fun setNotificationContainerBounds(position: NotificationContainerBounds) {
139         SceneContainerFlag.assertInLegacyMode()
140         _notificationPlaceholderBounds.value = position
141     }
142 
143     /** Whether the system is in doze mode. */
144     val isDozing: StateFlow<Boolean> = repository.isDozing
145 
146     /** Receive an event for doze time tick */
147     val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
148 
149     /** Whether Always-on Display mode is available. */
150     val isAodAvailable: StateFlow<Boolean> = repository.isAodAvailable
151 
152     /**
153      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
154      * all.
155      */
156     val dozeAmount: Flow<Float> =
157         if (SceneContainerFlag.isEnabled) {
158             isAodAvailable.flatMapLatest { isAodAvailable ->
159                 if (isAodAvailable) {
160                     keyguardTransitionInteractor.transitionValue(AOD)
161                 } else {
162                     keyguardTransitionInteractor.transitionValue(DOZING)
163                 }
164             }
165         } else {
166             repository.linearDozeAmount
167         }
168 
169     /** Doze transition information. */
170     val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
171 
172     val isPulsing: Flow<Boolean> = dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }
173 
174     /** Whether the system is dreaming with an overlay active */
175     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
176 
177     /**
178      * Whether the system is dreaming. [KeyguardRepository.isDreaming] will be always be true when
179      * [isDozing] is true, but not vice-versa. Also accounts for [isDreamingWithOverlay].
180      */
181     val isDreaming: StateFlow<Boolean> =
182         merge(repository.isDreaming, repository.isDreamingWithOverlay)
183             .stateIn(
184                 scope = applicationScope,
185                 started = SharingStarted.Eagerly,
186                 initialValue = false,
187             )
188 
189     /** Whether any dreaming is running, including the doze dream. */
190     val isDreamingAny: Flow<Boolean> = repository.isDreaming
191 
192     /** Event for when the camera gesture is detected */
193     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
194         repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
195 
196     /** Event for when an unlocked keyguard has been requested, such as on device fold */
197     val showDismissibleKeyguard: Flow<Long> = repository.showDismissibleKeyguard.asStateFlow()
198 
199     /**
200      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
201      * that doze mode is not running and DREAMING is ok to commence.
202      *
203      * Allow a brief moment to prevent rapidly oscillating between true/false signals. The amount of
204      * time is [IS_ABLE_TO_DREAM_DELAY_MS] - consumers should consider waiting for that long before
205      * examining the value of this flow, to let other consumers have enough time to also see that
206      * same new value.
207      */
208     @OptIn(FlowPreview::class)
209     val isAbleToDream: Flow<Boolean> =
210         dozeTransitionModel
211             .flatMapLatest { dozeTransitionModel ->
212                 if (isDozeOff(dozeTransitionModel.to)) {
213                     // When dozing stops, it is a very early signal that the device is exiting the
214                     // dream state. DreamManagerService eventually notifies window manager, which
215                     // invokes SystemUI through KeyguardService. Because of this substantial delay,
216                     // do not immediately process any dreaming information when exiting AOD. It
217                     // should actually be quite strange to leave AOD and then go straight to
218                     // DREAMING so this should be fine.
219                     delay(IS_ABLE_TO_DREAM_DELAY_MS)
220                     isDreaming.debounce(50L)
221                 } else {
222                     flowOf(false)
223                 }
224             }
225             .stateIn(
226                 scope = applicationScope,
227                 started = SharingStarted.WhileSubscribed(),
228                 initialValue = false,
229             )
230 
231     /** Whether the keyguard is showing or not. */
232     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
233     val isKeyguardShowing: StateFlow<Boolean> = repository.isKeyguardShowing
234 
235     /** Whether the keyguard is dismissible or not. */
236     val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible
237 
238     /** Whether the keyguard is occluded (covered by an activity). */
239     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
240     val isKeyguardOccluded: StateFlow<Boolean> = repository.isKeyguardOccluded
241 
242     /** Whether the keyguard is going away. */
243     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
244     val isKeyguardGoingAway: StateFlow<Boolean> = repository.isKeyguardGoingAway.asStateFlow()
245 
246     /** Keyguard can be clipped at the top as the shade is dragged */
247     val topClippingBounds: Flow<Int?> by lazy {
248         combineTransform(
249                 keyguardTransitionInteractor
250                     .transitionValue(content = Scenes.Gone, stateWithoutSceneContainer = GONE)
251                     .map { it == 1f }
252                     .onStart { emit(false) }
253                     .distinctUntilChanged(),
254                 repository.topClippingBounds,
255             ) { isGone, topClippingBounds ->
256                 if (!isGone) {
257                     emit(topClippingBounds)
258                 }
259             }
260             .distinctUntilChanged()
261     }
262 
263     /** Last point that [KeyguardRootView] view was tapped */
264     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
265 
266     /** Is the ambient indication area visible? */
267     val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow()
268 
269     /** Whether the primary bouncer is showing or not. */
270     @JvmField val primaryBouncerShowing: StateFlow<Boolean> = bouncerRepository.primaryBouncerShow
271 
272     /** Whether the alternate bouncer is showing or not. */
273     val alternateBouncerShowing: Flow<Boolean> =
274         bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) {
275             alternateBouncerVisible,
276             isAbleToDream ->
277             if (isAbleToDream) {
278                 // If the alternate bouncer will show over a dream, it is likely that the dream has
279                 // requested a dismissal, which will stop the dream. By delaying this slightly, the
280                 // DREAMING->LOCKSCREEN transition will now happen first, followed by
281                 // LOCKSCREEN->ALTERNATE_BOUNCER.
282                 delay(600L)
283             }
284             alternateBouncerVisible
285         }
286 
287     /** Observable for the [StatusBarState] */
288     val statusBarState: StateFlow<StatusBarState> = repository.statusBarState
289 
290     /** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */
291     val biometricUnlockState: StateFlow<BiometricUnlockModel> = repository.biometricUnlockState
292 
293     /** Keyguard is present and is not occluded. */
294     val isKeyguardVisible: Flow<Boolean> =
295         combine(isKeyguardShowing, isKeyguardOccluded) { showing, occluded -> showing && !occluded }
296 
297     /**
298      * Event types that affect whether secure camera is active. Only used by [isSecureCameraActive].
299      */
300     private enum class SecureCameraRelatedEventType {
301         KeyguardBecameVisible,
302         PrimaryBouncerBecameVisible,
303         SecureCameraLaunched,
304     }
305 
306     /** Whether camera is launched over keyguard. */
307     val isSecureCameraActive: Flow<Boolean> =
308         merge(
309                 onCameraLaunchDetected
310                     .filter { it.type == CameraLaunchType.POWER_DOUBLE_TAP }
311                     .map { SecureCameraRelatedEventType.SecureCameraLaunched },
312                 isKeyguardVisible
313                     .filter { it }
314                     .map { SecureCameraRelatedEventType.KeyguardBecameVisible },
315                 primaryBouncerShowing
316                     .filter { it }
317                     .map { SecureCameraRelatedEventType.PrimaryBouncerBecameVisible },
318             )
319             .map {
320                 when (it) {
321                     SecureCameraRelatedEventType.SecureCameraLaunched -> true
322                     // When secure camera is closed, either the keyguard or the primary bouncer will
323                     // have to show, so those events tell us that secure camera is no longer active.
324                     SecureCameraRelatedEventType.KeyguardBecameVisible -> false
325                     SecureCameraRelatedEventType.PrimaryBouncerBecameVisible -> false
326                 }
327             }
328             .onStart { emit(false) }
329             .distinctUntilChanged()
330 
331     /** The approximate location on the screen of the fingerprint sensor, if one is available. */
332     val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
333 
334     /** The approximate location on the screen of the face unlock sensor, if one is available. */
335     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
336 
337     /** Temporary shim for fading out content when the brightness slider is used */
338     @Deprecated("SceneContainer uses NotificationStackAppearanceInteractor")
339     val panelAlpha: StateFlow<Float> = repository.panelAlpha.asStateFlow()
340 
341     /** Sets the zoom out scale of spatial model pushback from e.g. pulling down the shade. */
342     val zoomOut: StateFlow<Float> = repository.zoomOut
343 
344     /**
345      * When the lockscreen can be dismissed, emit an alpha value as the user swipes up. This is
346      * useful just before the code commits to moving to GONE.
347      *
348      * This uses legacyShadeExpansion to process swipe up events. In the future, the touch input
349      * signal should be sent directly to transitions.
350      */
351     val dismissAlpha: Flow<Float> =
352         shadeRepository.legacyShadeExpansion
353             .sampleCombine(
354                 keyguardTransitionInteractor.currentKeyguardState,
355                 keyguardTransitionInteractor.transitionState,
356                 isKeyguardDismissible,
357                 keyguardTransitionInteractor.isFinishedIn(Scenes.Communal, GLANCEABLE_HUB),
358             )
359             .filter { (_, _, step, _, _) -> !step.transitionState.isTransitioning() }
360             .transform {
361                 (
362                     legacyShadeExpansion,
363                     currentKeyguardState,
364                     step,
365                     isKeyguardDismissible,
366                     onGlanceableHub) ->
367                 if (
368                     statusBarState.value == StatusBarState.KEYGUARD &&
369                         isKeyguardDismissible &&
370                         currentKeyguardState == LOCKSCREEN &&
371                         legacyShadeExpansion != 1f
372                 ) {
373                     emit(MathUtils.constrainedMap(0f, 1f, 0.82f, 1f, legacyShadeExpansion))
374                 } else if (
375                     !onGlanceableHub &&
376                         isKeyguardDismissible &&
377                         (legacyShadeExpansion == 0f || legacyShadeExpansion == 1f)
378                 ) {
379                     // Resets alpha state
380                     emit(1f)
381                 }
382             }
383             .onStart { emit(1f) }
384             .distinctUntilChanged()
385 
386     val keyguardTranslationY: Flow<Float> =
387         configurationInteractor
388             .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
389             .flatMapLatest { translationDistance ->
390                 combineTransform(
391                     shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
392                     keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
393                 ) { legacyShadeExpansion, goneValue ->
394                     val isLegacyShadeInResetPosition =
395                         legacyShadeExpansion == 0f || legacyShadeExpansion == 1f
396                     if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) {
397                         // Reset the translation value
398                         emit(0f)
399                     } else if (!isLegacyShadeInResetPosition) {
400                         // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
401                         // transition is running, which means this is a swipe to dismiss. Values of
402                         // 0f and 1f need to be ignored in the legacy shade expansion. These can
403                         // flip arbitrarily as the legacy shade is reset, and would cause the
404                         // translation value to jump around unexpectedly.
405                         emit(MathUtils.lerp(translationDistance, 0, legacyShadeExpansion))
406                     }
407                 }
408             }
409             .stateIn(
410                 scope = applicationScope,
411                 started = SharingStarted.WhileSubscribed(),
412                 initialValue = 0f,
413             )
414 
415     /** Whether to animate the next doze mode transition. */
416     val animateDozingTransitions: Flow<Boolean> by lazy {
417         if (SceneContainerFlag.isEnabled) {
418             sceneInteractorProvider
419                 .get()
420                 .transitioningTo
421                 .map { it == Scenes.Lockscreen }
422                 .distinctUntilChanged()
423                 .flatMapLatest { isTransitioningToLockscreenScene ->
424                     if (isTransitioningToLockscreenScene) {
425                         flowOf(false)
426                     } else {
427                         repository.animateBottomAreaDozingTransitions
428                     }
429                 }
430         } else {
431             repository.animateBottomAreaDozingTransitions
432         }
433     }
434 
435     /** Which keyguard state to use when the device goes to sleep. */
436     val asleepKeyguardState: StateFlow<KeyguardState> =
437         repository.isAodAvailable
438             .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
439             .stateIn(applicationScope, SharingStarted.Eagerly, DOZING)
440 
441     /**
442      * Whether the primary authentication is required for the given user due to lockdown or
443      * encryption after reboot.
444      */
445     val isEncryptedOrLockdown: Flow<Boolean> = repository.isEncryptedOrLockdown
446 
447     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
448         return dozeTransitionModel.filter { states.contains(it.to) }
449     }
450 
451     fun isKeyguardShowing(): Boolean {
452         return repository.isKeyguardShowing()
453     }
454 
455     private fun cameraLaunchSourceIntToType(value: Int): CameraLaunchType {
456         return when (value) {
457             StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchType.WIGGLE
458             StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP ->
459                 CameraLaunchType.POWER_DOUBLE_TAP
460             StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER -> CameraLaunchType.LIFT_TRIGGER
461             StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE ->
462                 CameraLaunchType.QUICK_AFFORDANCE
463             else -> throw IllegalArgumentException("Invalid CameraLaunchType value: $value")
464         }
465     }
466 
467     /** Sets whether quick settings or quick-quick settings is visible. */
468     fun setQuickSettingsVisible(isVisible: Boolean) {
469         repository.setQuickSettingsVisible(isVisible)
470     }
471 
472     fun setPanelAlpha(alpha: Float) {
473         repository.setPanelAlpha(alpha)
474     }
475 
476     fun setZoomOut(zoomOutFromShadeRadius: Float) {
477         repository.setZoomOut(zoomOutFromShadeRadius)
478     }
479 
480     fun setAnimateDozingTransitions(animate: Boolean) {
481         repository.setAnimateDozingTransitions(animate)
482     }
483 
484     fun setLastRootViewTapPosition(point: Point?) {
485         repository.lastRootViewTapPosition.value = point
486     }
487 
488     fun setAmbientIndicationVisible(isVisible: Boolean) {
489         repository.ambientIndicationVisible.value = isVisible
490     }
491 
492     fun keyguardDoneAnimationsFinished() {
493         repository.keyguardDoneAnimationsFinished()
494     }
495 
496     fun setTopClippingBounds(top: Int?) {
497         repository.topClippingBounds.value = top
498     }
499 
500     fun setDreaming(isDreaming: Boolean) {
501         repository.setDreaming(isDreaming)
502     }
503 
504     /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
505     fun showKeyguard() {
506         fromGoneTransitionInteractor.get().showKeyguard()
507     }
508 
509     /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
510     fun showGlanceableHub(): Boolean {
511         return fromGoneTransitionInteractor.get().showGlanceableHub()
512     }
513 
514     /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
515     fun dismissKeyguard() {
516         when (keyguardTransitionInteractor.transitionState.value.to) {
517             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
518             OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
519             ALTERNATE_BOUNCER ->
520                 fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
521             else -> Log.v(TAG, "Keyguard was dismissed, no direct transition call needed")
522         }
523     }
524 
525     fun onCameraLaunchDetected(source: Int) {
526         repository.onCameraLaunchDetected.value =
527             CameraLaunchSourceModel(type = cameraLaunchSourceIntToType(source))
528     }
529 
530     fun showDismissibleKeyguard() {
531         repository.showDismissibleKeyguard()
532     }
533 
534     fun setShortcutAbsoluteTop(top: Float) {
535         wallpaperFocalAreaRepository.setShortcutAbsoluteTop(top)
536     }
537 
538     fun setIsKeyguardGoingAway(isGoingAway: Boolean) {
539         repository.isKeyguardGoingAway.value = isGoingAway
540     }
541 
542     fun setNotificationStackAbsoluteBottom(bottom: Float) {
543         wallpaperFocalAreaRepository.setNotificationStackAbsoluteBottom(bottom)
544     }
545 
546     suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
547         isDozing
548             .logDiffsForTable(
549                 tableLogBuffer = tableLogBuffer,
550                 columnName = "isDozing",
551                 initialValue = isDozing.value,
552             )
553             .collect()
554     }
555 
556     companion object {
557         private const val TAG = "KeyguardInteractor"
558         /**
559          * Amount of time that [KeyguardInteractor.isAbleToDream] is delayed; consumers of that flow
560          * should consider waiting this amount of time before check the value of this flow, to let
561          * other consumers have enough time to see the new value.
562          */
563         const val IS_ABLE_TO_DREAM_DELAY_MS = 500L
564     }
565 }
566