1 /* <lambda>null2 * Copyright (C) 2024 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.deviceentry.domain.interactor 18 19 import android.content.Context 20 import android.content.Intent 21 import com.android.app.tracing.coroutines.launchTraced as launch 22 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 23 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor 24 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.deviceentry.shared.model.BiometricMessage 28 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage 29 import com.android.systemui.keyguard.KeyguardWmStateRefactor 30 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository 31 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 32 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 33 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus 34 import com.android.systemui.keyguard.shared.model.KeyguardState 35 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus 36 import com.android.systemui.plugins.ActivityStarter 37 import com.android.systemui.power.domain.interactor.PowerInteractor 38 import com.android.systemui.res.R 39 import com.android.systemui.util.kotlin.combine 40 import com.android.systemui.util.kotlin.sample 41 import javax.inject.Inject 42 import kotlinx.coroutines.CoroutineScope 43 import kotlinx.coroutines.flow.Flow 44 import kotlinx.coroutines.flow.combine 45 import kotlinx.coroutines.flow.distinctUntilChanged 46 import kotlinx.coroutines.flow.emptyFlow 47 import kotlinx.coroutines.flow.filter 48 import kotlinx.coroutines.flow.filterNot 49 import kotlinx.coroutines.flow.flatMapLatest 50 import kotlinx.coroutines.flow.flowOf 51 import kotlinx.coroutines.flow.map 52 53 /** Business logic for handling authentication events when an app is occluding the lockscreen. */ 54 @SysUISingleton 55 class OccludingAppDeviceEntryInteractor 56 @Inject 57 constructor( 58 biometricMessageInteractor: BiometricMessageInteractor, 59 fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, 60 keyguardInteractor: KeyguardInteractor, 61 primaryBouncerInteractor: PrimaryBouncerInteractor, 62 alternateBouncerInteractor: AlternateBouncerInteractor, 63 @Application scope: CoroutineScope, 64 private val context: Context, 65 activityStarter: ActivityStarter, 66 powerInteractor: PowerInteractor, 67 keyguardTransitionInteractor: KeyguardTransitionInteractor, 68 communalSceneInteractor: CommunalSceneInteractor, 69 ) { 70 private val keyguardOccludedByApp: Flow<Boolean> = 71 if (KeyguardWmStateRefactor.isEnabled) { 72 combine( 73 keyguardTransitionInteractor.currentKeyguardState, 74 communalSceneInteractor.isIdleOnCommunal, 75 ::Pair, 76 ) 77 .map { (currentState, isIdleOnCommunal) -> 78 currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal 79 } 80 } else { 81 combine( 82 keyguardInteractor.isKeyguardOccluded, 83 keyguardInteractor.isKeyguardShowing, 84 primaryBouncerInteractor.isShowing, 85 alternateBouncerInteractor.isVisible, 86 keyguardInteractor.isDozing, 87 communalSceneInteractor.isIdleOnCommunal, 88 ) { 89 occluded, 90 showing, 91 primaryBouncerShowing, 92 alternateBouncerVisible, 93 dozing, 94 isIdleOnCommunal -> 95 occluded && 96 showing && 97 !primaryBouncerShowing && 98 !alternateBouncerVisible && 99 !dozing && 100 !isIdleOnCommunal 101 } 102 .distinctUntilChanged() 103 } 104 105 private val fingerprintUnlockSuccessEvents: Flow<Unit> = 106 fingerprintAuthRepository.authenticationStatus 107 .ifKeyguardOccludedByApp() 108 .filter { it is SuccessFingerprintAuthenticationStatus } 109 .map {} // maps FingerprintAuthenticationStatus => Unit 110 private val fingerprintLockoutEvents: Flow<Unit> = 111 fingerprintAuthRepository.authenticationStatus 112 .ifKeyguardOccludedByApp() 113 .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutError() } 114 .map {} // maps FingerprintAuthenticationStatus => Unit 115 val message: Flow<BiometricMessage?> = 116 biometricMessageInteractor.fingerprintMessage 117 .filterNot { fingerprintMessage -> 118 // On lockout, the device will show the bouncer. Let's not show the message 119 // before the transition or else it'll look flickery. 120 fingerprintMessage is FingerprintLockoutMessage 121 } 122 .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null)) 123 124 init { 125 // This seems undesirable in most cases, except when a video is playing and can PiP when 126 // unlocked. It was originally added for tablets, so allow it there 127 if (context.resources.getBoolean(R.bool.config_goToHomeFromOccludedApps)) { 128 scope.launch { 129 // On fingerprint success when the screen is on and not dreaming, go to the home 130 // screen 131 fingerprintUnlockSuccessEvents 132 .sample( 133 combine( 134 powerInteractor.isInteractive, 135 keyguardInteractor.isDreaming, 136 ::Pair, 137 ) 138 ) 139 .collect { (interactive, dreaming) -> 140 if (interactive && !dreaming) { 141 goToHomeScreen() 142 } 143 // don't go to the home screen if the authentication is from 144 // AOD/dozing/off/dreaming 145 } 146 } 147 } 148 149 scope.launch { 150 // On device fingerprint lockout, request the bouncer with a runnable to 151 // go to the home screen. Without this, the bouncer won't proceed to the home 152 // screen. 153 fingerprintLockoutEvents.collect { 154 activityStarter.dismissKeyguardThenExecute( 155 object : ActivityStarter.OnDismissAction { 156 override fun onDismiss(): Boolean { 157 goToHomeScreen() 158 return false 159 } 160 161 override fun willRunAnimationOnKeyguard(): Boolean { 162 return false 163 } 164 }, 165 /* cancel= */ null, 166 /* afterKeyguardGone */ false, 167 ) 168 } 169 } 170 } 171 172 /** Launches an Activity which forces the current app to background by going home. */ 173 private fun goToHomeScreen() { 174 context.startActivity( 175 Intent(Intent.ACTION_MAIN).apply { 176 addCategory(Intent.CATEGORY_HOME) 177 flags = Intent.FLAG_ACTIVITY_NEW_TASK 178 } 179 ) 180 } 181 182 private fun <T> Flow<T>.ifKeyguardOccludedByApp(elseFlow: Flow<T> = emptyFlow()): Flow<T> { 183 return keyguardOccludedByApp.flatMapLatest { keyguardOccludedByApp -> 184 if (keyguardOccludedByApp) { 185 this 186 } else { 187 elseFlow 188 } 189 } 190 } 191 } 192