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