Merge changes from topic "too_dark_face_acquisition_message_enable" into tm-qpr-dev

* changes:
  Show low light soft-error on FACE_TIMEOUT
  [Co-ex] Show 'too_dark' face acquired messages
This commit is contained in:
TreeHugger Robot
2022-09-08 15:34:15 +00:00
committed by Android (Google) Code Review
6 changed files with 378 additions and 41 deletions

View File

@@ -1804,7 +1804,7 @@
<!-- Message shown during face acquisition when the image is too bright [CHAR LIMIT=50] --> <!-- Message shown during face acquisition when the image is too bright [CHAR LIMIT=50] -->
<string name="face_acquired_too_bright">Too bright. Try gentler lighting.</string> <string name="face_acquired_too_bright">Too bright. Try gentler lighting.</string>
<!-- Message shown during face acquisition when the image is too dark [CHAR LIMIT=50] --> <!-- Message shown during face acquisition when the image is too dark [CHAR LIMIT=50] -->
<string name="face_acquired_too_dark">Try brighter lighting</string> <string name="face_acquired_too_dark">Not enough light</string>
<!-- Message shown during face acquisition when the user is too close to sensor [CHAR LIMIT=50] --> <!-- Message shown during face acquisition when the user is too close to sensor [CHAR LIMIT=50] -->
<string name="face_acquired_too_close">Move phone farther away</string> <string name="face_acquired_too_close">Move phone farther away</string>
<!-- Message shown during face acquisition when the user is too far from sensor [CHAR LIMIT=50] --> <!-- Message shown during face acquisition when the user is too far from sensor [CHAR LIMIT=50] -->

View File

@@ -617,8 +617,9 @@
<!-- Which face help messages to surface when fingerprint is also enrolled. <!-- Which face help messages to surface when fingerprint is also enrolled.
Message ids correspond with the acquired ids in BiometricFaceConstants --> Message ids correspond with the acquired ids in BiometricFaceConstants -->
<integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled">
<item>25</item> <item>3</item> <!-- TOO_DARK -->
<item>26</item> <item>25</item> <!-- DARK_GLASSES -->
<item>26</item> <!-- MOUTH_COVERING_DETECTED -->
</integer-array> </integer-array>
<!-- Whether the communal service should be enabled --> <!-- Whether the communal service should be enabled -->

View File

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

