Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d42ad47c authored by Beverly's avatar Beverly Committed by Beverly Tai
Browse files

Only analyze the last 500ms of deferred face auth info

To determine whether to defer a face auth help message
when SysUI receives the face auth TIMEOUT.

For face auth that sends 30fps, this will analyze the last
15 frames before the timeout to determine whether to show
deferred messages like "low light".

Test: atest FaceHelpMessageDeferralTest FaceHelpMessageDebouncerTest
Bug: 351863611
Flag: com.android.systemui.face_message_defer_update
Change-Id: I9dc2eed3924f2f47a81dc0e2538fd9ff151b6146
parent 992ffb63
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
@@ -218,4 +218,60 @@ class FaceHelpMessageDebouncerTest : SysuiTestCase() {
        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
    }

    @Test
    fun messageMustMeetThreshold() {
        underTest =
            FaceHelpMessageDebouncer(
                window = window,
                startWindow = 0,
                shownFaceMessageFrequencyBoost = 0,
                threshold = .8f,
            )

        underTest.addMessage(
            HelpFaceAuthenticationStatus(
                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
                "tooClose",
                0
            )
        )
        underTest.addMessage(
            HelpFaceAuthenticationStatus(
                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
                "tooClose",
                0
            )
        )
        underTest.addMessage(
            HelpFaceAuthenticationStatus(
                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
                "tooBright",
                0
            )
        )

        // although tooClose message is the majority, it doesn't meet the 80% threshold
        assertThat(underTest.getMessageToShow(startWindow)).isNull()

        underTest.addMessage(
            HelpFaceAuthenticationStatus(
                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
                "tooClose",
                0
            )
        )
        underTest.addMessage(
            HelpFaceAuthenticationStatus(
                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
                "tooClose",
                0
            )
        )

        // message shows once it meets the threshold
        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
        assertThat(underTest.getMessageToShow(startWindow)?.msg).isEqualTo("tooClose")
    }
}
+68 −11
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

package com.android.systemui.biometrics

import androidx.test.ext.junit.runners.AndroidJUnit4
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -31,14 +35,29 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWith(ParameterizedAndroidJunit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
class FaceHelpMessageDeferralTest : SysuiTestCase() {
class FaceHelpMessageDeferralTest(flags: FlagsParameterization) : SysuiTestCase() {
    val threshold = .75f
    @Mock lateinit var logger: BiometricMessageDeferralLogger
    @Mock lateinit var dumpManager: DumpManager
    val systemClock = FakeSystemClock()

    companion object {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return FlagsParameterization.allCombinationsOf(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
        }
    }

    init {
        mSetFlagsRule.setFlagsParameterization(flags)
    }

    @Before
    fun setUp() {
@@ -111,6 +130,7 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
    fun testReturnsMostFrequentDeferredMessage() {
        val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))

@@ -124,7 +144,41 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
        biometricMessageDeferral.processFrame(2)
        biometricMessageDeferral.updateMessage(2, "msgId-2")

        // THEN the most frequent deferred message is that meets the threshold is returned
        // THEN the most frequent deferred message that meets the threshold is returned
        assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
    }

    @Test
    @EnableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
    fun testReturnsMostFrequentDeferredMessage_onlyAnalyzesLastNWindow() {
        val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))

        // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2, but the last
        // N window only contains messages with msgId=2
        repeat(80) { biometricMessageDeferral.processFrame(1) }
        biometricMessageDeferral.updateMessage(1, "msgId-1")
        systemClock.setElapsedRealtime(systemClock.elapsedRealtime() + 501L)
        repeat(20) { biometricMessageDeferral.processFrame(2) }
        biometricMessageDeferral.updateMessage(2, "msgId-2")

        // THEN the most frequent deferred message in the last N window (500L) is returned
        assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
    }

    @Test
    @DisableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
    fun testReturnsMostFrequentDeferredMessage_analyzesAllFrames() {
        val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))

        // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2, but the last
        // N window only contains messages with msgId=2
        repeat(80) { biometricMessageDeferral.processFrame(1) }
        biometricMessageDeferral.updateMessage(1, "msgId-1")
        systemClock.setElapsedRealtime(systemClock.elapsedRealtime() + 501L)
        repeat(20) { biometricMessageDeferral.processFrame(2) }
        biometricMessageDeferral.updateMessage(2, "msgId-2")

        // THEN the most frequent deferred message is returned
        assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
    }

