Loading packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt +15 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin import com.android.systemui.biometrics.FaceHelpMessageDebouncer import com.android.systemui.biometrics.data.repository.FaceSensorInfo import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository Loading Loading @@ -75,11 +76,16 @@ class BouncerMessageViewModelTest : SysuiTestCase() { private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val bouncerInteractor by lazy { kosmos.bouncerInteractor } private lateinit var underTest: BouncerMessageViewModel private val ignoreHelpMessageId = 1 @Before fun setUp() { kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER)) kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true overrideResource( R.array.config_face_acquire_device_entry_ignorelist, intArrayOf(ignoreHelpMessageId) ) underTest = kosmos.bouncerMessageViewModel overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable") kosmos.fakeSystemPropertiesHelper.set( Loading Loading @@ -379,7 +385,15 @@ class BouncerMessageViewModelTest : SysuiTestCase() { runCurrent() kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus( HelpFaceAuthenticationStatus(1, "some helpful message") HelpFaceAuthenticationStatus(0, "some helpful message", 0) ) runCurrent() kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus( HelpFaceAuthenticationStatus( 0, "some helpful message", FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS ) ) runCurrent() assertThat(bouncerMessage?.text).isEqualTo("Enter PIN") Loading packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +0 −25 Original line number Diff line number Diff line Loading @@ -52,7 +52,6 @@ import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGER import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.dump.DumpManager Loading @@ -79,7 +78,6 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.testKosmos Loading Loading @@ -477,29 +475,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat((emittedValues.first() as ErrorFaceAuthenticationStatus).msgId).isEqualTo(-1) } @Test fun faceHelpMessagesAreIgnoredBasedOnConfig() = testScope.runTest { overrideResource( R.array.config_face_acquire_device_entry_ignorelist, intArrayOf(10, 11) ) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() allPreconditionsToRunFaceAuthAreTrue() underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() authenticationCallback.value.onAuthenticationHelp(9, "help msg") authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg") authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg") val response = authStatus() as HelpFaceAuthenticationStatus assertThat(response.msg).isEqualTo("help msg") assertThat(response.msgId).isEqualTo(response.msgId) } @Test fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() = testScope.runTest { Loading packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt 0 → 100644 +125 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.biometrics import android.util.Log import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus /** * Debounces face help messages with parameters: * - window: Window of time (in milliseconds) to analyze face acquired messages) * - startWindow: Window of time on start required before showing the first help message * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to * the user */ class FaceHelpMessageDebouncer( private val window: Long = DEFAULT_WINDOW_MS, private val startWindow: Long = window, private val shownFaceMessageFrequencyBoost: Int = 4, ) { private val TAG = "FaceHelpMessageDebouncer" private var startTime = 0L private var helpFaceAuthStatuses: MutableList<HelpFaceAuthenticationStatus> = mutableListOf() private var lastMessageIdShown: Int? = null /** Remove messages that are outside of the time [window]. */ private fun removeOldMessages(currTimestamp: Long) { var numToRemove = 0 // This works under the assumption that timestamps are ordered from first to last // in chronological order for (index in helpFaceAuthStatuses.indices) { if ((helpFaceAuthStatuses[index].createdAt + window) >= currTimestamp) { break // all timestamps from here and on are within the window } numToRemove += 1 } // Remove all outside time window repeat(numToRemove) { helpFaceAuthStatuses.removeFirst() } if (numToRemove > 0) { Log.v(TAG, "removedFirst=$numToRemove") } } private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? { // freqMap: msgId => frequency val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap() // Give shownFaceMessageFrequencyBoost to lastMessageIdShown if (lastMessageIdShown != null) { freqMap.computeIfPresent(lastMessageIdShown!!) { _, value -> value + shownFaceMessageFrequencyBoost } } // Go through all msgId keys & find the highest frequency msgId val msgIdWithHighestFrequency = freqMap.entries .maxWithOrNull { (msgId1, freq1), (msgId2, freq2) -> // ties are broken by more recent message if (freq1 == freq2) { helpFaceAuthStatuses .findLast { it.msgId == msgId1 }!! .createdAt .compareTo( helpFaceAuthStatuses.findLast { it.msgId == msgId2 }!!.createdAt ) } else { freq1.compareTo(freq2) } } ?.key return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency } } fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) { helpFaceAuthStatuses.add(helpFaceAuthStatus) Log.v(TAG, "added message=$helpFaceAuthStatus") } fun getMessageToShow(atTimestamp: Long): HelpFaceAuthenticationStatus? { if (helpFaceAuthStatuses.isEmpty() || (atTimestamp - startTime) < startWindow) { // there's not enough time that has passed to determine whether to show anything yet Log.v(TAG, "No message; haven't made initial threshold window OR no messages") return null } removeOldMessages(atTimestamp) val messageToShow = getMostFrequentHelpMessage() if (lastMessageIdShown != messageToShow?.msgId) { Log.v( TAG, "showMessage previousLastMessageId=$lastMessageIdShown" + "\n\tmessageToShow=$messageToShow " + "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" + "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses" ) lastMessageIdShown = messageToShow?.msgId } return messageToShow } fun startNewFaceAuthSession(faceAuthStartedTime: Long) { Log.d(TAG, "startNewFaceAuthSession at startTime=$startTime") startTime = faceAuthStartedTime helpFaceAuthStatuses.clear() lastMessageIdShown = null } companion object { const val DEFAULT_WINDOW_MS = 200L } } packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +2 −17 Original line number Diff line number Diff line Loading @@ -57,16 +57,13 @@ import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.SessionTracker import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.Arrays import java.util.concurrent.Executor import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope Loading Loading @@ -170,7 +167,6 @@ constructor( ) : DeviceEntryFaceAuthRepository, Dumpable { private var authCancellationSignal: CancellationSignal? = null private var detectCancellationSignal: CancellationSignal? = null private var faceAcquiredInfoIgnoreList: Set<Int> private var retryCount = 0 private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null) Loading Loading @@ -240,14 +236,6 @@ constructor( faceManager?.addLockoutResetCallback(faceLockoutResetCallback) faceAuthLogger.addLockoutResetCallbackDone() } faceAcquiredInfoIgnoreList = Arrays.stream( context.resources.getIntArray( R.array.config_face_acquire_device_entry_ignorelist ) ) .boxed() .collect(Collectors.toSet()) dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this) canRunFaceAuth = Loading Loading @@ -485,10 +473,8 @@ constructor( } override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) { if (faceAcquiredInfoIgnoreList.contains(code)) { return } _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString()) _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr?.toString()) } override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { Loading Loading @@ -731,7 +717,6 @@ constructor( pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}") pw.println(" authCancellationSignal: $authCancellationSignal") pw.println(" detectCancellationSignal: $detectCancellationSignal") pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList") pw.println(" _authenticationStatus: ${_authenticationStatus.value}") pw.println(" _detectionStatus: ${_detectionStatus.value}") pw.println(" currentUserId: $currentUserId") Loading packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.deviceentry.domain.interactor import android.content.res.Resources import android.hardware.biometrics.BiometricFaceConstants import com.android.systemui.biometrics.FaceHelpMessageDebouncer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.res.R import java.util.Arrays import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transform /** * Process face authentication statuses. * - Ignores face help messages based on R.array.config_face_acquire_device_entry_ignorelist. * - Uses FaceHelpMessageDebouncer to debounce flickery help messages. */ @SysUISingleton class DeviceEntryFaceAuthStatusInteractor @Inject constructor( repository: DeviceEntryFaceAuthRepository, @Main private val resources: Resources, @Application private val applicationScope: CoroutineScope, ) { private val faceHelpMessageDebouncer = FaceHelpMessageDebouncer() private var faceAcquiredInfoIgnoreList: Set<Int> = Arrays.stream(resources.getIntArray(R.array.config_face_acquire_device_entry_ignorelist)) .boxed() .collect(Collectors.toSet()) val authenticationStatus: StateFlow<FaceAuthenticationStatus?> = repository.authenticationStatus .transform { authenticationStatus -> if (authenticationStatus is AcquiredFaceAuthenticationStatus) { if ( authenticationStatus.acquiredInfo == BiometricFaceConstants.FACE_ACQUIRED_START ) { faceHelpMessageDebouncer.startNewFaceAuthSession( authenticationStatus.createdAt ) } } if (authenticationStatus is HelpFaceAuthenticationStatus) { if (!faceAcquiredInfoIgnoreList.contains(authenticationStatus.msgId)) { faceHelpMessageDebouncer.addMessage(authenticationStatus) } val messageToShow = faceHelpMessageDebouncer.getMessageToShow( atTimestamp = authenticationStatus.createdAt, ) if (messageToShow != null) { emit(messageToShow) } return@transform } emit(authenticationStatus) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = null, ) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt +15 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin import com.android.systemui.biometrics.FaceHelpMessageDebouncer import com.android.systemui.biometrics.data.repository.FaceSensorInfo import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository Loading Loading @@ -75,11 +76,16 @@ class BouncerMessageViewModelTest : SysuiTestCase() { private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val bouncerInteractor by lazy { kosmos.bouncerInteractor } private lateinit var underTest: BouncerMessageViewModel private val ignoreHelpMessageId = 1 @Before fun setUp() { kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER)) kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true overrideResource( R.array.config_face_acquire_device_entry_ignorelist, intArrayOf(ignoreHelpMessageId) ) underTest = kosmos.bouncerMessageViewModel overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable") kosmos.fakeSystemPropertiesHelper.set( Loading Loading @@ -379,7 +385,15 @@ class BouncerMessageViewModelTest : SysuiTestCase() { runCurrent() kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus( HelpFaceAuthenticationStatus(1, "some helpful message") HelpFaceAuthenticationStatus(0, "some helpful message", 0) ) runCurrent() kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus( HelpFaceAuthenticationStatus( 0, "some helpful message", FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS ) ) runCurrent() assertThat(bouncerMessage?.text).isEqualTo("Enter PIN") Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +0 −25 Original line number Diff line number Diff line Loading @@ -52,7 +52,6 @@ import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGER import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.dump.DumpManager Loading @@ -79,7 +78,6 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.testKosmos Loading Loading @@ -477,29 +475,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat((emittedValues.first() as ErrorFaceAuthenticationStatus).msgId).isEqualTo(-1) } @Test fun faceHelpMessagesAreIgnoredBasedOnConfig() = testScope.runTest { overrideResource( R.array.config_face_acquire_device_entry_ignorelist, intArrayOf(10, 11) ) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() allPreconditionsToRunFaceAuthAreTrue() underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() authenticationCallback.value.onAuthenticationHelp(9, "help msg") authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg") authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg") val response = authStatus() as HelpFaceAuthenticationStatus assertThat(response.msg).isEqualTo("help msg") assertThat(response.msgId).isEqualTo(response.msgId) } @Test fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() = testScope.runTest { Loading
packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt 0 → 100644 +125 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.biometrics import android.util.Log import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus /** * Debounces face help messages with parameters: * - window: Window of time (in milliseconds) to analyze face acquired messages) * - startWindow: Window of time on start required before showing the first help message * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to * the user */ class FaceHelpMessageDebouncer( private val window: Long = DEFAULT_WINDOW_MS, private val startWindow: Long = window, private val shownFaceMessageFrequencyBoost: Int = 4, ) { private val TAG = "FaceHelpMessageDebouncer" private var startTime = 0L private var helpFaceAuthStatuses: MutableList<HelpFaceAuthenticationStatus> = mutableListOf() private var lastMessageIdShown: Int? = null /** Remove messages that are outside of the time [window]. */ private fun removeOldMessages(currTimestamp: Long) { var numToRemove = 0 // This works under the assumption that timestamps are ordered from first to last // in chronological order for (index in helpFaceAuthStatuses.indices) { if ((helpFaceAuthStatuses[index].createdAt + window) >= currTimestamp) { break // all timestamps from here and on are within the window } numToRemove += 1 } // Remove all outside time window repeat(numToRemove) { helpFaceAuthStatuses.removeFirst() } if (numToRemove > 0) { Log.v(TAG, "removedFirst=$numToRemove") } } private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? { // freqMap: msgId => frequency val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap() // Give shownFaceMessageFrequencyBoost to lastMessageIdShown if (lastMessageIdShown != null) { freqMap.computeIfPresent(lastMessageIdShown!!) { _, value -> value + shownFaceMessageFrequencyBoost } } // Go through all msgId keys & find the highest frequency msgId val msgIdWithHighestFrequency = freqMap.entries .maxWithOrNull { (msgId1, freq1), (msgId2, freq2) -> // ties are broken by more recent message if (freq1 == freq2) { helpFaceAuthStatuses .findLast { it.msgId == msgId1 }!! .createdAt .compareTo( helpFaceAuthStatuses.findLast { it.msgId == msgId2 }!!.createdAt ) } else { freq1.compareTo(freq2) } } ?.key return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency } } fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) { helpFaceAuthStatuses.add(helpFaceAuthStatus) Log.v(TAG, "added message=$helpFaceAuthStatus") } fun getMessageToShow(atTimestamp: Long): HelpFaceAuthenticationStatus? { if (helpFaceAuthStatuses.isEmpty() || (atTimestamp - startTime) < startWindow) { // there's not enough time that has passed to determine whether to show anything yet Log.v(TAG, "No message; haven't made initial threshold window OR no messages") return null } removeOldMessages(atTimestamp) val messageToShow = getMostFrequentHelpMessage() if (lastMessageIdShown != messageToShow?.msgId) { Log.v( TAG, "showMessage previousLastMessageId=$lastMessageIdShown" + "\n\tmessageToShow=$messageToShow " + "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" + "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses" ) lastMessageIdShown = messageToShow?.msgId } return messageToShow } fun startNewFaceAuthSession(faceAuthStartedTime: Long) { Log.d(TAG, "startNewFaceAuthSession at startTime=$startTime") startTime = faceAuthStartedTime helpFaceAuthStatuses.clear() lastMessageIdShown = null } companion object { const val DEFAULT_WINDOW_MS = 200L } }
packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +2 −17 Original line number Diff line number Diff line Loading @@ -57,16 +57,13 @@ import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.SessionTracker import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.Arrays import java.util.concurrent.Executor import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope Loading Loading @@ -170,7 +167,6 @@ constructor( ) : DeviceEntryFaceAuthRepository, Dumpable { private var authCancellationSignal: CancellationSignal? = null private var detectCancellationSignal: CancellationSignal? = null private var faceAcquiredInfoIgnoreList: Set<Int> private var retryCount = 0 private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null) Loading Loading @@ -240,14 +236,6 @@ constructor( faceManager?.addLockoutResetCallback(faceLockoutResetCallback) faceAuthLogger.addLockoutResetCallbackDone() } faceAcquiredInfoIgnoreList = Arrays.stream( context.resources.getIntArray( R.array.config_face_acquire_device_entry_ignorelist ) ) .boxed() .collect(Collectors.toSet()) dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this) canRunFaceAuth = Loading Loading @@ -485,10 +473,8 @@ constructor( } override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) { if (faceAcquiredInfoIgnoreList.contains(code)) { return } _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString()) _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr?.toString()) } override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { Loading Loading @@ -731,7 +717,6 @@ constructor( pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}") pw.println(" authCancellationSignal: $authCancellationSignal") pw.println(" detectCancellationSignal: $detectCancellationSignal") pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList") pw.println(" _authenticationStatus: ${_authenticationStatus.value}") pw.println(" _detectionStatus: ${_detectionStatus.value}") pw.println(" currentUserId: $currentUserId") Loading
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.deviceentry.domain.interactor import android.content.res.Resources import android.hardware.biometrics.BiometricFaceConstants import com.android.systemui.biometrics.FaceHelpMessageDebouncer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.res.R import java.util.Arrays import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transform /** * Process face authentication statuses. * - Ignores face help messages based on R.array.config_face_acquire_device_entry_ignorelist. * - Uses FaceHelpMessageDebouncer to debounce flickery help messages. */ @SysUISingleton class DeviceEntryFaceAuthStatusInteractor @Inject constructor( repository: DeviceEntryFaceAuthRepository, @Main private val resources: Resources, @Application private val applicationScope: CoroutineScope, ) { private val faceHelpMessageDebouncer = FaceHelpMessageDebouncer() private var faceAcquiredInfoIgnoreList: Set<Int> = Arrays.stream(resources.getIntArray(R.array.config_face_acquire_device_entry_ignorelist)) .boxed() .collect(Collectors.toSet()) val authenticationStatus: StateFlow<FaceAuthenticationStatus?> = repository.authenticationStatus .transform { authenticationStatus -> if (authenticationStatus is AcquiredFaceAuthenticationStatus) { if ( authenticationStatus.acquiredInfo == BiometricFaceConstants.FACE_ACQUIRED_START ) { faceHelpMessageDebouncer.startNewFaceAuthSession( authenticationStatus.createdAt ) } } if (authenticationStatus is HelpFaceAuthenticationStatus) { if (!faceAcquiredInfoIgnoreList.contains(authenticationStatus.msgId)) { faceHelpMessageDebouncer.addMessage(authenticationStatus) } val messageToShow = faceHelpMessageDebouncer.getMessageToShow( atTimestamp = authenticationStatus.createdAt, ) if (messageToShow != null) { emit(messageToShow) } return@transform } emit(authenticationStatus) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = null, ) }