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