• 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.domain.interactor
19 
20 import android.util.Log
21 import com.android.keyguard.ClockEventController
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
25 import com.android.systemui.keyguard.shared.model.ClockSize
26 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
27 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
28 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
29 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
30 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
31 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
32 import com.android.systemui.plugins.clocks.ClockController
33 import com.android.systemui.plugins.clocks.ClockId
34 import com.android.systemui.scene.shared.flag.SceneContainerFlag
35 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
36 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
37 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
38 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
39 import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
40 import com.android.systemui.util.kotlin.combine
41 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
42 import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractor
43 import javax.inject.Inject
44 import kotlinx.coroutines.CoroutineScope
45 import kotlinx.coroutines.flow.Flow
46 import kotlinx.coroutines.flow.SharingStarted
47 import kotlinx.coroutines.flow.StateFlow
48 import kotlinx.coroutines.flow.combine
49 import kotlinx.coroutines.flow.filterNotNull
50 import kotlinx.coroutines.flow.flowOf
51 import kotlinx.coroutines.flow.map
52 import kotlinx.coroutines.flow.stateIn
53 
54 private val TAG = KeyguardClockInteractor::class.simpleName
55 
56 /** Manages and encapsulates the clock components of the lockscreen root view. */
57 @SysUISingleton
58 class KeyguardClockInteractor
59 @Inject
60 constructor(
61     mediaCarouselInteractor: MediaCarouselInteractor,
62     activeNotificationsInteractor: ActiveNotificationsInteractor,
63     aodPromotedNotificationInteractor: AODPromotedNotificationInteractor,
64     shadeModeInteractor: ShadeModeInteractor,
65     keyguardInteractor: KeyguardInteractor,
66     keyguardTransitionInteractor: KeyguardTransitionInteractor,
67     headsUpNotificationInteractor: HeadsUpNotificationInteractor,
68     @Application private val applicationScope: CoroutineScope,
69     val keyguardClockRepository: KeyguardClockRepository,
70     private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
71 ) {
72     private val isOnAod: Flow<Boolean> =
73         keyguardTransitionInteractor.currentKeyguardState.map { it == AOD }
74 
75     /**
76      * The clock size setting explicitly selected by the user. When it is `SMALL`, the large clock
77      * is never shown. When it is `DYNAMIC`, the clock size gets determined based on a combination
78      * of system signals.
79      */
80     val selectedClockSize: StateFlow<ClockSizeSetting> = keyguardClockRepository.selectedClockSize
81 
82     val currentClockId: Flow<ClockId> = keyguardClockRepository.currentClockId
83 
84     val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock
85 
86     val previewClock: Flow<ClockController> = keyguardClockRepository.previewClock
87 
88     val clockEventController: ClockEventController = keyguardClockRepository.clockEventController
89 
90     var clock: ClockController? by keyguardClockRepository.clockEventController::clock
91 
92     private val isAodPromotedNotificationPresent: Flow<Boolean> =
93         if (PromotedNotificationUi.isEnabled) {
94             aodPromotedNotificationInteractor.isPresent
95         } else {
96             flowOf(false)
97         }
98 
99     private val areAnyNotificationsPresent: Flow<Boolean> =
100         if (PromotedNotificationUi.isEnabled) {
101             combine(
102                 activeNotificationsInteractor.areAnyNotificationsPresent,
103                 isAodPromotedNotificationPresent,
104             ) { areAnyNotificationsPresent, isAodPromotedNotificationPresent ->
105                 areAnyNotificationsPresent || isAodPromotedNotificationPresent
106             }
107         } else {
108             activeNotificationsInteractor.areAnyNotificationsPresent
109         }
110 
111     private val dynamicClockSize: Flow<ClockSize> =
112         if (SceneContainerFlag.isEnabled) {
113             combine(
114                 shadeModeInteractor.isShadeLayoutWide,
115                 areAnyNotificationsPresent,
116                 mediaCarouselInteractor.hasActiveMediaOrRecommendation,
117                 keyguardInteractor.isDozing,
118                 isOnAod,
119             ) { isShadeLayoutWide, hasNotifs, hasMedia, isDozing, isOnAod ->
120                 when {
121                     keyguardClockRepository.shouldForceSmallClock && !isOnAod -> ClockSize.SMALL
122                     !isShadeLayoutWide && (hasNotifs || hasMedia) -> ClockSize.SMALL
123                     !isShadeLayoutWide -> ClockSize.LARGE
124                     hasMedia && !isDozing -> ClockSize.SMALL
125                     else -> ClockSize.LARGE
126                 }
127             }
128         } else {
129             keyguardClockRepository.clockSize
130         }
131 
132     val clockSize: StateFlow<ClockSize> =
133         selectedClockSize
134             .flatMapLatestConflated { selectedSize ->
135                 if (selectedSize == ClockSizeSetting.SMALL) {
136                     flowOf(ClockSize.SMALL)
137                 } else {
138                     dynamicClockSize
139                 }
140             }
141             .stateIn(
142                 scope = applicationScope,
143                 started = SharingStarted.Eagerly,
144                 initialValue = ClockSize.LARGE,
145             )
146 
147     val clockShouldBeCentered: Flow<Boolean> =
148         if (SceneContainerFlag.isEnabled) {
149             combine(
150                 shadeModeInteractor.isShadeLayoutWide,
151                 areAnyNotificationsPresent,
152                 isAodPromotedNotificationPresent,
153                 isOnAod,
154                 headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
155                 keyguardInteractor.isDozing,
156             ) {
157                 isShadeLayoutWide,
158                 areAnyNotificationsPresent,
159                 isAodPromotedNotificationPresent,
160                 isOnAod,
161                 isHeadsUp,
162                 isDozing ->
163                 when {
164                     !isShadeLayoutWide -> true
165                     !areAnyNotificationsPresent -> true
166                     // Pulsing notification appears on the right. Move clock left to avoid overlap.
167                     isHeadsUp && isDozing -> false
168                     isAodPromotedNotificationPresent -> false
169                     else -> isOnAod
170                 }
171             }
172         } else {
173             combine(
174                     shadeModeInteractor.isShadeLayoutWide,
175                     areAnyNotificationsPresent,
176                     isAodPromotedNotificationPresent,
177                     keyguardInteractor.dozeTransitionModel,
178                     keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to == AOD },
179                     keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
180                         it.to == LOCKSCREEN
181                     },
182                     keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
183                         it.to == DOZING
184                     },
185                     keyguardInteractor.isPulsing,
186                     keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to == GONE },
187                 ) {
188                     isShadeLayoutWide,
189                     areAnyNotificationsPresent,
190                     isAodPromotedNotificationPresent,
191                     dozeTransitionModel,
192                     startedToAod,
193                     startedToLockScreen,
194                     startedToDoze,
195                     isPulsing,
196                     startedToGone ->
197                     when {
198                         !isShadeLayoutWide -> true
199                         // [areAnyNotificationsPresent] also reacts to notification stack in
200                         // homescreen
201                         // it may cause unnecessary `false` emission when there's notification in
202                         // homescreen
203                         // but none in lockscreen when going from GONE to AOD / DOZING
204                         // use null to skip emitting wrong value
205                         startedToGone || startedToDoze -> null
206                         startedToLockScreen -> !areAnyNotificationsPresent
207                         startedToAod -> !(isPulsing || isAodPromotedNotificationPresent)
208                         else -> true
209                     }
210                 }
211                 .filterNotNull()
212         }
213 
214     fun setClockSize(size: ClockSize) {
215         SceneContainerFlag.assertInLegacyMode()
216         keyguardClockRepository.setClockSize(size)
217     }
218 
219     val renderedClockId: ClockId
220         get() {
221             return clock?.config?.id
222                 ?: run {
223                     Log.e(TAG, "No clock is available")
224                     "MISSING_CLOCK_ID"
225                 }
226         }
227 
228     fun handleFidgetTap(x: Float, y: Float) {
229         if (!com.android.systemui.Flags.clockFidgetAnimation()) {
230             return
231         }
232 
233         if (selectedClockSize.value == ClockSizeSetting.DYNAMIC) {
234             clockEventController.handleFidgetTap(x, y)
235         } else {
236             wallpaperFocalAreaInteractor.setTapPosition(x, y)
237         }
238     }
239 
240     fun animateFoldToAod(foldFraction: Float) {
241         clock?.let { clock ->
242             clock.smallClock.animations.fold(foldFraction)
243             clock.largeClock.animations.fold(foldFraction)
244         }
245     }
246 
247     fun setNotificationStackDefaultTop(top: Float) {
248         wallpaperFocalAreaInteractor.setNotificationDefaultTop(top)
249     }
250 }
251