@@ -213,14 +267,17 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
    private fun createMsgDeferral(
        messagesToDefer: Set<Int>,
        acquiredInfoToIgnore: Set<Int> = emptySet(),
        windowToAnalyzeLastNFrames: Long = 500L,
    ): BiometricMessageDeferral {
        return BiometricMessageDeferral(
            messagesToDefer,
            acquiredInfoToIgnore,
            threshold,
            logger,
            dumpManager,
            "0",
            messagesToDefer = messagesToDefer,
            acquiredInfoToIgnore = acquiredInfoToIgnore,
            threshold = threshold,
            windowToAnalyzeLastNFrames = windowToAnalyzeLastNFrames,
            logBuffer = logger,
            dumpManager = dumpManager,
            id = "0",
            systemClock = { systemClock },
        )
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -727,6 +727,10 @@
        .75
    </item>

    <!-- The last x ms of face acquired info messages to analyze to determine
         whether to show a deferred face auth help message. -->
    <integer name="config_face_help_msgs_defer_analyze_timeframe">500</integer>

    <!-- 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">
+25 −4
Original line number Diff line number Diff line
@@ -25,11 +25,13 @@ import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatu
 * - startWindow: Window of time on start required before showing the first help message
 * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
 *   the user
 * - threshold: minimum percentage of frames a message must appear in order to show it
 */
class FaceHelpMessageDebouncer(
    private val window: Long = DEFAULT_WINDOW_MS,
    private val startWindow: Long = window,
    private val shownFaceMessageFrequencyBoost: Int = 4,
    private val threshold: Float = 0f,
) {
    private val TAG = "FaceHelpMessageDebouncer"
    private var startTime = 0L
@@ -56,7 +58,7 @@ class FaceHelpMessageDebouncer(
        }
    }

    private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? {
    private fun getMostFrequentHelpMessageSurpassingThreshold(): HelpFaceAuthenticationStatus? {
        // freqMap: msgId => frequency
        val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()

@@ -83,7 +85,25 @@ class FaceHelpMessageDebouncer(
                    }
                }
                ?.key
        return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }

        if (msgIdWithHighestFrequency == null) {
            return null
        }

        val freq =
            if (msgIdWithHighestFrequency == lastMessageIdShown) {
                    freqMap[msgIdWithHighestFrequency]!! - shownFaceMessageFrequencyBoost
                } else {
                    freqMap[msgIdWithHighestFrequency]!!
                }
                .toFloat()

        return if ((freq / helpFaceAuthStatuses.size.toFloat()) >= threshold) {
            helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
        } else {
            Log.v(TAG, "most frequent helpFaceAuthStatus didn't make the threshold: $threshold")
            null
        }
    }

    fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
@@ -98,14 +118,15 @@ class FaceHelpMessageDebouncer(
            return null
        }
        removeOldMessages(atTimestamp)
        val messageToShow = getMostFrequentHelpMessage()
        val messageToShow = getMostFrequentHelpMessageSurpassingThreshold()
        if (lastMessageIdShown != messageToShow?.msgId) {
            Log.v(
                TAG,
                "showMessage previousLastMessageId=$lastMessageIdShown" +
                    "\n\tmessageToShow=$messageToShow " +
                    "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
                    "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses"
                    "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses" +
                    "\n\tthreshold=$threshold"
            )
            lastMessageIdShown = messageToShow?.msgId
        }
+79 −17
Original line number Diff line number Diff line
@@ -17,14 +17,19 @@
package com.android.systemui.biometrics

import android.content.res.Resources
import android.os.SystemClock.elapsedRealtime
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.systemui.Dumpable
import com.android.systemui.Flags.faceMessageDeferUpdate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.res.R
import com.android.systemui.util.time.SystemClock
import dagger.Lazy
import java.io.PrintWriter
import java.util.Objects
import java.util.UUID
@@ -36,7 +41,8 @@ class FaceHelpMessageDeferralFactory
constructor(
    @Main private val resources: Resources,
    @BiometricLog private val logBuffer: LogBuffer,
    private val dumpManager: DumpManager
    private val dumpManager: DumpManager,
    private val systemClock: Lazy<SystemClock>,
) {
    fun create(): FaceHelpMessageDeferral {
        val id = UUID.randomUUID().toString()
@@ -45,6 +51,7 @@ constructor(
            logBuffer = BiometricMessageDeferralLogger(logBuffer, "FaceHelpMessageDeferral[$id]"),
            dumpManager = dumpManager,
            id = id,
            systemClock,
        )
    }
}
@@ -58,14 +65,17 @@ class FaceHelpMessageDeferral(
    logBuffer: BiometricMessageDeferralLogger,
    dumpManager: DumpManager,
    val id: String,
    val systemClock: Lazy<SystemClock>,
) :
    BiometricMessageDeferral(
        resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
        resources.getIntArray(R.array.config_face_help_msgs_ignore).toHashSet(),
        resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
        resources.getInteger(R.integer.config_face_help_msgs_defer_analyze_timeframe).toLong(),
        logBuffer,
        dumpManager,
        id,
        systemClock,
    )

