diff options
11 files changed, 612 insertions, 269 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index a8027238a0bf..37549c927a90 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -611,6 +611,33 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> + <!-- Messages that should NOT be shown to the user during face authentication on keyguard. + This includes both lockscreen and bouncer. This should be used to hide messages that may be + too chatty or messages that the user can't do much about. Entries are defined in + android.hardware.biometrics.face@1.0 types.hal. + + Although not visibly shown to the user, these acquired messages (sent per face auth frame) + are still counted towards the total frames to determine whether a deferred message + (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on + face timeout. --> + <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" > + </integer-array> + + <!-- Which face help messages to defer until face auth times out. If face auth is cancelled + or ends on another error, then the message is never surfaced. May also never surface + if it doesn't meet a threshold % of authentication frames specified by. + config_face_help_msgs_defer_until_timeout_threshold. --> + <integer-array name="config_face_help_msgs_defer_until_timeout"> + </integer-array> + + <!-- Percentage of face auth frames received required to show a deferred message at + FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages + that are deferred.--> + <item name="config_face_help_msgs_defer_until_timeout_threshold" + translatable="false" format="float" type="dimen"> + .75 + </item> + <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 763b29e52cf2..32c1cf9802ab 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -157,6 +157,7 @@ import com.google.android.collect.Lists; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -167,6 +168,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -281,6 +283,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; private final UiEventLogger mUiEventLogger; + private final Set<Integer> mFaceAcquiredInfoIgnoreList; private int mStatusBarState; private final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { @@ -1023,6 +1026,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAuthFailed() { Assert.isMainThread(); + mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1639,6 +1643,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { + return; + } handleFaceHelp(helpMsgId, helpString.toString()); } @@ -1931,6 +1938,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mWakeOnFingerprintAcquiredStart = context.getResources() .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); + mFaceAcquiredInfoIgnoreList = Arrays.stream( + mContext.getResources().getIntArray( + R.array.config_face_acquire_device_entry_ignorelist)) + .boxed() + .collect(Collectors.toSet()); mHandler = new Handler(mainLooper) { @Override diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt new file mode 100644 index 000000000000..2c2ab7b39161 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.logging + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.dagger.BiometricMessagesLog +import javax.inject.Inject + +/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ +@SysUISingleton +class FaceMessageDeferralLogger +@Inject +constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) : + BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger") + +open class BiometricMessageDeferralLogger( + private val logBuffer: LogBuffer, + private val tag: String +) { + fun reset() { + logBuffer.log(tag, DEBUG, "reset") + } + + fun logUpdateMessage(acquiredInfo: Int, helpString: String) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + str1 = helpString + }, + { "updateMessage acquiredInfo=$int1 helpString=$str1" } + ) + } + + fun logFrameProcessed( + acquiredInfo: Int, + totalFrames: Int, + mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold + ) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + int2 = totalFrames + str1 = mostFrequentAcquiredInfoToDefer + }, + { + "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " + + "messageToShowOnTimeout=$str1" + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt deleted file mode 100644 index f2d8aaa30f21..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -/** - * Provides whether an acquired error message should be shown immediately when its received (see - * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage]. - * @property excludedMessages messages that are excluded from counts - * @property messagesToDefer messages that shouldn't show immediately when received, but may be - * shown later if the message is the most frequent message processed and meets [THRESHOLD] - * percentage of all messages (excluding [excludedMessages]) - */ -class BiometricMessageDeferral( - private val excludedMessages: Set<Int>, - private val messagesToDefer: Set<Int> -) { - private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg - private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message - private var totalRelevantMessages = 0 - private var mostFrequentMsgIdToDefer: Int? = null - - /** Reset all saved counts. */ - fun reset() { - totalRelevantMessages = 0 - msgCounts.clear() - msgIdToCharSequence.clear() - } - - /** Whether the given message should be deferred instead of being shown immediately. */ - fun shouldDefer(acquiredMsgId: Int): Boolean { - return messagesToDefer.contains(acquiredMsgId) - } - - /** - * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count - * messages that shouldn't be deferred in these counts. - */ - fun processMessage(acquiredMsgId: Int, helpString: CharSequence) { - if (excludedMessages.contains(acquiredMsgId)) { - return - } - - totalRelevantMessages++ - msgIdToCharSequence[acquiredMsgId] = helpString - - val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1 - msgCounts[acquiredMsgId] = newAcquiredMsgCount - if ( - messagesToDefer.contains(acquiredMsgId) && - (mostFrequentMsgIdToDefer == null || - newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0)) - ) { - mostFrequentMsgIdToDefer = acquiredMsgId - } - } - - /** - * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed - * messages excluding [excludedMessages]. - * @return null if no messages have been deferred OR deferred messages didn't meet the - * [THRESHOLD] percentage of messages to show. - */ - fun getDeferredMessage(): CharSequence? { - mostFrequentMsgIdToDefer?.let { - if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) { - return msgIdToCharSequence[mostFrequentMsgIdToDefer] - } - } - - return null - } - companion object { - const val THRESHOLD = .5f - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt new file mode 100644 index 000000000000..fabc1c1bb908 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.res.Resources +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.keyguard.logging.FaceMessageDeferralLogger +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import java.io.PrintWriter +import java.util.* +import javax.inject.Inject + +/** + * Provides whether a face acquired help message should be shown immediately when its received or + * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage]. + */ +@SysUISingleton +class FaceHelpMessageDeferral +@Inject +constructor( + @Main resources: Resources, + logBuffer: FaceMessageDeferralLogger, + dumpManager: DumpManager +) : + BiometricMessageDeferral( + resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(), + resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), + logBuffer, + dumpManager + ) + +/** + * @property messagesToDefer messages that shouldn't show immediately when received, but may be + * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] + * percentage of all passed acquired frames. + */ +open class BiometricMessageDeferral( + private val messagesToDefer: Set<Int>, + private val threshold: Float, + private val logBuffer: BiometricMessageDeferralLogger, + dumpManager: DumpManager +) : Dumpable { + private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() + private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() + private var mostFrequentAcquiredInfoToDefer: Int? = null + private var totalFrames = 0 + + init { + dumpManager.registerDumpable(this.javaClass.name, this) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("messagesToDefer=$messagesToDefer") + pw.println("totalFrames=$totalFrames") + pw.println("threshold=$threshold") + } + + /** Reset all saved counts. */ + fun reset() { + totalFrames = 0 + mostFrequentAcquiredInfoToDefer = null + acquiredInfoToFrequency.clear() + acquiredInfoToHelpString.clear() + logBuffer.reset() + } + + /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ + fun updateMessage(acquiredInfo: Int, helpString: String) { + if (!messagesToDefer.contains(acquiredInfo)) { + return + } + if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { + logBuffer.logUpdateMessage(acquiredInfo, helpString) + acquiredInfoToHelpString[acquiredInfo] = helpString + } + } + + /** Whether the given message should be deferred instead of being shown immediately. */ + fun shouldDefer(acquiredMsgId: Int): Boolean { + return messagesToDefer.contains(acquiredMsgId) + } + + /** Adds the acquiredInfo frame to the counts. We account for all frames. */ + fun processFrame(acquiredInfo: Int) { + if (messagesToDefer.isEmpty()) { + return + } + + totalFrames++ + + val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 + acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount + if ( + messagesToDefer.contains(acquiredInfo) && + (mostFrequentAcquiredInfoToDefer == null || + newAcquiredInfoCount > + acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) + ) { + mostFrequentAcquiredInfoToDefer = acquiredInfo + } + + logBuffer.logFrameProcessed( + acquiredInfo, + totalFrames, + mostFrequentAcquiredInfoToDefer?.toString() + ) + } + + /** + * Get the most frequent deferred message that meets the [threshold] percentage of processed + * frames. + * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the + * [threshold] percentage. + */ + fun getDeferredMessage(): CharSequence? { + mostFrequentAcquiredInfoToDefer?.let { + if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { + return acquiredInfoToHelpString[it] + } + } + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java new file mode 100644 index 000000000000..7f1ad6d20c16 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral} + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface BiometricMessagesLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index c2a87649adef..756feb0c55c2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -287,6 +287,17 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for use by + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}. + */ + @Provides + @SysUISingleton + @BiometricMessagesLog + public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) { + return factory.create("BiometricMessagesLog", 150); + } + + /** * Provides a {@link LogBuffer} for use by the status bar network controller. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 408c61f8f387..e06c97756ea7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,9 +19,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_FIRST_FRAME_RECEIVED; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_START; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.view.View.GONE; @@ -53,6 +50,7 @@ import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; @@ -82,7 +80,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; -import com.android.systemui.biometrics.BiometricMessageDeferral; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -184,6 +182,7 @@ public class KeyguardIndicationController { private long mChargingTimeRemaining; private String mBiometricErrorMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow; + private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -233,7 +232,8 @@ public class KeyguardIndicationController { LockPatternUtils lockPatternUtils, ScreenLifecycle screenLifecycle, KeyguardBypassController keyguardBypassController, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + FaceHelpMessageDeferral faceHelpMessageDeferral) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -254,6 +254,7 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); + mFaceAcquiredMessageDeferral = faceHelpMessageDeferral; mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); @@ -1041,8 +1042,22 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.processFrame(acquireInfo); + } + } + + @Override public void onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString); + if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { + return; + } + } + // TODO(b/141025588): refactor to reduce repetition of code/comments // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to @@ -1053,17 +1068,6 @@ public class KeyguardIndicationController { return; } - if (biometricSourceType == FACE) { - if (msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED) { - mFaceAcquiredMessageDeferral.reset(); - } else { - mFaceAcquiredMessageDeferral.processMessage(msgId, helpString); - if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { - return; - } - } - } - final boolean faceAuthSoftError = biometricSourceType == FACE && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; final boolean faceAuthFailed = biometricSourceType == FACE @@ -1109,11 +1113,23 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.reset(); + } + } + + @Override public void onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType) { CharSequence deferredFaceMessage = null; if (biometricSourceType == FACE) { - deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { + deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (DEBUG) { + Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage); + } + } mFaceAcquiredMessageDeferral.reset(); } @@ -1308,14 +1324,4 @@ public class KeyguardIndicationController { } } }; - - private final BiometricMessageDeferral mFaceAcquiredMessageDeferral = - new BiometricMessageDeferral( - Set.of( - FACE_ACQUIRED_GOOD, - FACE_ACQUIRED_START, - FACE_ACQUIRED_FIRST_FRAME_RECEIVED - ), - Set.of(FACE_ACQUIRED_TOO_DARK) - ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt deleted file mode 100644 index 419fedf99c15..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class BiometricMessageDeferralTest : SysuiTestCase() { - - @Test - fun testProcessNoMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf()) - - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testProcessNonDeferredMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there are no deferred messages processed - for (i in 0..3) { - biometricMessageDeferral.processMessage(4, "test") - } - - // THEN getDeferredMessage is null - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testAllProcessedMessagesWereDeferred() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN all the processed messages are a deferred message - for (i in 0..3) { - biometricMessageDeferral.processMessage(1, "test") - } - - // THEN deferredMessage will return the string associated with the deferred msgId - assertEquals("test", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testReturnsMostFrequentDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there's two msgId=1 processed and one msgId=2 processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN the most frequent deferred message is that meets the threshold is returned - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_mustMeetThreshold() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN more nonDeferredMessages are shown than the deferred message - val totalMessages = 10 - val nonDeferredMessagesCount = - (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until nonDeferredMessagesCount) { - biometricMessageDeferral.processMessage(4, "non-deferred-msg") - } - for (i in nonDeferredMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there's no deferred message because it didn't meet the threshold - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_manyExcludedMessages_getDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1)) - - // WHEN more excludedMessages are shown than the deferred message - val totalMessages = 10 - val excludedMessagesCount = (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until excludedMessagesCount) { - biometricMessageDeferral.processMessage(3, "excluded-msg") - } - for (i in excludedMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there IS a deferred message because the deferred msg meets the threshold amongst the - // non-excluded messages - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testResetClearsOutCounts() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // GIVEN two msgId=1 events processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - - // WHEN counts are reset and then a single deferred message is processed (msgId=2) - biometricMessageDeferral.reset() - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN msgId-2 is the deferred message since the two msgId=1 events were reset - assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testShouldDefer() { - // GIVEN should defer msgIds 1 and 2 - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1, 2)) - - // THEN shouldDefer returns true for ids 1 & 2 - assertTrue(biometricMessageDeferral.shouldDefer(1)) - assertTrue(biometricMessageDeferral.shouldDefer(2)) - - // THEN should defer returns false for ids 3 & 4 - assertFalse(biometricMessageDeferral.shouldDefer(3)) - assertFalse(biometricMessageDeferral.shouldDefer(4)) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt new file mode 100644 index 000000000000..c9ccdb36da89 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FaceHelpMessageDeferralTest : SysuiTestCase() { + val threshold = .75f + @Mock lateinit var logger: BiometricMessageDeferralLogger + @Mock lateinit var dumpManager: DumpManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testProcessFrame_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.processFrame(1) + verify(logger).logFrameProcessed(1, 1, "1") + } + + @Test + fun testUpdateMessage_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.updateMessage(1, "hi") + verify(logger).logUpdateMessage(1, "hi") + } + + @Test + fun testReset_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.reset() + verify(logger).reset() + } + + @Test + fun testProcessNoMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(emptySet()) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessNonDeferredMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there are no deferred messages processed + for (i in 0..3) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "test") + } + + // THEN getDeferredMessage is null + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessMessagesWithDeferredMessage_deferredMessageWasNeverGivenAString() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + biometricMessageDeferral.processFrame(1) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testAllProcessedMessagesWereDeferred() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN all the processed messages are a deferred message + for (i in 0..3) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "test") + } + + // THEN deferredMessage will return the string associated with the deferred msgId + assertEquals("test", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testReturnsMostFrequentDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2 + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN the most frequent deferred message is that meets the threshold is returned + assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testDeferredMessage_mustMeetThreshold() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN more nonDeferredMessages are shown than the deferred message + val totalMessages = 10 + val nonDeferredMessagesCount = (totalMessages * threshold).toInt() + 1 + for (i in 0 until nonDeferredMessagesCount) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "non-deferred-msg") + } + for (i in nonDeferredMessagesCount until totalMessages) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + } + + // THEN there's no deferred message because it didn't meet the threshold + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testResetClearsOutCounts() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // GIVEN two msgId=1 events processed + biometricMessageDeferral.processFrame( + 1, + ) + biometricMessageDeferral.updateMessage(1, "msgId-1") + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + // WHEN counts are reset and then a single deferred message is processed (msgId=2) + biometricMessageDeferral.reset() + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN msgId-2 is the deferred message since the two msgId=1 events were reset + assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testShouldDefer() { + // GIVEN should defer msgIds 1 and 2 + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // THEN shouldDefer returns true for ids 1 & 2 + assertTrue(biometricMessageDeferral.shouldDefer(1)) + assertTrue(biometricMessageDeferral.shouldDefer(2)) + + // THEN should defer returns false for ids 3 & 4 + assertFalse(biometricMessageDeferral.shouldDefer(3)) + assertFalse(biometricMessageDeferral.shouldDefer(4)) + } + + private fun createMsgDeferral(messagesToDefer: Set<Int>): BiometricMessageDeferral { + return BiometricMessageDeferral(messagesToDefer, threshold, logger, dumpManager) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index ac8874b0e131..945cf7f8774f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -19,8 +19,10 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; @@ -86,6 +88,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; @@ -167,6 +170,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock + private FaceHelpMessageDeferral mFaceHelpMessageDeferral; + @Mock private ScreenLifecycle mScreenLifecycle; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @@ -259,7 +264,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils, - mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager); + mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, + mFaceHelpMessageDeferral); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); @@ -535,7 +541,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.setVisible(true); mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); @@ -582,7 +588,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN there's a face not recognized message mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); @@ -748,8 +754,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(false); - // WHEN help message received + // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -777,8 +785,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); - // WHEN help message received + // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -1123,7 +1133,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen); } - @Test public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() { // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled @@ -1269,6 +1278,87 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen); } + @Test + public void faceOnAcquired_processFrame() { + createController(); + + // WHEN face sends an acquired message + final int acquireInfo = 1; + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo); + + // THEN face help message deferral should process the acquired frame + verify(mFaceHelpMessageDeferral).processFrame(acquireInfo); + } + + @Test + public void fingerprintOnAcquired_noProcessFrame() { + createController(); + + // WHEN fingerprint sends an acquired message + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1); + + // THEN face help message deferral should NOT process any acquired frames + verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt()); + } + + @Test + public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() { + createController(); + + // WHEN fingerprint sends an onBiometricHelp + mKeyguardUpdateMonitorCallback.onBiometricHelp( + 1, + "placeholder", + BiometricSourceType.FINGERPRINT); + + // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString()); + verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt()); + } + + @Test + public void onBiometricFailed_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricError_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face has an error + mKeyguardUpdateMonitorCallback.onBiometricError(4, "string", + BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + final int msgId = 1; + final String helpString = "test"; + mKeyguardUpdateMonitorCallback.onBiometricHelp( + msgId, + "test", + BiometricSourceType.FACE); + + // THEN face help message deferral is NOT reset and message IS updated + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString); + } + + + private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); } |