Merge "Use more face-acquired frames for msg deferral" into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-09-15 22:14:08 +00:00
committed by Android (Google) Code Review
11 changed files with 612 additions and 269 deletions

View File

@@ -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">

View File

@@ -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

View File

@@ -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"
}
)
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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 {
}

View File

@@ -286,6 +286,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.
*/

View File

@@ -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);
@@ -1040,9 +1041,23 @@ 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
@@ -1108,12 +1112,24 @@ 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)
);
}

View File

@@ -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))
}
}

View File

@@ -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)
}
}

View File

@@ -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());
}