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:
committed by
Android (Google) Code Review
commit
a12fe48a22
@@ -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] -->
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user