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 Original line Diff line number Diff line
@@ -218,4 +218,60 @@ class FaceHelpMessageDebouncerTest : SysuiTestCase() {
        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
            .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 Original line Diff line number Diff line
@@ -16,11 +16,15 @@


package com.android.systemui.biometrics
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 androidx.test.filters.SmallTest
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertNull
@@ -31,14 +35,29 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters


@SmallTest
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWith(ParameterizedAndroidJunit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
@android.platform.test.annotations.EnabledOnRavenwood
class FaceHelpMessageDeferralTest : SysuiTestCase() {
class FaceHelpMessageDeferralTest(flags: FlagsParameterization) : SysuiTestCase() {
    val threshold = .75f
    val threshold = .75f
    @Mock lateinit var logger: BiometricMessageDeferralLogger
    @Mock lateinit var logger: BiometricMessageDeferralLogger
    @Mock lateinit var dumpManager: DumpManager
    @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
    @Before
    fun setUp() {
    fun setUp() {
@@ -111,6 +130,7 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
    }
    }


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


@@ -124,7 +144,41 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
        biometricMessageDeferral.processFrame(2)
        biometricMessageDeferral.processFrame(2)
        biometricMessageDeferral.updateMessage(2, "msgId-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())
        assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
    }
    }


@@ -213,14 +267,17 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
    private fun createMsgDeferral(
    private fun createMsgDeferral(
        messagesToDefer: Set<Int>,
        messagesToDefer: Set<Int>,
        acquiredInfoToIgnore: Set<Int> = emptySet(),
        acquiredInfoToIgnore: Set<Int> = emptySet(),
        windowToAnalyzeLastNFrames: Long = 500L,
    ): BiometricMessageDeferral {
    ): BiometricMessageDeferral {
        return BiometricMessageDeferral(
        return BiometricMessageDeferral(
            messagesToDefer,
            messagesToDefer = messagesToDefer,
            acquiredInfoToIgnore,
            acquiredInfoToIgnore = acquiredInfoToIgnore,
            threshold,
            threshold = threshold,
            logger,
            windowToAnalyzeLastNFrames = windowToAnalyzeLastNFrames,
            dumpManager,
            logBuffer = logger,
            "0",
            dumpManager = dumpManager,
            id = "0",
            systemClock = { systemClock },
        )
        )
    }
    }
}
}
+4 −0
Original line number Original line Diff line number Diff line
@@ -727,6 +727,10 @@
        .75
        .75
    </item>
    </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.
    <!-- 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">
+25 −4
Original line number Original line 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
 * - startWindow: Window of time on start required before showing the first help message
 * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
 * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
 *   the user
 *   the user
 * - threshold: minimum percentage of frames a message must appear in order to show it
 */
 */
class FaceHelpMessageDebouncer(
class FaceHelpMessageDebouncer(
    private val window: Long = DEFAULT_WINDOW_MS,
    private val window: Long = DEFAULT_WINDOW_MS,
    private val startWindow: Long = window,
    private val startWindow: Long = window,
    private val shownFaceMessageFrequencyBoost: Int = 4,
    private val shownFaceMessageFrequencyBoost: Int = 4,
    private val threshold: Float = 0f,
) {
) {
    private val TAG = "FaceHelpMessageDebouncer"
    private val TAG = "FaceHelpMessageDebouncer"
    private var startTime = 0L
    private var startTime = 0L
@@ -56,7 +58,7 @@ class FaceHelpMessageDebouncer(
        }
        }
    }
    }


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


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


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


/**
/**
@@ -77,10 +87,24 @@ open class BiometricMessageDeferral(
    private val messagesToDefer: Set<Int>,
    private val messagesToDefer: Set<Int>,
    private val acquiredInfoToIgnore: Set<Int>,
    private val acquiredInfoToIgnore: Set<Int>,
    private val threshold: Float,
    private val threshold: Float,
    private val windowToAnalyzeLastNFrames: Long,
    private val logBuffer: BiometricMessageDeferralLogger,
    private val logBuffer: BiometricMessageDeferralLogger,
    dumpManager: DumpManager,
    dumpManager: DumpManager,
    id: String,
    id: String,
    private val systemClock: Lazy<SystemClock>,
) : Dumpable {
) : 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 acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
    private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
    private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
    private var mostFrequentAcquiredInfoToDefer: Int? = null
    private var mostFrequentAcquiredInfoToDefer: Int? = null
@@ -97,13 +121,20 @@ open class BiometricMessageDeferral(
        pw.println("messagesToDefer=$messagesToDefer")
        pw.println("messagesToDefer=$messagesToDefer")
        pw.println("totalFrames=$totalFrames")
        pw.println("totalFrames=$totalFrames")
        pw.println("threshold=$threshold")
        pw.println("threshold=$threshold")
        pw.println("faceMessageDeferUpdateFlagEnabled=${faceMessageDeferUpdate()}")
        if (faceMessageDeferUpdate()) {
            pw.println("windowToAnalyzeLastNFrames(ms)=$windowToAnalyzeLastNFrames")
        }
    }
    }


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

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

        totalFrames++
        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
            val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
            acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
            acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
            if (
            if (
                messagesToDefer.contains(acquiredInfo) &&
                messagesToDefer.contains(acquiredInfo) &&
                    (mostFrequentAcquiredInfoToDefer == null ||
                    (mostFrequentAcquiredInfoToDefer == null ||
                        newAcquiredInfoCount >
                        newAcquiredInfoCount >
                        acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0))
                            acquiredInfoToFrequency.getOrDefault(
                                mostFrequentAcquiredInfoToDefer!!,
                                0
                            ))
            ) {
            ) {
                mostFrequentAcquiredInfoToDefer = acquiredInfo
                mostFrequentAcquiredInfoToDefer = acquiredInfo
            }
            }
        }


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


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