From cb175195b89243f6ae8c52cd3c21f0a2ce4dd3cb Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 22 Aug 2022 20:09:19 +0000 Subject: [PATCH 1/2] [Co-ex] Show 'too_dark' face acquired messages Update message. If fingerprint is enrolled, and face too_dark message is received, then tell the user to there's not enough light (for face auth) and suggest trying fingerprint to unlock. Test: atest KeyguardIndicationControllerTest Fixes: 234886175 Fixes: 243405121 Change-Id: Ia40e0e01895b04b906924d967763bb48d53124b5 --- core/res/res/values/strings.xml | 2 +- packages/SystemUI/res/values/config.xml | 5 ++-- .../KeyguardIndicationController.java | 22 +++++++++------ .../KeyguardIndicationControllerTest.java | 28 +++++++++++++++++-- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index aad32b118b8f1..f0ba5ff2ecf48 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1804,7 +1804,7 @@ Too bright. Try gentler lighting. - Try brighter lighting + Not enough light Move phone farther away diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index ec22c609b0b92..ae9ebbacea055 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -617,8 +617,9 @@ - 25 - 26 + 3 + 25 + 26 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 47dc5c2a5513e..03d4390be2906 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,6 +19,7 @@ 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_TOO_DARK; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -178,7 +179,7 @@ public class KeyguardIndicationController { private boolean mBatteryPresent = true; private long mChargingTimeRemaining; private String mBiometricErrorMessageToShowOnScreenOn; - private final Set mCoExFaceHelpMsgIdsToShow; + private final Set mCoExFaceAcquisitionMsgIdsToShow; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -249,11 +250,11 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); - mCoExFaceHelpMsgIdsToShow = new HashSet<>(); + mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); for (int msgId : msgIds) { - mCoExFaceHelpMsgIdsToShow.add(msgId); + mCoExFaceAcquisitionMsgIdsToShow.add(msgId); } mHandler = new Handler(mainLooper) { @@ -990,7 +991,7 @@ public class KeyguardIndicationController { mTopIndicationView == null ? null : mTopIndicationView.getText())); pw.println(" computePowerIndication(): " + computePowerIndication()); pw.println(" trustGrantedIndication: " + getTrustGrantedIndication()); - pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceHelpMsgIdsToShow); + pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceAcquisitionMsgIdsToShow); mRotateTextViewController.dump(pw, args); } @@ -1055,9 +1056,9 @@ public class KeyguardIndicationController { final boolean isUnlockWithFingerprintPossible = mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( getCurrentUser()); - if (faceAuthSoftError - && isUnlockWithFingerprintPossible - && !mCoExFaceHelpMsgIdsToShow.contains(msgId)) { + final boolean isCoExFaceAcquisitionMessage = + faceAuthSoftError && isUnlockWithFingerprintPossible; + if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) { if (DEBUG) { Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString + ", due to co-ex logic"); @@ -1067,7 +1068,12 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mInitialTextColorState); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { - if (faceAuthFailed && isUnlockWithFingerprintPossible) { + if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) { + showBiometricMessage( + helpString, + mContext.getString(R.string.keyguard_suggest_fingerprint) + ); + } else if (faceAuthFailed && isUnlockWithFingerprintPossible) { showBiometricMessage( mContext.getString(R.string.keyguard_face_failed), mContext.getString(R.string.keyguard_suggest_fingerprint) 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 798f47d2d6cbd..7623510f4bbc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -667,7 +667,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { final String helpString = "helpString"; final int[] msgIds = new int[]{ BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED, - BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED + BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED, + BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK }; Set messages = new HashSet<>(); for (int msgId : msgIds) { @@ -698,8 +699,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, - BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, - BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK + BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT }; for (int msgId : msgIds) { mKeyguardUpdateMonitorCallback.onBiometricHelp( @@ -742,6 +742,28 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings); } + @Test + public void sendTooDarkFaceHelpMessages_fingerprintEnrolled() { + createController(); + + // GIVEN fingerprint enrolled + when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + 0)).thenReturn(true); + + // WHEN help message received + final String helpString = "helpMsg"; + mKeyguardUpdateMonitorCallback.onBiometricHelp( + BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, + helpString, + BiometricSourceType.FACE + ); + + // THEN help message shown and try fingerprint message shown + verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString); + verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_suggest_fingerprint)); + } + @Test public void updateMonitor_listenerUpdatesIndication() { createController(); From 59ce3eac823e3fdf7c7f44861dbf81554b5e56e6 Mon Sep 17 00:00:00 2001 From: Beverly Date: Thu, 1 Sep 2022 00:50:02 +0000 Subject: [PATCH 2/2] Show low light soft-error on FACE_TIMEOUT If it meets a threshold of 70% of the acquired messages received by SysUI. Test: atest KeyguardIndicationControllerTest Test: atest BiometricMessageDeferralTest Test: enroll face auth, go into a low-light area, attempt face auth and see the "low light" message Fixes: 244362700 Change-Id: I270c2459a89d087f9a9ea5de52a8c5179d9991ce --- .../biometrics/BiometricMessageDeferral.kt | 89 +++++++++++ .../KeyguardIndicationController.java | 82 +++++++--- .../BiometricMessageDeferralTest.kt | 147 ++++++++++++++++++ .../KeyguardIndicationControllerTest.java | 54 +++++-- 4 files changed, 340 insertions(+), 32 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt new file mode 100644 index 0000000000000..f2d8aaa30f21d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt @@ -0,0 +1,89 @@ +/* + * 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, + private val messagesToDefer: Set +) { + private val msgCounts: MutableMap = HashMap() // msgId => frequency of msg + private val msgIdToCharSequence: MutableMap = 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/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 03d4390be2906..2251ab5460d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,6 +19,9 @@ 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; @@ -79,6 +82,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.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -1049,6 +1053,17 @@ 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 @@ -1096,34 +1111,44 @@ public class KeyguardIndicationController { @Override public void onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType) { - if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) { - return; + CharSequence deferredFaceMessage = null; + if (biometricSourceType == FACE) { + deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + mFaceAcquiredMessageDeferral.reset(); } - if (biometricSourceType == FACE - && msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { - // suppress all face UNABLE_TO_PROCESS errors + if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) { if (DEBUG) { - Log.d(TAG, "skip showing FACE_ERROR_UNABLE_TO_PROCESS errString=" - + errString); + Log.d(TAG, "suppressingBiometricError msgId=" + msgId + + " source=" + biometricSourceType); } - } else if (biometricSourceType == FACE - && msgId == FaceManager.FACE_ERROR_TIMEOUT) { + } else if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_TIMEOUT) { + // Co-ex: show deferred message OR nothing if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())) { - // no message if fingerprint is also enrolled + KeyguardUpdateMonitor.getCurrentUser())) { + // if we're on the lock screen (bouncer isn't showing), show the deferred msg + if (deferredFaceMessage != null + && !mStatusBarKeyguardViewManager.isBouncerShowing()) { + showBiometricMessage( + deferredFaceMessage, + mContext.getString(R.string.keyguard_suggest_fingerprint) + ); + return; + } + + // otherwise, don't show any message if (DEBUG) { Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); } return; } - // The face timeout message is not very actionable, let's ask the user to + // Face-only: The face timeout message is not very actionable, let's ask the user to // manually retry. - if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { - mStatusBarKeyguardViewManager.showBouncerMessage( - mContext.getResources().getString(R.string.keyguard_try_fingerprint), - mInitialTextColorState + if (deferredFaceMessage != null) { + showBiometricMessage( + deferredFaceMessage, + mContext.getString(R.string.keyguard_unlock) ); } else { // suggest swiping up to unlock (try face auth again or swipe up to bouncer) @@ -1140,8 +1165,9 @@ public class KeyguardIndicationController { private boolean shouldSuppressBiometricError(int msgId, BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) { - if (biometricSourceType == BiometricSourceType.FINGERPRINT) + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { return shouldSuppressFingerprintError(msgId, updateMonitor); + } if (biometricSourceType == FACE) { return shouldSuppressFaceError(msgId, updateMonitor); } @@ -1167,7 +1193,8 @@ public class KeyguardIndicationController { // check of whether non-strong biometric is allowed return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) - || msgId == FaceManager.FACE_ERROR_CANCELED); + || msgId == FaceManager.FACE_ERROR_CANCELED + || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS); } @@ -1206,10 +1233,11 @@ public class KeyguardIndicationController { boolean isStrongBiometric) { super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric); hideBiometricMessage(); - - if (biometricSourceType == FACE - && !mKeyguardBypassController.canBypass()) { - showActionToUnlock(); + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.reset(); + if (!mKeyguardBypassController.canBypass()) { + showActionToUnlock(); + } } } @@ -1280,4 +1308,14 @@ 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 new file mode 100644 index 0000000000000..419fedf99c158 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt @@ -0,0 +1,147 @@ +/* + * 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/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 7623510f4bbc7..d73d07ca1423c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -19,6 +19,7 @@ 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_ERROR_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; @@ -65,7 +66,6 @@ import android.content.pm.UserInfo; import android.graphics.Color; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; -import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.BatteryManager; import android.os.Looper; @@ -602,7 +602,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { String message = mContext.getString(R.string.keyguard_unlock); mController.setVisible(true); - mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, + mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); @@ -636,7 +636,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true); mController.setVisible(true); - mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, + mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); verify(mStatusBarKeyguardViewManager).showBouncerMessage(eq(message), any()); @@ -651,7 +651,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.setVisible(true); mController.getKeyguardCallback().onBiometricError( - FaceManager.FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE); + FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE); verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); } @@ -667,8 +667,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { final String helpString = "helpString"; final int[] msgIds = new int[]{ BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED, - BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED, - BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK + BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED }; Set messages = new HashSet<>(); for (int msgId : msgIds) { @@ -728,8 +727,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, - BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, - BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK + BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT }; for (int msgId : msgIds) { final String numberedHelpString = helpString + msgId; @@ -743,7 +741,36 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test - public void sendTooDarkFaceHelpMessages_fingerprintEnrolled() { + public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() { + createController(); + + // GIVEN fingerprint NOT enrolled + when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + 0)).thenReturn(false); + + // WHEN help message received + final String helpString = "helpMsg"; + mKeyguardUpdateMonitorCallback.onBiometricHelp( + BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, + helpString, + BiometricSourceType.FACE + ); + + // THEN help message not shown yet + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); + + // WHEN face timeout error received + mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_TIMEOUT, "face timeout", + BiometricSourceType.FACE); + + // THEN the low light message shows with suggestion to swipe up to unlock + verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString); + verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_unlock)); + } + + @Test + public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() { createController(); // GIVEN fingerprint enrolled @@ -758,7 +785,14 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { BiometricSourceType.FACE ); - // THEN help message shown and try fingerprint message shown + // THEN help message not shown yet + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); + + // WHEN face timeout error received + mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_TIMEOUT, "face timeout", + BiometricSourceType.FACE); + + // THEN the low light message shows and suggests trying fingerprint verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, mContext.getString(R.string.keyguard_suggest_fingerprint));