• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.biometrics
18 
19 import android.util.Log
20 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
21 
22 /**
23  * Debounces face help messages with parameters:
24  * - window: Window of time (in milliseconds) to analyze face acquired messages)
25  * - startWindow: Window of time on start required before showing the first help message
26  * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
27  *   the user
28  * - threshold: minimum percentage of frames a message must appear in order to show it
29  */
30 class FaceHelpMessageDebouncer(
31     private val window: Long = DEFAULT_WINDOW_MS,
32     private val startWindow: Long = window,
33     private val shownFaceMessageFrequencyBoost: Int = 4,
34     private val threshold: Float = 0f,
35 ) {
36     private val TAG = "FaceHelpMessageDebouncer"
37     private var startTime = 0L
38     private var helpFaceAuthStatuses: MutableList<HelpFaceAuthenticationStatus> = mutableListOf()
39     private var lastMessageIdShown: Int? = null
40 
41     /** Remove messages that are outside of the time [window]. */
42     private fun removeOldMessages(currTimestamp: Long) {
43         var numToRemove = 0
44         // This works under the assumption that timestamps are ordered from first to last
45         // in chronological order
46         for (index in helpFaceAuthStatuses.indices) {
47             if ((helpFaceAuthStatuses[index].createdAt + window) >= currTimestamp) {
48                 break // all timestamps from here and on are within the window
49             }
50             numToRemove += 1
51         }
52 
53         // Remove all outside time window
54         repeat(numToRemove) { helpFaceAuthStatuses.removeFirst() }
55 
56         if (numToRemove > 0) {
57             Log.v(TAG, "removedFirst=$numToRemove")
58         }
59     }
60 
61     private fun getMostFrequentHelpMessageSurpassingThreshold(): HelpFaceAuthenticationStatus? {
62         // freqMap: msgId => frequency
63         val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()
64 
65         // Give shownFaceMessageFrequencyBoost to lastMessageIdShown
66         if (lastMessageIdShown != null) {
67             freqMap.computeIfPresent(lastMessageIdShown!!) { _, value ->
68                 value + shownFaceMessageFrequencyBoost
69             }
70         }
71         // Go through all msgId keys & find the highest frequency msgId
72         val msgIdWithHighestFrequency =
73             freqMap.entries
74                 .maxWithOrNull { (msgId1, freq1), (msgId2, freq2) ->
75                     // ties are broken by more recent message
76                     if (freq1 == freq2) {
77                         helpFaceAuthStatuses
78                             .findLast { it.msgId == msgId1 }!!
79                             .createdAt
80                             .compareTo(
81                                 helpFaceAuthStatuses.findLast { it.msgId == msgId2 }!!.createdAt
82                             )
83                     } else {
84                         freq1.compareTo(freq2)
85                     }
86                 }
87                 ?.key
88 
89         if (msgIdWithHighestFrequency == null) {
90             return null
91         }
92 
93         val freq =
94             if (msgIdWithHighestFrequency == lastMessageIdShown) {
95                     freqMap[msgIdWithHighestFrequency]!! - shownFaceMessageFrequencyBoost
96                 } else {
97                     freqMap[msgIdWithHighestFrequency]!!
98                 }
99                 .toFloat()
100 
101         return if ((freq / helpFaceAuthStatuses.size.toFloat()) >= threshold) {
102             helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
103         } else {
104             Log.v(TAG, "most frequent helpFaceAuthStatus didn't make the threshold: $threshold")
105             null
106         }
107     }
108 
109     fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
110         helpFaceAuthStatuses.add(helpFaceAuthStatus)
111         Log.v(TAG, "added message=$helpFaceAuthStatus")
112     }
113 
114     fun getMessageToShow(atTimestamp: Long): HelpFaceAuthenticationStatus? {
115         if (helpFaceAuthStatuses.isEmpty() || (atTimestamp - startTime) < startWindow) {
116             // there's not enough time that has passed to determine whether to show anything yet
117             Log.v(TAG, "No message; haven't made initial threshold window OR no messages")
118             return null
119         }
120         removeOldMessages(atTimestamp)
121         val messageToShow = getMostFrequentHelpMessageSurpassingThreshold()
122         if (lastMessageIdShown != messageToShow?.msgId) {
123             Log.v(
124                 TAG,
125                 "showMessage previousLastMessageId=$lastMessageIdShown" +
126                     "\n\tmessageToShow=$messageToShow " +
127                     "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
128                     "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses" +
129                     "\n\tthreshold=$threshold"
130             )
131             lastMessageIdShown = messageToShow?.msgId
132         }
133         return messageToShow
134     }
135 
136     fun startNewFaceAuthSession(faceAuthStartedTime: Long) {
137         Log.d(TAG, "startNewFaceAuthSession at startTime=$startTime")
138         startTime = faceAuthStartedTime
139         helpFaceAuthStatuses.clear()
140         lastMessageIdShown = null
141     }
142 
143     companion object {
144         const val DEFAULT_WINDOW_MS = 200L
145     }
146 }
147