@@ -19,6 +19,10 @@ package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; 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_MANAGEMENT_DISCLOSURE;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_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.hardware.biometrics.BiometricSourceType.FACE;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
@@ -78,6 +82,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.biometrics.BiometricMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Background;
@@ -178,7 +183,7 @@ public class KeyguardIndicationController {
private boolean mBatteryPresent = true; private boolean mBatteryPresent = true;
private long mChargingTimeRemaining; private long mChargingTimeRemaining;
private String mBiometricErrorMessageToShowOnScreenOn; private String mBiometricErrorMessageToShowOnScreenOn;
private final Set<Integer> mCoExFaceHelpMsgIdsToShow; private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow;
private boolean mInited; private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -249,11 +254,11 @@ public class KeyguardIndicationController {
mScreenLifecycle = screenLifecycle; mScreenLifecycle = screenLifecycle;
mScreenLifecycle.addObserver(mScreenObserver); mScreenLifecycle.addObserver(mScreenObserver);
mCoExFaceHelpMsgIdsToShow = new HashSet<>(); mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
int[] msgIds = context.getResources().getIntArray( int[] msgIds = context.getResources().getIntArray(
com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled);
for (int msgId : msgIds) { for (int msgId : msgIds) {
mCoExFaceHelpMsgIdsToShow.add(msgId); mCoExFaceAcquisitionMsgIdsToShow.add(msgId);
} }
mHandler = new Handler(mainLooper) { mHandler = new Handler(mainLooper) {
@@ -990,7 +995,7 @@ public class KeyguardIndicationController {
mTopIndicationView == null ? null : mTopIndicationView.getText())); mTopIndicationView == null ? null : mTopIndicationView.getText()));
pw.println(" computePowerIndication(): " + computePowerIndication()); pw.println(" computePowerIndication(): " + computePowerIndication());
pw.println(" trustGrantedIndication: " + getTrustGrantedIndication()); pw.println(" trustGrantedIndication: " + getTrustGrantedIndication());
pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceHelpMsgIdsToShow); pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceAcquisitionMsgIdsToShow);
mRotateTextViewController.dump(pw, args); mRotateTextViewController.dump(pw, args);
} }
@@ -1048,6 +1053,17 @@ public class KeyguardIndicationController {
return; 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 final boolean faceAuthSoftError = biometricSourceType == FACE
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
final boolean faceAuthFailed = biometricSourceType == FACE final boolean faceAuthFailed = biometricSourceType == FACE
@@ -1055,9 +1071,9 @@ public class KeyguardIndicationController {
final boolean isUnlockWithFingerprintPossible = final boolean isUnlockWithFingerprintPossible =
mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
getCurrentUser()); getCurrentUser());
if (faceAuthSoftError final boolean isCoExFaceAcquisitionMessage =
&& isUnlockWithFingerprintPossible faceAuthSoftError && isUnlockWithFingerprintPossible;
&& !mCoExFaceHelpMsgIdsToShow.contains(msgId)) { if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString
+ ", due to co-ex logic"); + ", due to co-ex logic");
@@ -1067,7 +1083,12 @@ public class KeyguardIndicationController {
mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
mInitialTextColorState); mInitialTextColorState);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { } 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( showBiometricMessage(
mContext.getString(R.string.keyguard_face_failed), mContext.getString(R.string.keyguard_face_failed),
mContext.getString(R.string.keyguard_suggest_fingerprint) mContext.getString(R.string.keyguard_suggest_fingerprint)
@@ -1090,34 +1111,44 @@ public class KeyguardIndicationController {
@Override @Override
public void onBiometricError(int msgId, String errString, public void onBiometricError(int msgId, String errString,
BiometricSourceType biometricSourceType) { BiometricSourceType biometricSourceType) {
CharSequence deferredFaceMessage = null;
if (biometricSourceType == FACE) {
deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
}
if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) { if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
if (DEBUG) {
Log.d(TAG, "suppressingBiometricError msgId=" + msgId
+ " source=" + biometricSourceType);
}
} else if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_TIMEOUT) {
// Co-ex: show deferred message OR nothing
if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
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; return;
} }
if (biometricSourceType == FACE // otherwise, don't show any message
&& msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
// suppress all face UNABLE_TO_PROCESS errors
if (DEBUG) {
Log.d(TAG, "skip showing FACE_ERROR_UNABLE_TO_PROCESS errString="
+ errString);
}
} else if (biometricSourceType == FACE
&& msgId == FaceManager.FACE_ERROR_TIMEOUT) {
if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
getCurrentUser())) {
// no message if fingerprint is also enrolled
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
} }
return; 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. // manually retry.
if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { if (deferredFaceMessage != null) {
mStatusBarKeyguardViewManager.showBouncerMessage( showBiometricMessage(
mContext.getResources().getString(R.string.keyguard_try_fingerprint), deferredFaceMessage,
mInitialTextColorState mContext.getString(R.string.keyguard_unlock)
); );
} else { } else {
// suggest swiping up to unlock (try face auth again or swipe up to bouncer) // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
@@ -1134,8 +1165,9 @@ public class KeyguardIndicationController {
private boolean shouldSuppressBiometricError(int msgId, private boolean shouldSuppressBiometricError(int msgId,
BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) { BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
return shouldSuppressFingerprintError(msgId, updateMonitor); return shouldSuppressFingerprintError(msgId, updateMonitor);
}
if (biometricSourceType == FACE) { if (biometricSourceType == FACE) {
return shouldSuppressFaceError(msgId, updateMonitor); return shouldSuppressFaceError(msgId, updateMonitor);
} }
@@ -1161,7 +1193,8 @@ public class KeyguardIndicationController {
// check of whether non-strong biometric is allowed // check of whether non-strong biometric is allowed
return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
&& msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
|| msgId == FaceManager.FACE_ERROR_CANCELED); || msgId == FaceManager.FACE_ERROR_CANCELED
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
} }
@@ -1200,12 +1233,13 @@ public class KeyguardIndicationController {
boolean isStrongBiometric) { boolean isStrongBiometric) {
super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric); super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
hideBiometricMessage(); hideBiometricMessage();
if (biometricSourceType == FACE) {
if (biometricSourceType == FACE mFaceAcquiredMessageDeferral.reset();
&& !mKeyguardBypassController.canBypass()) { if (!mKeyguardBypassController.canBypass()) {
showActionToUnlock(); showActionToUnlock();
} }
} }
}
@Override @Override
public void onUserSwitchComplete(int userId) { public void onUserSwitchComplete(int userId) {
@@ -1274,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)
);
} }

View File

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

View File

@@ -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_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; 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.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_ALIGNMENT;
@@ -65,7 +66,6 @@ import android.content.pm.UserInfo;
import android.graphics.Color; import android.graphics.Color;
import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Looper; import android.os.Looper;
@@ -602,7 +602,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
String message = mContext.getString(R.string.keyguard_unlock); String message = mContext.getString(R.string.keyguard_unlock);
mController.setVisible(true); mController.setVisible(true);
mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
"A message", BiometricSourceType.FACE); "A message", BiometricSourceType.FACE);
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
@@ -636,7 +636,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true); when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
mController.setVisible(true); mController.setVisible(true);
mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
"A message", BiometricSourceType.FACE); "A message", BiometricSourceType.FACE);
verify(mStatusBarKeyguardViewManager).showBouncerMessage(eq(message), any()); verify(mStatusBarKeyguardViewManager).showBouncerMessage(eq(message), any());
@@ -651,7 +651,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mController.setVisible(true); mController.setVisible(true);
mController.getKeyguardCallback().onBiometricError( mController.getKeyguardCallback().onBiometricError(
FaceManager.FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE); FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE);
verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
} }
@@ -698,8 +698,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT,
BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH,
BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW,
BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT
BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK
}; };
for (int msgId : msgIds) { for (int msgId : msgIds) {
mKeyguardUpdateMonitorCallback.onBiometricHelp( mKeyguardUpdateMonitorCallback.onBiometricHelp(
@@ -728,8 +727,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT, BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT,
BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH, BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH,
BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW, BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW,
BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT, BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT
BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK
}; };
for (int msgId : msgIds) { for (int msgId : msgIds) {
final String numberedHelpString = helpString + msgId; final String numberedHelpString = helpString + msgId;
@@ -742,6 +740,64 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings); verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings);
} }
@Test
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
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 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));
}
@Test @Test
public void updateMonitor_listenerUpdatesIndication() { public void updateMonitor_listenerUpdatesIndication() {
createController(); createController();