summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/config.xml27
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt141
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt147
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt188
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java102
11 files changed, 612 insertions, 269 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index a8027238a0bf..37549c927a90 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -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">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 763b29e52cf2..32c1cf9802ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -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;
@@ -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;
@@ -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() {
@@ -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++) {
@@ -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());
}
@@ -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
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
new file mode 100644
index 000000000000..2c2ab7b39161
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -0,0 +1,71 @@
+/*
+ * 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"
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt
deleted file mode 100644
index f2d8aaa30f21..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
new file mode 100644
index 000000000000..fabc1c1bb908
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
@@ -0,0 +1,141 @@
+/*
+ * 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
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
new file mode 100644
index 000000000000..7f1ad6d20c16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.log.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
+ * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
+ */
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BiometricMessagesLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c2a87649adef..756feb0c55c2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -287,6 +287,17 @@ public class LogModule {
}
/**
+ * Provides a {@link LogBuffer} for use by
+ * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}.
+ */
+ @Provides
+ @SysUISingleton
+ @BiometricMessagesLog
+ public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) {
+ return factory.create("BiometricMessagesLog", 150);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by the status bar network controller.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 408c61f8f387..e06c97756ea7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -19,9 +19,6 @@ package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_FIRST_FRAME_RECEIVED;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_GOOD;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.view.View.GONE;
@@ -53,6 +50,7 @@ import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
@@ -82,7 +80,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
-import com.android.systemui.biometrics.BiometricMessageDeferral;
+import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -184,6 +182,7 @@ public class KeyguardIndicationController {
private long mChargingTimeRemaining;
private String mBiometricErrorMessageToShowOnScreenOn;
private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow;
+ private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -233,7 +232,8 @@ public class KeyguardIndicationController {
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManager accessibilityManager,
+ FaceHelpMessageDeferral faceHelpMessageDeferral) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -254,6 +254,7 @@ public class KeyguardIndicationController {
mScreenLifecycle = screenLifecycle;
mScreenLifecycle.addObserver(mScreenObserver);
+ mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
int[] msgIds = context.getResources().getIntArray(
com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled);
@@ -1041,8 +1042,22 @@ public class KeyguardIndicationController {
}
@Override
+ public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) {
+ if (biometricSourceType == FACE) {
+ mFaceAcquiredMessageDeferral.processFrame(acquireInfo);
+ }
+ }
+
+ @Override
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FACE) {
+ mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString);
+ if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) {
+ return;
+ }
+ }
+
// TODO(b/141025588): refactor to reduce repetition of code/comments
// Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
// as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
@@ -1053,17 +1068,6 @@ public class KeyguardIndicationController {
return;
}
- if (biometricSourceType == FACE) {
- if (msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED) {
- mFaceAcquiredMessageDeferral.reset();
- } else {
- mFaceAcquiredMessageDeferral.processMessage(msgId, helpString);
- if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) {
- return;
- }
- }
- }
-
final boolean faceAuthSoftError = biometricSourceType == FACE
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
final boolean faceAuthFailed = biometricSourceType == FACE
@@ -1109,11 +1113,23 @@ public class KeyguardIndicationController {
}
@Override
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FACE) {
+ mFaceAcquiredMessageDeferral.reset();
+ }
+ }
+
+ @Override
public void onBiometricError(int msgId, String errString,
BiometricSourceType biometricSourceType) {
CharSequence deferredFaceMessage = null;
if (biometricSourceType == FACE) {
- deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+ if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
+ deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+ if (DEBUG) {
+ Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage);
+ }
+ }
mFaceAcquiredMessageDeferral.reset();
}
@@ -1308,14 +1324,4 @@ public class KeyguardIndicationController {
}
}
};
-
- private final BiometricMessageDeferral mFaceAcquiredMessageDeferral =
- new BiometricMessageDeferral(
- Set.of(
- FACE_ACQUIRED_GOOD,
- FACE_ACQUIRED_START,
- FACE_ACQUIRED_FIRST_FRAME_RECEIVED
- ),
- Set.of(FACE_ACQUIRED_TOO_DARK)
- );
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt
deleted file mode 100644
index 419fedf99c15..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class BiometricMessageDeferralTest : SysuiTestCase() {
-
- @Test
- fun testProcessNoMessages_noDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf())
-
- assertNull(biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testProcessNonDeferredMessages_noDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2))
-
- // WHEN there are no deferred messages processed
- for (i in 0..3) {
- biometricMessageDeferral.processMessage(4, "test")
- }
-
- // THEN getDeferredMessage is null
- assertNull(biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testAllProcessedMessagesWereDeferred() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1))
-
- // WHEN all the processed messages are a deferred message
- for (i in 0..3) {
- biometricMessageDeferral.processMessage(1, "test")
- }
-
- // THEN deferredMessage will return the string associated with the deferred msgId
- assertEquals("test", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testReturnsMostFrequentDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2))
-
- // WHEN there's two msgId=1 processed and one msgId=2 processed
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(2, "msgId-2")
-
- // THEN the most frequent deferred message is that meets the threshold is returned
- assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testDeferredMessage_mustMeetThreshold() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1))
-
- // WHEN more nonDeferredMessages are shown than the deferred message
- val totalMessages = 10
- val nonDeferredMessagesCount =
- (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1
- for (i in 0 until nonDeferredMessagesCount) {
- biometricMessageDeferral.processMessage(4, "non-deferred-msg")
- }
- for (i in nonDeferredMessagesCount until totalMessages) {
- biometricMessageDeferral.processMessage(1, "msgId-1")
- }
-
- // THEN there's no deferred message because it didn't meet the threshold
- assertNull(biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testDeferredMessage_manyExcludedMessages_getDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1))
-
- // WHEN more excludedMessages are shown than the deferred message
- val totalMessages = 10
- val excludedMessagesCount = (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1
- for (i in 0 until excludedMessagesCount) {
- biometricMessageDeferral.processMessage(3, "excluded-msg")
- }
- for (i in excludedMessagesCount until totalMessages) {
- biometricMessageDeferral.processMessage(1, "msgId-1")
- }
-
- // THEN there IS a deferred message because the deferred msg meets the threshold amongst the
- // non-excluded messages
- assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testResetClearsOutCounts() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2))
-
- // GIVEN two msgId=1 events processed
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(1, "msgId-1")
-
- // WHEN counts are reset and then a single deferred message is processed (msgId=2)
- biometricMessageDeferral.reset()
- biometricMessageDeferral.processMessage(2, "msgId-2")
-
- // THEN msgId-2 is the deferred message since the two msgId=1 events were reset
- assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testShouldDefer() {
- // GIVEN should defer msgIds 1 and 2
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1, 2))
-
- // THEN shouldDefer returns true for ids 1 & 2
- assertTrue(biometricMessageDeferral.shouldDefer(1))
- assertTrue(biometricMessageDeferral.shouldDefer(2))
-
- // THEN should defer returns false for ids 3 & 4
- assertFalse(biometricMessageDeferral.shouldDefer(3))
- assertFalse(biometricMessageDeferral.shouldDefer(4))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
new file mode 100644
index 000000000000..c9ccdb36da89
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.BiometricMessageDeferralLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FaceHelpMessageDeferralTest : SysuiTestCase() {
+ val threshold = .75f
+ @Mock lateinit var logger: BiometricMessageDeferralLogger
+ @Mock lateinit var dumpManager: DumpManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testProcessFrame_logs() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+ biometricMessageDeferral.processFrame(1)
+ verify(logger).logFrameProcessed(1, 1, "1")
+ }
+
+ @Test
+ fun testUpdateMessage_logs() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+ biometricMessageDeferral.updateMessage(1, "hi")
+ verify(logger).logUpdateMessage(1, "hi")
+ }
+
+ @Test
+ fun testReset_logs() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+ biometricMessageDeferral.reset()
+ verify(logger).reset()
+ }
+
+ @Test
+ fun testProcessNoMessages_noDeferredMessage() {
+ val biometricMessageDeferral = createMsgDeferral(emptySet())
+
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testProcessNonDeferredMessages_noDeferredMessage() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // WHEN there are no deferred messages processed
+ for (i in 0..3) {
+ biometricMessageDeferral.processFrame(4)
+ biometricMessageDeferral.updateMessage(4, "test")
+ }
+
+ // THEN getDeferredMessage is null
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testProcessMessagesWithDeferredMessage_deferredMessageWasNeverGivenAString() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ biometricMessageDeferral.processFrame(1)
+
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testAllProcessedMessagesWereDeferred() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+
+ // WHEN all the processed messages are a deferred message
+ for (i in 0..3) {
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "test")
+ }
+
+ // THEN deferredMessage will return the string associated with the deferred msgId
+ assertEquals("test", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testReturnsMostFrequentDeferredMessage() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+
+ biometricMessageDeferral.processFrame(2)
+ biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+ // THEN the most frequent deferred message is that meets the threshold is returned
+ assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testDeferredMessage_mustMeetThreshold() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+
+ // WHEN more nonDeferredMessages are shown than the deferred message
+ val totalMessages = 10
+ val nonDeferredMessagesCount = (totalMessages * threshold).toInt() + 1
+ for (i in 0 until nonDeferredMessagesCount) {
+ biometricMessageDeferral.processFrame(4)
+ biometricMessageDeferral.updateMessage(4, "non-deferred-msg")
+ }
+ for (i in nonDeferredMessagesCount until totalMessages) {
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+ }
+
+ // THEN there's no deferred message because it didn't meet the threshold
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testResetClearsOutCounts() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // GIVEN two msgId=1 events processed
+ biometricMessageDeferral.processFrame(
+ 1,
+ )
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+
+ // WHEN counts are reset and then a single deferred message is processed (msgId=2)
+ biometricMessageDeferral.reset()
+ biometricMessageDeferral.processFrame(2)
+ biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+ // THEN msgId-2 is the deferred message since the two msgId=1 events were reset
+ assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testShouldDefer() {
+ // GIVEN should defer msgIds 1 and 2
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // THEN shouldDefer returns true for ids 1 & 2
+ assertTrue(biometricMessageDeferral.shouldDefer(1))
+ assertTrue(biometricMessageDeferral.shouldDefer(2))
+
+ // THEN should defer returns false for ids 3 & 4
+ assertFalse(biometricMessageDeferral.shouldDefer(3))
+ assertFalse(biometricMessageDeferral.shouldDefer(4))
+ }
+
+ private fun createMsgDeferral(messagesToDefer: Set<Int>): BiometricMessageDeferral {
+ return BiometricMessageDeferral(messagesToDefer, threshold, logger, dumpManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index ac8874b0e131..945cf7f8774f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -19,8 +19,10 @@ package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
@@ -86,6 +88,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -167,6 +170,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Mock
private AccessibilityManager mAccessibilityManager;
@Mock
+ private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
+ @Mock
private ScreenLifecycle mScreenLifecycle;
@Captor
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
@@ -259,7 +264,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
- mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager);
+ mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
+ mFaceHelpMessageDeferral);
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -535,7 +541,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mController.setVisible(true);
mController.getKeyguardCallback().onBiometricHelp(
- KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
+ BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
BiometricSourceType.FACE);
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
reset(mRotateTextViewController);
@@ -582,7 +588,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
// WHEN there's a face not recognized message
mController.getKeyguardCallback().onBiometricHelp(
- KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
+ BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
message,
BiometricSourceType.FACE);
@@ -748,8 +754,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
0)).thenReturn(false);
- // WHEN help message received
+ // WHEN help message received and deferred message is valid
final String helpString = "helpMsg";
+ when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
+ when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
helpString,
@@ -777,8 +785,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
0)).thenReturn(true);
- // WHEN help message received
+ // WHEN help message received and deferredMessage is valid
final String helpString = "helpMsg";
+ when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
+ when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
helpString,
@@ -1123,7 +1133,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
}
-
@Test
public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
@@ -1269,6 +1278,87 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
}
+ @Test
+ public void faceOnAcquired_processFrame() {
+ createController();
+
+ // WHEN face sends an acquired message
+ final int acquireInfo = 1;
+ mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+
+ // THEN face help message deferral should process the acquired frame
+ verify(mFaceHelpMessageDeferral).processFrame(acquireInfo);
+ }
+
+ @Test
+ public void fingerprintOnAcquired_noProcessFrame() {
+ createController();
+
+ // WHEN fingerprint sends an acquired message
+ mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1);
+
+ // THEN face help message deferral should NOT process any acquired frames
+ verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt());
+ }
+
+ @Test
+ public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() {
+ createController();
+
+ // WHEN fingerprint sends an onBiometricHelp
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ 1,
+ "placeholder",
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer
+ verify(mFaceHelpMessageDeferral, never()).reset();
+ verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString());
+ verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt());
+ }
+
+ @Test
+ public void onBiometricFailed_resetFaceHelpMessageDeferral() {
+ createController();
+
+ // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+ mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE);
+
+ // THEN face help message deferral is reset
+ verify(mFaceHelpMessageDeferral).reset();
+ }
+
+ @Test
+ public void onBiometricError_resetFaceHelpMessageDeferral() {
+ createController();
+
+ // WHEN face has an error
+ mKeyguardUpdateMonitorCallback.onBiometricError(4, "string",
+ BiometricSourceType.FACE);
+
+ // THEN face help message deferral is reset
+ verify(mFaceHelpMessageDeferral).reset();
+ }
+
+ @Test
+ public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() {
+ createController();
+
+ // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+ final int msgId = 1;
+ final String helpString = "test";
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ msgId,
+ "test",
+ BiometricSourceType.FACE);
+
+ // THEN face help message deferral is NOT reset and message IS updated
+ verify(mFaceHelpMessageDeferral, never()).reset();
+ verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString);
+ }
+
+
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}