• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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