1 /* <lambda>null2 * Copyright (C) 2023 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 */ 17 18 package com.android.systemui.keyguard.ui.viewmodel 19 20 import android.graphics.Point 21 import android.util.MathUtils 22 import android.view.View.VISIBLE 23 import com.android.app.tracing.coroutines.launch 24 import com.android.systemui.Flags.newAodTransition 25 import com.android.systemui.common.shared.model.NotificationContainerBounds 26 import com.android.systemui.communal.domain.interactor.CommunalInteractor 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.dagger.qualifiers.Application 29 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor 30 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 31 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 32 import com.android.systemui.keyguard.shared.model.BurnInModel 33 import com.android.systemui.keyguard.shared.model.Edge 34 import com.android.systemui.keyguard.shared.model.KeyguardState 35 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD 36 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE 37 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 38 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED 39 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER 40 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING 41 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED 42 import com.android.systemui.keyguard.ui.StateToValue 43 import com.android.systemui.scene.shared.model.Scenes 44 import com.android.systemui.shade.domain.interactor.ShadeInteractor 45 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor 46 import com.android.systemui.statusbar.phone.DozeParameters 47 import com.android.systemui.statusbar.phone.ScreenOffAnimationController 48 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf 49 import com.android.systemui.util.kotlin.pairwise 50 import com.android.systemui.util.kotlin.sample 51 import com.android.systemui.util.ui.AnimatableEvent 52 import com.android.systemui.util.ui.AnimatedValue 53 import com.android.systemui.util.ui.toAnimatedValueFlow 54 import com.android.systemui.util.ui.zip 55 import javax.inject.Inject 56 import kotlin.math.max 57 import kotlinx.coroutines.CoroutineScope 58 import kotlinx.coroutines.ExperimentalCoroutinesApi 59 import kotlinx.coroutines.Job 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.combine 66 import kotlinx.coroutines.flow.combineTransform 67 import kotlinx.coroutines.flow.distinctUntilChanged 68 import kotlinx.coroutines.flow.filter 69 import kotlinx.coroutines.flow.map 70 import kotlinx.coroutines.flow.merge 71 import kotlinx.coroutines.flow.onStart 72 import kotlinx.coroutines.flow.stateIn 73 74 @OptIn(ExperimentalCoroutinesApi::class) 75 @SysUISingleton 76 class KeyguardRootViewModel 77 @Inject 78 constructor( 79 @Application private val applicationScope: CoroutineScope, 80 private val deviceEntryInteractor: DeviceEntryInteractor, 81 private val dozeParameters: DozeParameters, 82 private val keyguardInteractor: KeyguardInteractor, 83 private val communalInteractor: CommunalInteractor, 84 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 85 private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, 86 private val alternateBouncerToGoneTransitionViewModel: 87 AlternateBouncerToGoneTransitionViewModel, 88 private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, 89 private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, 90 private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, 91 private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, 92 private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, 93 private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, 94 private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, 95 private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel, 96 private val glanceableHubToLockscreenTransitionViewModel: 97 GlanceableHubToLockscreenTransitionViewModel, 98 private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, 99 private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel, 100 private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel, 101 private val goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel, 102 private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, 103 private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel, 104 private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, 105 private val lockscreenToGlanceableHubTransitionViewModel: 106 LockscreenToGlanceableHubTransitionViewModel, 107 private val lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, 108 private val lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, 109 private val lockscreenToPrimaryBouncerTransitionViewModel: 110 LockscreenToPrimaryBouncerTransitionViewModel, 111 private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, 112 private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel, 113 private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, 114 private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, 115 private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, 116 private val primaryBouncerToLockscreenTransitionViewModel: 117 PrimaryBouncerToLockscreenTransitionViewModel, 118 private val screenOffAnimationController: ScreenOffAnimationController, 119 private val aodBurnInViewModel: AodBurnInViewModel, 120 private val aodAlphaViewModel: AodAlphaViewModel, 121 private val shadeInteractor: ShadeInteractor, 122 ) { 123 private var burnInJob: Job? = null 124 private val _burnInModel = MutableStateFlow(BurnInModel()) 125 val burnInModel = _burnInModel.asStateFlow() 126 127 val burnInLayerVisibility: Flow<Int> = 128 keyguardTransitionInteractor.startedKeyguardState 129 .filter { it == AOD || it == LOCKSCREEN } 130 .map { VISIBLE } 131 132 val goneToAodTransition = 133 keyguardTransitionInteractor.transition( 134 edge = Edge.create(Scenes.Gone, AOD), 135 edgeWithoutSceneContainer = Edge.create(GONE, AOD) 136 ) 137 138 private val goneToAodTransitionRunning: Flow<Boolean> = 139 goneToAodTransition 140 .map { it.transitionState == STARTED || it.transitionState == RUNNING } 141 .onStart { emit(false) } 142 .distinctUntilChanged() 143 144 private val isOnLockscreen: Flow<Boolean> = 145 combine( 146 keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) }, 147 anyOf( 148 keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)), 149 keyguardTransitionInteractor.isInTransition(Edge.create(from = LOCKSCREEN)), 150 ), 151 ) { onLockscreen, transitioningToOrFromLockscreen -> 152 onLockscreen || transitioningToOrFromLockscreen 153 } 154 .distinctUntilChanged() 155 156 private val alphaOnShadeExpansion: Flow<Float> = 157 combineTransform( 158 keyguardTransitionInteractor.isInTransition( 159 edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone), 160 edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE), 161 ), 162 keyguardTransitionInteractor.isInTransition( 163 edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Lockscreen), 164 edgeWithoutSceneContainer = 165 Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), 166 ), 167 isOnLockscreen, 168 shadeInteractor.qsExpansion, 169 shadeInteractor.shadeExpansion, 170 ) { 171 lockscreenToGoneTransitionRunning, 172 primaryBouncerToLockscreenTransitionRunning, 173 isOnLockscreen, 174 qsExpansion, 175 shadeExpansion -> 176 // Fade out quickly as the shade expands 177 if ( 178 isOnLockscreen && 179 !lockscreenToGoneTransitionRunning && 180 !primaryBouncerToLockscreenTransitionRunning 181 ) { 182 val alpha = 183 1f - 184 MathUtils.constrainedMap( 185 /* rangeMin = */ 0f, 186 /* rangeMax = */ 1f, 187 /* valueMin = */ 0f, 188 /* valueMax = */ 0.2f, 189 /* value = */ max(qsExpansion, shadeExpansion) 190 ) 191 emit(alpha) 192 } 193 } 194 .distinctUntilChanged() 195 196 /** 197 * Keyguard should not show while the communal hub is fully visible. This check is added since 198 * at the moment, closing the notification shade will cause the keyguard alpha to be set back to 199 * 1. Also ensure keyguard is never visible when GONE. 200 */ 201 private val hideKeyguard: Flow<Boolean> = 202 combine( 203 communalInteractor.isIdleOnCommunal, 204 keyguardTransitionInteractor 205 .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE) 206 .map { it == 1f } 207 .onStart { emit(false) }, 208 keyguardTransitionInteractor 209 .transitionValue(OCCLUDED) 210 .map { it == 1f } 211 .onStart { emit(false) }, 212 keyguardTransitionInteractor 213 .transitionValue(KeyguardState.DREAMING) 214 .map { it == 1f } 215 .onStart { emit(false) }, 216 ) { isIdleOnCommunal, isGone, isOccluded, isDreaming -> 217 isIdleOnCommunal || isGone || isOccluded || isDreaming 218 } 219 .distinctUntilChanged() 220 221 /** Last point that the root view was tapped */ 222 val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition 223 224 /** 225 * The keyguard root view can be clipped as the shade is pulled down, typically only for 226 * non-split shade cases. 227 */ 228 val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds 229 230 /** An observable for the alpha level for the entire keyguard root view. */ 231 fun alpha(viewState: ViewStateAccessor): Flow<Float> { 232 return combine( 233 hideKeyguard, 234 // The transitions are mutually exclusive, so they are safe to merge to get the last 235 // value emitted by any of them. Do not add flows that cannot make this guarantee. 236 merge( 237 alphaOnShadeExpansion, 238 keyguardInteractor.dismissAlpha, 239 alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState), 240 aodToGoneTransitionViewModel.lockscreenAlpha(viewState), 241 aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), 242 aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), 243 dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), 244 dozingToLockscreenTransitionViewModel.lockscreenAlpha, 245 dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), 246 dreamingToGoneTransitionViewModel.lockscreenAlpha, 247 dreamingToLockscreenTransitionViewModel.lockscreenAlpha, 248 glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, 249 goneToAodTransitionViewModel.enterFromTopAnimationAlpha, 250 goneToDozingTransitionViewModel.lockscreenAlpha, 251 goneToDreamingTransitionViewModel.lockscreenAlpha, 252 goneToLockscreenTransitionViewModel.lockscreenAlpha, 253 lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState), 254 lockscreenToDozingTransitionViewModel.lockscreenAlpha, 255 lockscreenToDreamingTransitionViewModel.lockscreenAlpha, 256 lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, 257 lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), 258 lockscreenToOccludedTransitionViewModel.lockscreenAlpha, 259 lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, 260 occludedToAodTransitionViewModel.lockscreenAlpha, 261 occludedToDozingTransitionViewModel.lockscreenAlpha, 262 occludedToLockscreenTransitionViewModel.lockscreenAlpha, 263 primaryBouncerToAodTransitionViewModel.lockscreenAlpha, 264 primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, 265 primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, 266 ) 267 .onStart { emit(1f) } 268 ) { hideKeyguard, alpha -> 269 if (hideKeyguard) { 270 0f 271 } else { 272 alpha 273 } 274 } 275 .distinctUntilChanged() 276 } 277 278 /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ 279 @Deprecated("only used for legacy status view") 280 fun lockscreenStateAlpha(viewState: ViewStateAccessor): Flow<Float> { 281 return aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState) 282 } 283 284 /** For elements that appear and move during the animation -> AOD */ 285 val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha 286 287 val translationY: Flow<Float> = burnInModel.map { it.translationY.toFloat() } 288 289 val translationX: Flow<StateToValue> = 290 merge( 291 burnInModel.map { StateToValue(to = AOD, value = it.translationX.toFloat()) }, 292 lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX, 293 glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX, 294 ) 295 296 fun updateBurnInParams(params: BurnInParameters) { 297 burnInJob?.cancel() 298 299 burnInJob = 300 applicationScope.launch("$TAG#aodBurnInViewModel") { 301 aodBurnInViewModel.movement(params).collect { _burnInModel.value = it } 302 } 303 } 304 305 val scale: Flow<BurnInScaleViewModel> = 306 burnInModel.map { 307 BurnInScaleViewModel( 308 scale = it.scale, 309 scaleClockOnly = it.scaleClockOnly, 310 ) 311 } 312 313 /** Is the notification icon container visible? */ 314 val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> = 315 combine( 316 goneToAodTransitionRunning, 317 keyguardTransitionInteractor.finishedKeyguardState.map { 318 KeyguardState.lockscreenVisibleInState(it) 319 }, 320 deviceEntryInteractor.isBypassEnabled, 321 areNotifsFullyHiddenAnimated(), 322 isPulseExpandingAnimated(), 323 ) { 324 goneToAodTransitionRunning: Boolean, 325 onKeyguard: Boolean, 326 isBypassEnabled: Boolean, 327 notifsFullyHidden: AnimatedValue<Boolean>, 328 pulseExpanding: AnimatedValue<Boolean>, 329 -> 330 when { 331 // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off 332 // animation is playing, in which case we want them to be visible if we're 333 // animating in the AOD UI and will be switching to KEYGUARD shortly. 334 goneToAodTransitionRunning || 335 (!onKeyguard && 336 !screenOffAnimationController.shouldShowAodIconsWhenShade()) -> 337 AnimatedValue.NotAnimating(false) 338 else -> 339 zip(notifsFullyHidden, pulseExpanding) { 340 areNotifsFullyHidden, 341 isPulseExpanding, 342 -> 343 when { 344 // If we're bypassing, then we're visible 345 isBypassEnabled -> true 346 // If we are pulsing (and not bypassing), then we are hidden 347 isPulseExpanding -> false 348 // If notifs are fully gone, then we're visible 349 areNotifsFullyHidden -> true 350 // Otherwise, we're hidden 351 else -> false 352 } 353 } 354 } 355 } 356 .stateIn( 357 scope = applicationScope, 358 started = SharingStarted.WhileSubscribed(), 359 initialValue = AnimatedValue.NotAnimating(false), 360 ) 361 362 fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) { 363 keyguardInteractor.setNotificationContainerBounds( 364 NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate) 365 ) 366 } 367 368 /** Is there an expanded pulse, are we animating in response? */ 369 private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> { 370 return notificationsKeyguardInteractor.isPulseExpanding 371 .pairwise(initialValue = null) 372 // If pulsing changes, start animating, unless it's the first emission 373 .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) } 374 .toAnimatedValueFlow() 375 } 376 377 /** Are notifications completely hidden from view, are we animating in response? */ 378 private fun areNotifsFullyHiddenAnimated(): Flow<AnimatedValue<Boolean>> { 379 return notificationsKeyguardInteractor.areNotificationsFullyHidden 380 .pairwise(initialValue = null) 381 .sample(deviceEntryInteractor.isBypassEnabled) { (prev, fullyHidden), bypassEnabled -> 382 val animate = 383 when { 384 // Don't animate for the first value 385 prev == null -> false 386 // Always animate if bypass is enabled. 387 bypassEnabled -> true 388 // If we're not bypassing and we're not going to AOD, then we're not 389 // animating. 390 !dozeParameters.alwaysOn -> false 391 // Don't animate when going to AOD if the display needs blanking. 392 dozeParameters.displayNeedsBlanking -> false 393 // We only want the appear animations to happen when the notifications 394 // get fully hidden, since otherwise the un-hide animation overlaps. 395 newAodTransition() -> true 396 else -> fullyHidden 397 } 398 AnimatableEvent(fullyHidden, animate) 399 } 400 .toAnimatedValueFlow() 401 } 402 403 fun setRootViewLastTapPosition(point: Point) { 404 keyguardInteractor.setLastRootViewTapPosition(point) 405 } 406 407 companion object { 408 private const val TAG = "KeyguardRootViewModel" 409 } 410 } 411