/**
@@ -77,10 +87,24 @@ open class BiometricMessageDeferral(
    private val messagesToDefer: Set<Int>,
    private val acquiredInfoToIgnore: Set<Int>,
    private val threshold: Float,
    private val windowToAnalyzeLastNFrames: Long,
    private val logBuffer: BiometricMessageDeferralLogger,
    dumpManager: DumpManager,
    id: String,
    private val systemClock: Lazy<SystemClock>,
) : Dumpable {

    private val faceHelpMessageDebouncer: FaceHelpMessageDebouncer? =
        if (faceMessageDeferUpdate()) {
            FaceHelpMessageDebouncer(
                window = windowToAnalyzeLastNFrames,
                startWindow = 0L,
                shownFaceMessageFrequencyBoost = 0,
                threshold = threshold,
            )
        } else {
            null
        }
    private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
    private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
    private var mostFrequentAcquiredInfoToDefer: Int? = null
@@ -97,13 +121,20 @@ open class BiometricMessageDeferral(
        pw.println("messagesToDefer=$messagesToDefer")
        pw.println("totalFrames=$totalFrames")
        pw.println("threshold=$threshold")
        pw.println("faceMessageDeferUpdateFlagEnabled=${faceMessageDeferUpdate()}")
        if (faceMessageDeferUpdate()) {
            pw.println("windowToAnalyzeLastNFrames(ms)=$windowToAnalyzeLastNFrames")
        }
    }

    /** Reset all saved counts. */
    fun reset() {
        totalFrames = 0
        if (!faceMessageDeferUpdate()) {
            mostFrequentAcquiredInfoToDefer = null
            acquiredInfoToFrequency.clear()
        }

        acquiredInfoToHelpString.clear()
        logBuffer.reset()
    }
@@ -137,24 +168,48 @@ open class BiometricMessageDeferral(
            logBuffer.logFrameIgnored(acquiredInfo)
            return
        }

        totalFrames++

        if (faceMessageDeferUpdate()) {
            faceHelpMessageDebouncer?.let {
                val helpFaceAuthStatus =
                    HelpFaceAuthenticationStatus(
                        msgId = acquiredInfo,
                        msg = null,
                        systemClock.get().elapsedRealtime()
                    )
                if (totalFrames == 1) { // first frame
                    it.startNewFaceAuthSession(helpFaceAuthStatus.createdAt)
                }
                it.addMessage(helpFaceAuthStatus)
            }
        } else {
            val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
            acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
            if (
                messagesToDefer.contains(acquiredInfo) &&
                    (mostFrequentAcquiredInfoToDefer == null ||
                        newAcquiredInfoCount >
                        acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0))
                            acquiredInfoToFrequency.getOrDefault(
                                mostFrequentAcquiredInfoToDefer!!,
                                0
                            ))
            ) {
                mostFrequentAcquiredInfoToDefer = acquiredInfo
            }
        }

        logBuffer.logFrameProcessed(
            acquiredInfo,
            totalFrames,
            if (faceMessageDeferUpdate()) {
                faceHelpMessageDebouncer
                    ?.getMessageToShow(systemClock.get().elapsedRealtime())
                    ?.msgId
                    .toString()
            } else {
                mostFrequentAcquiredInfoToDefer?.toString()
            }
        )
    }

@@ -166,11 +221,18 @@ open class BiometricMessageDeferral(
     *   [threshold] percentage.
     */
    fun getDeferredMessage(): CharSequence? {
        if (faceMessageDeferUpdate()) {
            faceHelpMessageDebouncer?.let {
                val helpFaceAuthStatus = it.getMessageToShow(systemClock.get().elapsedRealtime())
                return acquiredInfoToHelpString[helpFaceAuthStatus?.msgId]
            }
        } else {
            mostFrequentAcquiredInfoToDefer?.let {
                if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
                    return acquiredInfoToHelpString[it]
                }
            }
        }
        return null
    }
}