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