Loading packages/SystemUI/res/values/config.xml +27 −0 Original line number Diff line number Diff line Loading @@ -611,6 +611,33 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> <!-- Messages that should NOT be shown to the user during face authentication on keyguard. This includes both lockscreen and bouncer. This should be used to hide messages that may be too chatty or messages that the user can't do much about. Entries are defined in android.hardware.biometrics.face@1.0 types.hal. Although not visibly shown to the user, these acquired messages (sent per face auth frame) are still counted towards the total frames to determine whether a deferred message (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on face timeout. --> <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" > </integer-array> <!-- Which face help messages to defer until face auth times out. If face auth is cancelled or ends on another error, then the message is never surfaced. May also never surface if it doesn't meet a threshold % of authentication frames specified by. config_face_help_msgs_defer_until_timeout_threshold. --> <integer-array name="config_face_help_msgs_defer_until_timeout"> </integer-array> <!-- Percentage of face auth frames received required to show a deferred message at FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages that are deferred.--> <item name="config_face_help_msgs_defer_until_timeout_threshold" translatable="false" format="float" type="dimen"> .75 </item> <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> Loading packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +12 −0 Original line number Diff line number Diff line Loading @@ -157,6 +157,7 @@ import com.google.android.collect.Lists; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; Loading @@ -167,6 +168,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; Loading Loading @@ -281,6 +283,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; private final UiEventLogger mUiEventLogger; private final Set<Integer> mFaceAcquiredInfoIgnoreList; private int mStatusBarState; private final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { Loading Loading @@ -1023,6 +1026,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAuthFailed() { Assert.isMainThread(); mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { Loading Loading @@ -1639,6 +1643,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { return; } handleFaceHelp(helpMsgId, helpString.toString()); } Loading Loading @@ -1931,6 +1938,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mWakeOnFingerprintAcquiredStart = context.getResources() .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); mFaceAcquiredInfoIgnoreList = Arrays.stream( mContext.getResources().getIntArray( R.array.config_face_acquire_device_entry_ignorelist)) .boxed() .collect(Collectors.toSet()); mHandler = new Handler(mainLooper) { @Override Loading packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.keyguard.logging import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.dagger.BiometricMessagesLog import javax.inject.Inject /** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ @SysUISingleton class FaceMessageDeferralLogger @Inject constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) : BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger") open class BiometricMessageDeferralLogger( private val logBuffer: LogBuffer, private val tag: String ) { fun reset() { logBuffer.log(tag, DEBUG, "reset") } fun logUpdateMessage(acquiredInfo: Int, helpString: String) { logBuffer.log( tag, DEBUG, { int1 = acquiredInfo str1 = helpString }, { "updateMessage acquiredInfo=$int1 helpString=$str1" } ) } fun logFrameProcessed( acquiredInfo: Int, totalFrames: Int, mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold ) { logBuffer.log( tag, DEBUG, { int1 = acquiredInfo int2 = totalFrames str1 = mostFrequentAcquiredInfoToDefer }, { "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " + "messageToShowOnTimeout=$str1" } ) } } packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.ktdeleted 100644 → 0 +0 −89 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 /** * Provides whether an acquired error message should be shown immediately when its received (see * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage]. * @property excludedMessages messages that are excluded from counts * @property messagesToDefer messages that shouldn't show immediately when received, but may be * shown later if the message is the most frequent message processed and meets [THRESHOLD] * percentage of all messages (excluding [excludedMessages]) */ class BiometricMessageDeferral( private val excludedMessages: Set<Int>, private val messagesToDefer: Set<Int> ) { private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message private var totalRelevantMessages = 0 private var mostFrequentMsgIdToDefer: Int? = null /** Reset all saved counts. */ fun reset() { totalRelevantMessages = 0 msgCounts.clear() msgIdToCharSequence.clear() } /** Whether the given message should be deferred instead of being shown immediately. */ fun shouldDefer(acquiredMsgId: Int): Boolean { return messagesToDefer.contains(acquiredMsgId) } /** * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count * messages that shouldn't be deferred in these counts. */ fun processMessage(acquiredMsgId: Int, helpString: CharSequence) { if (excludedMessages.contains(acquiredMsgId)) { return } totalRelevantMessages++ msgIdToCharSequence[acquiredMsgId] = helpString val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1 msgCounts[acquiredMsgId] = newAcquiredMsgCount if ( messagesToDefer.contains(acquiredMsgId) && (mostFrequentMsgIdToDefer == null || newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0)) ) { mostFrequentMsgIdToDefer = acquiredMsgId } } /** * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed * messages excluding [excludedMessages]. * @return null if no messages have been deferred OR deferred messages didn't meet the * [THRESHOLD] percentage of messages to show. */ fun getDeferredMessage(): CharSequence? { mostFrequentMsgIdToDefer?.let { if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) { return msgIdToCharSequence[mostFrequentMsgIdToDefer] } } return null } companion object { const val THRESHOLD = .5f } } packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt 0 → 100644 +141 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.content.res.Resources import com.android.keyguard.logging.BiometricMessageDeferralLogger import com.android.keyguard.logging.FaceMessageDeferralLogger import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import java.io.PrintWriter import java.util.* import javax.inject.Inject /** * Provides whether a face acquired help message should be shown immediately when its received or * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage]. */ @SysUISingleton class FaceHelpMessageDeferral @Inject constructor( @Main resources: Resources, logBuffer: FaceMessageDeferralLogger, dumpManager: DumpManager ) : BiometricMessageDeferral( resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(), resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), logBuffer, dumpManager ) /** * @property messagesToDefer messages that shouldn't show immediately when received, but may be * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] * percentage of all passed acquired frames. */ open class BiometricMessageDeferral( private val messagesToDefer: Set<Int>, private val threshold: Float, private val logBuffer: BiometricMessageDeferralLogger, dumpManager: DumpManager ) : Dumpable { private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() private var mostFrequentAcquiredInfoToDefer: Int? = null private var totalFrames = 0 init { dumpManager.registerDumpable(this.javaClass.name, this) } override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("messagesToDefer=$messagesToDefer") pw.println("totalFrames=$totalFrames") pw.println("threshold=$threshold") } /** Reset all saved counts. */ fun reset() { totalFrames = 0 mostFrequentAcquiredInfoToDefer = null acquiredInfoToFrequency.clear() acquiredInfoToHelpString.clear() logBuffer.reset() } /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ fun updateMessage(acquiredInfo: Int, helpString: String) { if (!messagesToDefer.contains(acquiredInfo)) { return } if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { logBuffer.logUpdateMessage(acquiredInfo, helpString) acquiredInfoToHelpString[acquiredInfo] = helpString } } /** Whether the given message should be deferred instead of being shown immediately. */ fun shouldDefer(acquiredMsgId: Int): Boolean { return messagesToDefer.contains(acquiredMsgId) } /** Adds the acquiredInfo frame to the counts. We account for all frames. */ fun processFrame(acquiredInfo: Int) { if (messagesToDefer.isEmpty()) { return } totalFrames++ val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount if ( messagesToDefer.contains(acquiredInfo) && (mostFrequentAcquiredInfoToDefer == null || newAcquiredInfoCount > acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) ) { mostFrequentAcquiredInfoToDefer = acquiredInfo } logBuffer.logFrameProcessed( acquiredInfo, totalFrames, mostFrequentAcquiredInfoToDefer?.toString() ) } /** * Get the most frequent deferred message that meets the [threshold] percentage of processed * frames. * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the * [threshold] percentage. */ fun getDeferredMessage(): CharSequence? { mostFrequentAcquiredInfoToDefer?.let { if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { return acquiredInfoToHelpString[it] } } return null } } Loading
packages/SystemUI/res/values/config.xml +27 −0 Original line number Diff line number Diff line Loading @@ -611,6 +611,33 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> <!-- Messages that should NOT be shown to the user during face authentication on keyguard. This includes both lockscreen and bouncer. This should be used to hide messages that may be too chatty or messages that the user can't do much about. Entries are defined in android.hardware.biometrics.face@1.0 types.hal. Although not visibly shown to the user, these acquired messages (sent per face auth frame) are still counted towards the total frames to determine whether a deferred message (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on face timeout. --> <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" > </integer-array> <!-- Which face help messages to defer until face auth times out. If face auth is cancelled or ends on another error, then the message is never surfaced. May also never surface if it doesn't meet a threshold % of authentication frames specified by. config_face_help_msgs_defer_until_timeout_threshold. --> <integer-array name="config_face_help_msgs_defer_until_timeout"> </integer-array> <!-- Percentage of face auth frames received required to show a deferred message at FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages that are deferred.--> <item name="config_face_help_msgs_defer_until_timeout_threshold" translatable="false" format="float" type="dimen"> .75 </item> <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> Loading
packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +12 −0 Original line number Diff line number Diff line Loading @@ -157,6 +157,7 @@ import com.google.android.collect.Lists; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; Loading @@ -167,6 +168,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; Loading Loading @@ -281,6 +283,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; private final UiEventLogger mUiEventLogger; private final Set<Integer> mFaceAcquiredInfoIgnoreList; private int mStatusBarState; private final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { Loading Loading @@ -1023,6 +1026,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAuthFailed() { Assert.isMainThread(); mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { Loading Loading @@ -1639,6 +1643,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { return; } handleFaceHelp(helpMsgId, helpString.toString()); } Loading Loading @@ -1931,6 +1938,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mWakeOnFingerprintAcquiredStart = context.getResources() .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); mFaceAcquiredInfoIgnoreList = Arrays.stream( mContext.getResources().getIntArray( R.array.config_face_acquire_device_entry_ignorelist)) .boxed() .collect(Collectors.toSet()); mHandler = new Handler(mainLooper) { @Override Loading
packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.keyguard.logging import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.dagger.BiometricMessagesLog import javax.inject.Inject /** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ @SysUISingleton class FaceMessageDeferralLogger @Inject constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) : BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger") open class BiometricMessageDeferralLogger( private val logBuffer: LogBuffer, private val tag: String ) { fun reset() { logBuffer.log(tag, DEBUG, "reset") } fun logUpdateMessage(acquiredInfo: Int, helpString: String) { logBuffer.log( tag, DEBUG, { int1 = acquiredInfo str1 = helpString }, { "updateMessage acquiredInfo=$int1 helpString=$str1" } ) } fun logFrameProcessed( acquiredInfo: Int, totalFrames: Int, mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold ) { logBuffer.log( tag, DEBUG, { int1 = acquiredInfo int2 = totalFrames str1 = mostFrequentAcquiredInfoToDefer }, { "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " + "messageToShowOnTimeout=$str1" } ) } }
packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.ktdeleted 100644 → 0 +0 −89 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 /** * Provides whether an acquired error message should be shown immediately when its received (see * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage]. * @property excludedMessages messages that are excluded from counts * @property messagesToDefer messages that shouldn't show immediately when received, but may be * shown later if the message is the most frequent message processed and meets [THRESHOLD] * percentage of all messages (excluding [excludedMessages]) */ class BiometricMessageDeferral( private val excludedMessages: Set<Int>, private val messagesToDefer: Set<Int> ) { private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message private var totalRelevantMessages = 0 private var mostFrequentMsgIdToDefer: Int? = null /** Reset all saved counts. */ fun reset() { totalRelevantMessages = 0 msgCounts.clear() msgIdToCharSequence.clear() } /** Whether the given message should be deferred instead of being shown immediately. */ fun shouldDefer(acquiredMsgId: Int): Boolean { return messagesToDefer.contains(acquiredMsgId) } /** * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count * messages that shouldn't be deferred in these counts. */ fun processMessage(acquiredMsgId: Int, helpString: CharSequence) { if (excludedMessages.contains(acquiredMsgId)) { return } totalRelevantMessages++ msgIdToCharSequence[acquiredMsgId] = helpString val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1 msgCounts[acquiredMsgId] = newAcquiredMsgCount if ( messagesToDefer.contains(acquiredMsgId) && (mostFrequentMsgIdToDefer == null || newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0)) ) { mostFrequentMsgIdToDefer = acquiredMsgId } } /** * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed * messages excluding [excludedMessages]. * @return null if no messages have been deferred OR deferred messages didn't meet the * [THRESHOLD] percentage of messages to show. */ fun getDeferredMessage(): CharSequence? { mostFrequentMsgIdToDefer?.let { if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) { return msgIdToCharSequence[mostFrequentMsgIdToDefer] } } return null } companion object { const val THRESHOLD = .5f } }
packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt 0 → 100644 +141 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.content.res.Resources import com.android.keyguard.logging.BiometricMessageDeferralLogger import com.android.keyguard.logging.FaceMessageDeferralLogger import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import java.io.PrintWriter import java.util.* import javax.inject.Inject /** * Provides whether a face acquired help message should be shown immediately when its received or * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage]. */ @SysUISingleton class FaceHelpMessageDeferral @Inject constructor( @Main resources: Resources, logBuffer: FaceMessageDeferralLogger, dumpManager: DumpManager ) : BiometricMessageDeferral( resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(), resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), logBuffer, dumpManager ) /** * @property messagesToDefer messages that shouldn't show immediately when received, but may be * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] * percentage of all passed acquired frames. */ open class BiometricMessageDeferral( private val messagesToDefer: Set<Int>, private val threshold: Float, private val logBuffer: BiometricMessageDeferralLogger, dumpManager: DumpManager ) : Dumpable { private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() private var mostFrequentAcquiredInfoToDefer: Int? = null private var totalFrames = 0 init { dumpManager.registerDumpable(this.javaClass.name, this) } override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("messagesToDefer=$messagesToDefer") pw.println("totalFrames=$totalFrames") pw.println("threshold=$threshold") } /** Reset all saved counts. */ fun reset() { totalFrames = 0 mostFrequentAcquiredInfoToDefer = null acquiredInfoToFrequency.clear() acquiredInfoToHelpString.clear() logBuffer.reset() } /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ fun updateMessage(acquiredInfo: Int, helpString: String) { if (!messagesToDefer.contains(acquiredInfo)) { return } if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { logBuffer.logUpdateMessage(acquiredInfo, helpString) acquiredInfoToHelpString[acquiredInfo] = helpString } } /** Whether the given message should be deferred instead of being shown immediately. */ fun shouldDefer(acquiredMsgId: Int): Boolean { return messagesToDefer.contains(acquiredMsgId) } /** Adds the acquiredInfo frame to the counts. We account for all frames. */ fun processFrame(acquiredInfo: Int) { if (messagesToDefer.isEmpty()) { return } totalFrames++ val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount if ( messagesToDefer.contains(acquiredInfo) && (mostFrequentAcquiredInfoToDefer == null || newAcquiredInfoCount > acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) ) { mostFrequentAcquiredInfoToDefer = acquiredInfo } logBuffer.logFrameProcessed( acquiredInfo, totalFrames, mostFrequentAcquiredInfoToDefer?.toString() ) } /** * Get the most frequent deferred message that meets the [threshold] percentage of processed * frames. * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the * [threshold] percentage. */ fun getDeferredMessage(): CharSequence? { mostFrequentAcquiredInfoToDefer?.let { if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { return acquiredInfoToHelpString[it] } } return null } }