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.systemui.common.shared.model.NotificationContainerBounds 24 import com.android.systemui.communal.domain.interactor.CommunalInteractor 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor 28 import com.android.systemui.dump.DumpManager 29 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 30 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 31 import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor 32 import com.android.systemui.keyguard.shared.model.Edge 33 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD 34 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE 35 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 36 import com.android.systemui.keyguard.shared.model.KeyguardState.OFF 37 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER 38 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING 39 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED 40 import com.android.systemui.keyguard.ui.StateToValue 41 import com.android.systemui.scene.shared.model.Overlays 42 import com.android.systemui.scene.shared.model.Scenes 43 import com.android.systemui.shade.domain.interactor.ShadeInteractor 44 import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel 45 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor 46 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel 47 import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor 48 import com.android.systemui.statusbar.phone.DozeParameters 49 import com.android.systemui.statusbar.phone.ScreenOffAnimationController 50 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf 51 import com.android.systemui.util.kotlin.FlowDumperImpl 52 import com.android.systemui.util.kotlin.pairwise 53 import com.android.systemui.util.kotlin.sample 54 import com.android.systemui.util.ui.AnimatableEvent 55 import com.android.systemui.util.ui.AnimatedValue 56 import com.android.systemui.util.ui.toAnimatedValueFlow 57 import com.android.systemui.util.ui.zip 58 import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractor 59 import javax.inject.Inject 60 import kotlin.math.max 61 import kotlinx.coroutines.CoroutineScope 62 import kotlinx.coroutines.flow.Flow 63 import kotlinx.coroutines.flow.SharingStarted 64 import kotlinx.coroutines.flow.StateFlow 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 @SysUISingleton 75 class KeyguardRootViewModel 76 @Inject 77 constructor( 78 @Application private val applicationScope: CoroutineScope, 79 private val deviceEntryInteractor: DeviceEntryInteractor, 80 private val dozeParameters: DozeParameters, 81 private val keyguardInteractor: KeyguardInteractor, 82 private val communalInteractor: CommunalInteractor, 83 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 84 private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, 85 private val pulseExpansionInteractor: PulseExpansionInteractor, 86 notificationShadeWindowModel: NotificationShadeWindowModel, 87 private val aodPromotedNotificationInteractor: AODPromotedNotificationInteractor, 88 private val aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, 89 private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel, 90 private val alternateBouncerToGoneTransitionViewModel: 91 AlternateBouncerToGoneTransitionViewModel, 92 private val alternateBouncerToLockscreenTransitionViewModel: 93 AlternateBouncerToLockscreenTransitionViewModel, 94 private val alternateBouncerToOccludedTransitionViewModel: 95 AlternateBouncerToOccludedTransitionViewModel, 96 private val alternateBouncerToPrimaryBouncerTransitionViewModel: 97 AlternateBouncerToPrimaryBouncerTransitionViewModel, 98 private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, 99 private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, 100 private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, 101 private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel, 102 private val aodToGlanceableHubTransitionViewModel: AodToGlanceableHubTransitionViewModel, 103 private val dozingToDreamingTransitionViewModel: DozingToDreamingTransitionViewModel, 104 private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, 105 private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, 106 private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, 107 private val dozingToPrimaryBouncerTransitionViewModel: 108 DozingToPrimaryBouncerTransitionViewModel, 109 private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel, 110 private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel, 111 private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, 112 private val glanceableHubToLockscreenTransitionViewModel: 113 GlanceableHubToLockscreenTransitionViewModel, 114 private val glanceableHubToAodTransitionViewModel: GlanceableHubToAodTransitionViewModel, 115 private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, 116 private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel, 117 private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel, 118 private val goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel, 119 private val goneToGlanceableHubTransitionViewModel: GoneToGlanceableHubTransitionViewModel, 120 private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, 121 private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel, 122 private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, 123 private val lockscreenToGlanceableHubTransitionViewModel: 124 LockscreenToGlanceableHubTransitionViewModel, 125 private val lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, 126 private val lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, 127 private val lockscreenToPrimaryBouncerTransitionViewModel: 128 LockscreenToPrimaryBouncerTransitionViewModel, 129 private val occludedToAlternateBouncerTransitionViewModel: 130 OccludedToAlternateBouncerTransitionViewModel, 131 private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, 132 private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel, 133 private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, 134 private val occludedToPrimaryBouncerTransitionViewModel: 135 OccludedToPrimaryBouncerTransitionViewModel, 136 private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel, 137 private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, 138 private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, 139 private val primaryBouncerToLockscreenTransitionViewModel: 140 PrimaryBouncerToLockscreenTransitionViewModel, 141 private val screenOffAnimationController: ScreenOffAnimationController, 142 private val aodBurnInViewModel: AodBurnInViewModel, 143 private val shadeInteractor: ShadeInteractor, 144 wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, 145 dumpManager: DumpManager, 146 ) : FlowDumperImpl(dumpManager) { 147 val burnInLayerVisibility: Flow<Int> = 148 keyguardTransitionInteractor.startedKeyguardTransitionStep 149 .filter { it.to == AOD || it.to == LOCKSCREEN } 150 .map { VISIBLE } 151 .dumpWhileCollecting("burnInLayerVisibility") 152 153 val goneToAodTransition = 154 keyguardTransitionInteractor 155 .transition( 156 edge = Edge.create(Scenes.Gone, AOD), 157 edgeWithoutSceneContainer = Edge.create(GONE, AOD), 158 ) 159 .dumpWhileCollecting("goneToAodTransition") 160 161 private val goneToAodTransitionRunning: Flow<Boolean> = 162 goneToAodTransition 163 .map { it.transitionState == STARTED || it.transitionState == RUNNING } 164 .onStart { emit(false) } 165 .distinctUntilChanged() 166 167 private val isOnOrGoingToLockscreen: Flow<Boolean> = 168 combine( 169 keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it == 1f }, 170 keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)), 171 ) { onLockscreen, transitioningToLockscreen -> 172 onLockscreen || transitioningToLockscreen 173 } 174 .distinctUntilChanged() 175 176 private val alphaOnShadeExpansion: Flow<Float> = 177 combineTransform( 178 keyguardTransitionInteractor.isInTransition( 179 edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN), 180 edgeWithoutSceneContainer = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), 181 ), 182 isOnOrGoingToLockscreen, 183 shadeInteractor.qsExpansion, 184 shadeInteractor.shadeExpansion, 185 ) { disabledTransitionRunning, isOnOrGoingToLockscreen, qsExpansion, shadeExpansion -> 186 // Fade out quickly as the shade expands 187 if (isOnOrGoingToLockscreen && !disabledTransitionRunning) { 188 val alpha = 189 1f - 190 MathUtils.constrainedMap( 191 /* rangeMin = */ 0f, 192 /* rangeMax = */ 1f, 193 /* valueMin = */ 0f, 194 /* valueMax = */ 0.2f, 195 /* value = */ max(qsExpansion, shadeExpansion), 196 ) 197 emit(alpha) 198 } 199 } 200 .distinctUntilChanged() 201 202 /** 203 * Keyguard should not show if fully transitioned into a hidden keyguard state or if 204 * transitioning between hidden states. 205 */ 206 private val hideKeyguard: Flow<Boolean> = 207 anyOf( 208 notificationShadeWindowModel.isKeyguardOccluded, 209 communalInteractor.isIdleOnCommunal, 210 keyguardTransitionInteractor 211 .transitionValue(OFF) 212 .map { it > 1f - offToLockscreenTransitionViewModel.alphaStartAt } 213 .onStart { emit(false) }, 214 keyguardTransitionInteractor 215 .transitionValue(content = Scenes.Gone, stateWithoutSceneContainer = GONE) 216 .map { it == 1f } 217 .onStart { emit(false) }, 218 ) 219 220 /** Last point that the root view was tapped */ 221 val lastRootViewTapPosition: Flow<Point?> = 222 keyguardInteractor.lastRootViewTapPosition.dumpWhileCollecting("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?> = 229 keyguardInteractor.topClippingBounds.dumpWhileCollecting("topClippingBounds") 230 231 /** An observable for the alpha level for the entire keyguard root view. */ 232 fun alpha(viewState: ViewStateAccessor): Flow<Float> { 233 return combine( 234 hideKeyguard, 235 // The transitions are mutually exclusive, so they are safe to merge to get the last 236 // value emitted by any of them. Do not add flows that cannot make this guarantee. 237 merge( 238 alphaOnShadeExpansion, 239 keyguardInteractor.dismissAlpha, 240 alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState), 241 alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState), 242 alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState), 243 alternateBouncerToPrimaryBouncerTransitionViewModel.lockscreenAlpha, 244 alternateBouncerToOccludedTransitionViewModel.lockscreenAlpha, 245 aodToGoneTransitionViewModel.lockscreenAlpha(viewState), 246 aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), 247 aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), 248 aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha, 249 aodToGlanceableHubTransitionViewModel.lockscreenAlpha(viewState), 250 dozingToDreamingTransitionViewModel.lockscreenAlpha, 251 dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), 252 dozingToLockscreenTransitionViewModel.lockscreenAlpha, 253 dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), 254 dozingToPrimaryBouncerTransitionViewModel.lockscreenAlpha, 255 dreamingToAodTransitionViewModel.lockscreenAlpha, 256 dreamingToGoneTransitionViewModel.lockscreenAlpha, 257 dreamingToLockscreenTransitionViewModel.lockscreenAlpha, 258 glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, 259 glanceableHubToAodTransitionViewModel.lockscreenAlpha, 260 goneToAodTransitionViewModel.enterFromTopAnimationAlpha, 261 goneToDozingTransitionViewModel.lockscreenAlpha, 262 goneToDreamingTransitionViewModel.lockscreenAlpha, 263 goneToLockscreenTransitionViewModel.lockscreenAlpha, 264 lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState), 265 lockscreenToAodTransitionViewModel.lockscreenAlphaOnFold, 266 lockscreenToDozingTransitionViewModel.lockscreenAlpha, 267 lockscreenToDreamingTransitionViewModel.lockscreenAlpha, 268 lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, 269 lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), 270 lockscreenToOccludedTransitionViewModel.lockscreenAlpha, 271 lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, 272 occludedToAlternateBouncerTransitionViewModel.lockscreenAlpha, 273 occludedToAodTransitionViewModel.lockscreenAlpha, 274 occludedToDozingTransitionViewModel.lockscreenAlpha, 275 occludedToLockscreenTransitionViewModel.lockscreenAlpha, 276 occludedToPrimaryBouncerTransitionViewModel.lockscreenAlpha, 277 offToLockscreenTransitionViewModel.lockscreenAlpha, 278 primaryBouncerToAodTransitionViewModel.lockscreenAlpha, 279 primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, 280 primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState), 281 goneToGlanceableHubTransitionViewModel.keyguardAlpha, 282 ) 283 .onStart { emit(0f) }, 284 ) { hideKeyguard, alpha -> 285 if (hideKeyguard) { 286 0f 287 } else { 288 alpha 289 } 290 } 291 .distinctUntilChanged() 292 .dumpWhileCollecting("alpha") 293 } 294 295 val scaleFromZoomOut: Flow<Float> = 296 keyguardInteractor.zoomOut 297 .map { 1 - it * PUSHBACK_SCALE_FOR_LOCKSCREEN } 298 .dumpWhileCollecting("scaleFromZoomOut") 299 300 val translationY: Flow<Float> = 301 aodBurnInViewModel.movement 302 .map { it.translationY.toFloat() } 303 .dumpWhileCollecting("translationY") 304 305 val translationX: Flow<StateToValue> = 306 merge( 307 aodBurnInViewModel.movement.map { 308 StateToValue(to = AOD, value = it.translationX.toFloat()) 309 }, 310 lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX, 311 glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX, 312 ) 313 .dumpWhileCollecting("translationX") 314 315 fun updateBurnInParams(params: BurnInParameters) { 316 aodBurnInViewModel.updateBurnInParams(params) 317 } 318 319 val scale: Flow<BurnInScaleViewModel> = 320 aodBurnInViewModel.movement 321 .map { BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly) } 322 .dumpWhileCollecting("scale") 323 324 val isAodPromotedNotifVisible: StateFlow<AnimatedValue<Boolean>> = 325 combine( 326 areNotifsFullyHiddenAnimated(), 327 isPulseExpandingAnimated(), 328 aodPromotedNotificationInteractor.isPresent, 329 ) { notifsFullyHiddenAnimated, pulseExpandingAnimated, haveAodPromotedNotif -> 330 zip(notifsFullyHiddenAnimated, pulseExpandingAnimated) { 331 notifsFullyHidden, 332 pulseExpanding -> 333 notifsFullyHidden && !pulseExpanding && haveAodPromotedNotif 334 } 335 } 336 .stateIn( 337 scope = applicationScope, 338 started = SharingStarted.WhileSubscribed(), 339 initialValue = AnimatedValue.NotAnimating(false), 340 ) 341 .dumpValue("isAodPromotedNotifVisible") 342 343 /** Is the notification icon container visible? */ 344 val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> = 345 combine( 346 goneToAodTransitionRunning, 347 keyguardTransitionInteractor 348 .transitionValue(LOCKSCREEN) 349 .map { it > 0f } 350 .onStart { emit(false) }, 351 keyguardTransitionInteractor.isFinishedIn( 352 content = Scenes.Gone, 353 stateWithoutSceneContainer = GONE, 354 ), 355 deviceEntryInteractor.isBypassEnabled, 356 areNotifsFullyHiddenAnimated(), 357 isPulseExpandingAnimated(), 358 aodNotificationIconViewModel.icons.map { it.visibleIcons.isNotEmpty() }, 359 ) { flows -> 360 val goneToAodTransitionRunning = flows[0] as Boolean 361 val isOnLockscreen = flows[1] as Boolean 362 val isOnGone = flows[2] as Boolean 363 val isBypassEnabled = flows[3] as Boolean 364 val notifsFullyHidden = flows[4] as AnimatedValue<Boolean> 365 val pulseExpanding = flows[5] as AnimatedValue<Boolean> 366 val hasAodIcons = flows[6] as Boolean 367 368 when { 369 // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off 370 // animation is playing, in which case we want them to be visible if we're 371 // animating in the AOD UI and will be switching to KEYGUARD shortly. 372 goneToAodTransitionRunning || 373 (isOnGone && !screenOffAnimationController.shouldShowAodIconsWhenShade()) -> 374 AnimatedValue.NotAnimating(false) 375 else -> 376 zip(notifsFullyHidden, pulseExpanding) { 377 areNotifsFullyHidden, 378 isPulseExpanding -> 379 when { 380 // If there are no notification icons to show, then it can be hidden 381 !hasAodIcons -> false 382 // If we are pulsing (and not bypassing), then we are hidden 383 isPulseExpanding -> false 384 // If notifs are fully gone, then we're visible 385 areNotifsFullyHidden -> true 386 // Otherwise, we're hidden 387 else -> false 388 } 389 } 390 } 391 } 392 .stateIn( 393 scope = applicationScope, 394 started = SharingStarted.WhileSubscribed(), 395 initialValue = AnimatedValue.NotAnimating(false), 396 ) 397 .dumpValue("isNotifIconContainerVisible") 398 399 fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) { 400 keyguardInteractor.setNotificationContainerBounds( 401 NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate) 402 ) 403 } 404 405 /** Is there an expanded pulse, are we animating in response? */ 406 private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> { 407 return pulseExpansionInteractor.isPulseExpanding 408 .pairwise(initialValue = null) 409 // If pulsing changes, start animating, unless it's the first emission 410 .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) } 411 .toAnimatedValueFlow() 412 } 413 414 /** Are notifications completely hidden from view, are we animating in response? */ 415 private fun areNotifsFullyHiddenAnimated(): Flow<AnimatedValue<Boolean>> { 416 return notificationsKeyguardInteractor.areNotificationsFullyHidden 417 .pairwise(initialValue = null) 418 .sample(deviceEntryInteractor.isBypassEnabled) { (prev, fullyHidden), bypassEnabled -> 419 val animate = 420 when { 421 // Don't animate for the first value 422 prev == null -> false 423 // Always animate if bypass is enabled. 424 bypassEnabled -> true 425 // If we're not bypassing and we're not going to AOD, then we're not 426 // animating. 427 !dozeParameters.alwaysOn -> false 428 // Don't animate when going to AOD if the display needs blanking. 429 dozeParameters.displayNeedsBlanking -> false 430 else -> true 431 } 432 AnimatableEvent(fullyHidden, animate) 433 } 434 .toAnimatedValueFlow() 435 } 436 437 fun setRootViewLastTapPosition(point: Point) { 438 keyguardInteractor.setLastRootViewTapPosition(point) 439 } 440 441 companion object { 442 private const val TAG = "KeyguardRootViewModel" 443 private const val PUSHBACK_SCALE_FOR_LOCKSCREEN = 0.05f 444 } 445 } 446