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.domain.interactor 18 19 import android.content.Context 20 import android.content.Intent 21 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 22 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dagger.qualifiers.Application 25 import com.android.systemui.flags.FeatureFlags 26 import com.android.systemui.flags.Flags 27 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository 28 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus 29 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus 30 import com.android.systemui.plugins.ActivityStarter 31 import com.android.systemui.power.domain.interactor.PowerInteractor 32 import com.android.systemui.util.kotlin.sample 33 import javax.inject.Inject 34 import kotlinx.coroutines.CoroutineScope 35 import kotlinx.coroutines.ExperimentalCoroutinesApi 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.combine 38 import kotlinx.coroutines.flow.distinctUntilChanged 39 import kotlinx.coroutines.flow.emptyFlow 40 import kotlinx.coroutines.flow.filter 41 import kotlinx.coroutines.flow.filterNot 42 import kotlinx.coroutines.flow.flatMapLatest 43 import kotlinx.coroutines.flow.flowOf 44 import kotlinx.coroutines.flow.map 45 import kotlinx.coroutines.flow.merge 46 import kotlinx.coroutines.launch 47 48 /** Business logic for handling authentication events when an app is occluding the lockscreen. */ 49 @ExperimentalCoroutinesApi 50 @SysUISingleton 51 class OccludingAppDeviceEntryInteractor 52 @Inject 53 constructor( 54 biometricMessageInteractor: BiometricMessageInteractor, 55 fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, 56 keyguardInteractor: KeyguardInteractor, 57 primaryBouncerInteractor: PrimaryBouncerInteractor, 58 alternateBouncerInteractor: AlternateBouncerInteractor, 59 @Application scope: CoroutineScope, 60 private val context: Context, 61 activityStarter: ActivityStarter, 62 powerInteractor: PowerInteractor, 63 featureFlags: FeatureFlags, 64 ) { 65 private val keyguardOccludedByApp: Flow<Boolean> = 66 combine( 67 keyguardInteractor.isKeyguardOccluded, 68 keyguardInteractor.isKeyguardShowing, 69 primaryBouncerInteractor.isShowing, 70 alternateBouncerInteractor.isVisible, 71 ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible -> 72 occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible 73 } 74 .distinctUntilChanged() 75 private val fingerprintUnlockSuccessEvents: Flow<Unit> = 76 fingerprintAuthRepository.authenticationStatus 77 .ifKeyguardOccludedByApp() 78 .filter { it is SuccessFingerprintAuthenticationStatus } 79 .map {} // maps FingerprintAuthenticationStatus => Unit 80 private val fingerprintLockoutEvents: Flow<Unit> = 81 fingerprintAuthRepository.authenticationStatus 82 .ifKeyguardOccludedByApp() 83 .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutMessage() } 84 .map {} // maps FingerprintAuthenticationStatus => Unit 85 val message: Flow<BiometricMessage?> = 86 merge( 87 biometricMessageInteractor.fingerprintErrorMessage.filterNot { 88 it.isFingerprintLockoutMessage() 89 }, 90 biometricMessageInteractor.fingerprintFailMessage, 91 biometricMessageInteractor.fingerprintHelpMessage, 92 ) 93 .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null)) 94 95 init { 96 if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) { 97 scope.launch { 98 // On fingerprint success when the screen is on, go to the home screen 99 fingerprintUnlockSuccessEvents.sample( 100 combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair), 101 ) 102 .collect { (interactive, dreaming) -> 103 if (interactive && !dreaming) { 104 goToHomeScreen() 105 } 106 // don't go to the home screen if the authentication is from 107 // AOD/dozing/off/dreaming 108 } 109 } 110 111 scope.launch { 112 // On device fingerprint lockout, request the bouncer with a runnable to 113 // go to the home screen. Without this, the bouncer won't proceed to the home 114 // screen. 115 fingerprintLockoutEvents.collect { 116 activityStarter.dismissKeyguardThenExecute( 117 object : ActivityStarter.OnDismissAction { 118 override fun onDismiss(): Boolean { 119 goToHomeScreen() 120 return false 121 } 122 123 override fun willRunAnimationOnKeyguard(): Boolean { 124 return false 125 } 126 }, 127 /* cancel= */ null, 128 /* afterKeyguardGone */ false 129 ) 130 } 131 } 132 } 133 } 134 135 /** Launches an Activity which forces the current app to background by going home. */ 136 private fun goToHomeScreen() { 137 context.startActivity( 138 Intent(Intent.ACTION_MAIN).apply { 139 addCategory(Intent.CATEGORY_HOME) 140 flags = Intent.FLAG_ACTIVITY_NEW_TASK 141 } 142 ) 143 } 144 145 private fun <T> Flow<T>.ifKeyguardOccludedByApp(elseFlow: Flow<T> = emptyFlow()): Flow<T> { 146 return keyguardOccludedByApp.flatMapLatest { keyguardOccludedByApp -> 147 if (keyguardOccludedByApp) { 148 this 149 } else { 150 elseFlow 151 } 152 } 153 } 154 } 155