1 /* <lambda>null2 * Copyright (C) 2022 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.bouncer.domain.interactor 18 19 import android.util.Log 20 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository 21 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dagger.qualifiers.Application 24 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor 25 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 26 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 27 import com.android.systemui.keyguard.shared.model.KeyguardState 28 import com.android.systemui.scene.domain.interactor.SceneInteractor 29 import com.android.systemui.scene.shared.flag.SceneContainerFlag 30 import com.android.systemui.scene.shared.model.Scenes 31 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf 32 import com.android.systemui.util.time.SystemClock 33 import dagger.Lazy 34 import javax.inject.Inject 35 import kotlinx.coroutines.CoroutineScope 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.SharingStarted 38 import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed 39 import kotlinx.coroutines.flow.StateFlow 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.distinctUntilChanged 42 import kotlinx.coroutines.flow.flatMapLatest 43 import kotlinx.coroutines.flow.flowOf 44 import kotlinx.coroutines.flow.map 45 import kotlinx.coroutines.flow.onEach 46 import kotlinx.coroutines.flow.stateIn 47 48 /** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */ 49 @SysUISingleton 50 class AlternateBouncerInteractor 51 @Inject 52 constructor( 53 private val bouncerRepository: KeyguardBouncerRepository, 54 fingerprintPropertyRepository: FingerprintPropertyRepository, 55 private val systemClock: SystemClock, 56 private val deviceEntryBiometricsAllowedInteractor: 57 Lazy<DeviceEntryBiometricsAllowedInteractor>, 58 private val keyguardInteractor: Lazy<KeyguardInteractor>, 59 keyguardTransitionInteractor: Lazy<KeyguardTransitionInteractor>, 60 sceneInteractor: Lazy<SceneInteractor>, 61 @Application scope: CoroutineScope, 62 ) { 63 var receivedDownTouch = false 64 val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible 65 private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet() 66 val alternateBouncerSupported: StateFlow<Boolean> = 67 fingerprintPropertyRepository.sensorType 68 .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() } 69 .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = false) 70 71 private val isDozingOrAod: Flow<Boolean> = 72 anyOf( 73 keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map { 74 it > 0f 75 }, 76 keyguardTransitionInteractor.get().transitionValue(KeyguardState.AOD).map { 77 it > 0f 78 }, 79 ) 80 .distinctUntilChanged() 81 82 /** 83 * Whether the current biometric, bouncer, and keyguard states allow the alternate bouncer to 84 * show. 85 */ 86 val canShowAlternateBouncer: StateFlow<Boolean> = 87 alternateBouncerSupported 88 .flatMapLatest { alternateBouncerSupported -> 89 if (alternateBouncerSupported) { 90 combine( 91 keyguardTransitionInteractor.get().currentKeyguardState, 92 sceneInteractor.get().currentScene, 93 ::Pair, 94 ) 95 .flatMapLatest { (currentKeyguardState, transitionState) -> 96 if (currentKeyguardState == KeyguardState.GONE) { 97 flowOf(false) 98 } else if ( 99 SceneContainerFlag.isEnabled && transitionState == Scenes.Gone 100 ) { 101 flowOf(false) 102 } else { 103 combine( 104 deviceEntryBiometricsAllowedInteractor 105 .get() 106 .isFingerprintAuthCurrentlyAllowed, 107 keyguardInteractor.get().isKeyguardDismissible, 108 bouncerRepository.primaryBouncerShow, 109 isDozingOrAod, 110 ) { 111 fingerprintAllowed, 112 keyguardDismissible, 113 primaryBouncerShowing, 114 dozing -> 115 fingerprintAllowed && 116 !keyguardDismissible && 117 !primaryBouncerShowing && 118 !dozing 119 } 120 } 121 } 122 } else { 123 flowOf(false) 124 } 125 } 126 .distinctUntilChanged() 127 .onEach { Log.d(TAG, "canShowAlternateBouncer changed to $it") } 128 .stateIn(scope = scope, started = WhileSubscribed(), initialValue = false) 129 130 /** 131 * Always shows the alternate bouncer. Requesters must check [canShowAlternateBouncer]` before 132 * calling this. 133 */ 134 fun forceShow() { 135 bouncerRepository.setAlternateVisible(true) 136 } 137 138 /** 139 * Sets the correct bouncer states to hide the bouncer. Should only be called through 140 * StatusBarKeyguardViewManager until ScrimController is refactored to use 141 * alternateBouncerInteractor. 142 * 143 * @return true if the alternate bouncer was newly hidden, else false. 144 */ 145 fun hide(): Boolean { 146 receivedDownTouch = false 147 val wasAlternateBouncerVisible = isVisibleState() 148 bouncerRepository.setAlternateVisible(false) 149 return wasAlternateBouncerVisible && !isVisibleState() 150 } 151 152 fun isVisibleState(): Boolean { 153 return bouncerRepository.alternateBouncerVisible.value 154 } 155 156 fun canShowAlternateBouncerForFingerprint(): Boolean { 157 return canShowAlternateBouncer.value 158 } 159 160 /** 161 * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the 162 * alternate bouncer and show the primary bouncer. 163 */ 164 fun hasAlternateBouncerShownWithMinTime(): Boolean { 165 return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) > 166 MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS 167 } 168 169 /** 170 * Should only be called through StatusBarKeyguardViewManager which propagates the source of 171 * truth to other concerned controllers. Will hide the alternate bouncer if it's no longer 172 * allowed to show. 173 * 174 * @return true if the alternate bouncer was newly hidden, else false. 175 */ 176 fun maybeHide(): Boolean { 177 if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) { 178 return hide() 179 } 180 return false 181 } 182 183 companion object { 184 private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L 185 186 private const val TAG = "AlternateBouncerInteractor" 187 } 188 } 189