1 /* 2 * 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 package com.android.systemui.keyguard.ui.viewmodel 18 19 import android.content.Context 20 import androidx.annotation.ColorInt 21 import com.android.settingslib.Utils.getColorAttrDefaultColor 22 import com.android.systemui.R 23 import com.android.systemui.keyguard.domain.interactor.BurnInOffsets 24 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 25 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 26 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor 27 import com.android.systemui.keyguard.shared.model.KeyguardState 28 import com.android.systemui.keyguard.shared.model.StatusBarState 29 import com.android.wm.shell.animation.Interpolators 30 import javax.inject.Inject 31 import kotlin.math.roundToInt 32 import kotlinx.coroutines.ExperimentalCoroutinesApi 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.combine 35 import kotlinx.coroutines.flow.flatMapLatest 36 import kotlinx.coroutines.flow.map 37 import kotlinx.coroutines.flow.merge 38 39 /** View-model for UDFPS lockscreen views. */ 40 @ExperimentalCoroutinesApi 41 open class UdfpsLockscreenViewModel( 42 context: Context, 43 lockscreenColorResId: Int, 44 alternateBouncerColorResId: Int, 45 transitionInteractor: KeyguardTransitionInteractor, 46 udfpsKeyguardInteractor: UdfpsKeyguardInteractor, 47 keyguardInteractor: KeyguardInteractor, 48 ) { 49 private val toLockscreen: Flow<TransitionViewModel> = 50 transitionInteractor.anyStateToLockscreenTransition.map { 51 TransitionViewModel( 52 alpha = 53 if (it.from == KeyguardState.AOD) { 54 it.value // animate 55 } else { 56 1f 57 }, 58 scale = 1f, 59 color = getColorAttrDefaultColor(context, lockscreenColorResId), 60 ) 61 } 62 63 private val toAlternateBouncer: Flow<TransitionViewModel> = 64 keyguardInteractor.statusBarState.flatMapLatest { statusBarState -> 65 transitionInteractor.anyStateToAlternateBouncerTransition.map { 66 TransitionViewModel( 67 alpha = 1f, 68 scale = 69 if (visibleInKeyguardState(it.from, statusBarState)) { 70 1f 71 } else { 72 Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) 73 }, 74 color = getColorAttrDefaultColor(context, alternateBouncerColorResId), 75 ) 76 } 77 } 78 79 private val fadeOut: Flow<TransitionViewModel> = 80 keyguardInteractor.statusBarState.flatMapLatest { statusBarState -> 81 merge( 82 transitionInteractor.anyStateToGoneTransition, 83 transitionInteractor.anyStateToAodTransition, 84 transitionInteractor.anyStateToOccludedTransition, 85 transitionInteractor.anyStateToPrimaryBouncerTransition, 86 transitionInteractor.anyStateToDreamingTransition, 87 ) 88 .map { 89 TransitionViewModel( 90 alpha = 91 if (visibleInKeyguardState(it.from, statusBarState)) { 92 1f - it.value 93 } else { 94 0f 95 }, 96 scale = 1f, 97 color = 98 if (it.from == KeyguardState.ALTERNATE_BOUNCER) { 99 getColorAttrDefaultColor(context, alternateBouncerColorResId) 100 } else { 101 getColorAttrDefaultColor(context, lockscreenColorResId) 102 }, 103 ) 104 } 105 } 106 107 private fun visibleInKeyguardState( 108 state: KeyguardState, 109 statusBarState: StatusBarState 110 ): Boolean { 111 return when (state) { 112 KeyguardState.OFF, 113 KeyguardState.DOZING, 114 KeyguardState.DREAMING, 115 KeyguardState.DREAMING_LOCKSCREEN_HOSTED, 116 KeyguardState.AOD, 117 KeyguardState.PRIMARY_BOUNCER, 118 KeyguardState.GONE, 119 KeyguardState.OCCLUDED -> false 120 KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD 121 KeyguardState.ALTERNATE_BOUNCER -> true 122 } 123 } 124 125 private val keyguardStateTransition = 126 merge( 127 toAlternateBouncer, 128 toLockscreen, 129 fadeOut, 130 ) 131 132 private val dialogHideAffordancesAlphaMultiplier: Flow<Float> = 133 udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances -> 134 if (hideAffordances) { 135 0f 136 } else { 137 1f 138 } 139 } 140 141 private val alphaMultiplier: Flow<Float> = 142 combine( 143 transitionInteractor.startedKeyguardState, 144 dialogHideAffordancesAlphaMultiplier, 145 udfpsKeyguardInteractor.shadeExpansion, 146 udfpsKeyguardInteractor.qsProgress, 147 ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress 148 -> 149 if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) { 150 1f 151 } else { 152 dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress) 153 } 154 } 155 156 val transition: Flow<TransitionViewModel> = 157 combine( 158 alphaMultiplier, 159 keyguardStateTransition, 160 ) { alphaMultiplier, keyguardStateTransition -> 161 TransitionViewModel( 162 alpha = keyguardStateTransition.alpha * alphaMultiplier, 163 scale = keyguardStateTransition.scale, 164 color = keyguardStateTransition.color, 165 ) 166 } 167 val visible: Flow<Boolean> = transition.map { it.alpha != 0f } 168 } 169 170 @ExperimentalCoroutinesApi 171 class FingerprintViewModel 172 @Inject 173 constructor( 174 val context: Context, 175 transitionInteractor: KeyguardTransitionInteractor, 176 interactor: UdfpsKeyguardInteractor, 177 keyguardInteractor: KeyguardInteractor, 178 ) : 179 UdfpsLockscreenViewModel( 180 context, 181 android.R.attr.textColorPrimary, 182 com.android.internal.R.attr.materialColorOnPrimaryFixed, 183 transitionInteractor, 184 interactor, 185 keyguardInteractor, 186 ) { 187 val dozeAmount: Flow<Float> = interactor.dozeAmount 188 val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets 189 190 // Padding between the fingerprint icon and its bounding box in pixels. 191 val padding: Flow<Int> = 192 interactor.scaleForResolution.map { scale -> 193 (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) 194 .roundToInt() 195 } 196 } 197 198 @ExperimentalCoroutinesApi 199 class BackgroundViewModel 200 @Inject 201 constructor( 202 val context: Context, 203 transitionInteractor: KeyguardTransitionInteractor, 204 interactor: UdfpsKeyguardInteractor, 205 keyguardInteractor: KeyguardInteractor, 206 ) : 207 UdfpsLockscreenViewModel( 208 context, 209 com.android.internal.R.attr.colorSurface, 210 com.android.internal.R.attr.materialColorPrimaryFixed, 211 transitionInteractor, 212 interactor, 213 keyguardInteractor, 214 ) 215 216 data class TransitionViewModel( 217 val alpha: Float, 218 val scale: Float, 219 @ColorInt val color: Int, 220 ) 221