• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.content.res.Resources
20 import android.os.SystemClock.elapsedRealtime
21 import com.android.keyguard.logging.BiometricMessageDeferralLogger
22 import com.android.systemui.Dumpable
23 import com.android.systemui.Flags.faceMessageDeferUpdate
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
27 import com.android.systemui.dump.DumpManager
28 import com.android.systemui.log.LogBuffer
29 import com.android.systemui.log.dagger.BiometricLog
30 import com.android.systemui.res.R
31 import com.android.systemui.util.time.SystemClock
32 import dagger.Lazy
33 import java.io.PrintWriter
34 import java.util.Objects
35 import java.util.UUID
36 import javax.inject.Inject
37 
38 @SysUISingleton
39 class FaceHelpMessageDeferralFactory
40 @Inject
41 constructor(
42     @Main private val resources: Resources,
43     @BiometricLog private val logBuffer: LogBuffer,
44     private val dumpManager: DumpManager,
45     private val systemClock: Lazy<SystemClock>,
46 ) {
createnull47     fun create(): FaceHelpMessageDeferral {
48         val id = UUID.randomUUID().toString()
49         return FaceHelpMessageDeferral(
50             resources = resources,
51             logBuffer = BiometricMessageDeferralLogger(logBuffer, "FaceHelpMessageDeferral[$id]"),
52             dumpManager = dumpManager,
53             id = id,
54             systemClock,
55         )
56     }
57 }
58 
59 /**
60  * Provides whether a face acquired help message should be shown immediately when its received or
61  * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage].
62  */
63 class FaceHelpMessageDeferral(
64     resources: Resources,
65     logBuffer: BiometricMessageDeferralLogger,
66     dumpManager: DumpManager,
67     val id: String,
68     val systemClock: Lazy<SystemClock>,
69 ) :
70     BiometricMessageDeferral(
71         resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
72         resources.getIntArray(R.array.config_face_help_msgs_ignore).toHashSet(),
73         resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
74         resources.getInteger(R.integer.config_face_help_msgs_defer_analyze_timeframe).toLong(),
75         logBuffer,
76         dumpManager,
77         id,
78         systemClock,
79     )
80 
81 /**
82  * @property messagesToDefer messages that shouldn't show immediately when received, but may be
83  *   shown later if the message is the most frequent acquiredInfo processed and meets [threshold]
84  *   percentage of all acquired frames, excluding [acquiredInfoToIgnore].
85  */
86 open class BiometricMessageDeferral(
87     private val messagesToDefer: Set<Int>,
88     private val acquiredInfoToIgnore: Set<Int>,
89     private val threshold: Float,
90     private val windowToAnalyzeLastNFrames: Long,
91     private val logBuffer: BiometricMessageDeferralLogger,
92     dumpManager: DumpManager,
93     id: String,
94     private val systemClock: Lazy<SystemClock>,
95 ) : Dumpable {
96 
97     private val faceHelpMessageDebouncer: FaceHelpMessageDebouncer? =
98         if (faceMessageDeferUpdate()) {
99             FaceHelpMessageDebouncer(
100                 window = windowToAnalyzeLastNFrames,
101                 startWindow = 0L,
102                 shownFaceMessageFrequencyBoost = 0,
103                 threshold = threshold,
104             )
105         } else {
106             null
107         }
108     private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
109     private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
110     private var mostFrequentAcquiredInfoToDefer: Int? = null
111     private var totalFrames = 0
112 
113     init {
114         dumpManager.registerNormalDumpable(
115             "${this.javaClass.name}[$id]",
116             this,
117         )
118     }
119 
dumpnull120     override fun dump(pw: PrintWriter, args: Array<out String>) {
121         pw.println("messagesToDefer=$messagesToDefer")
122         pw.println("totalFrames=$totalFrames")
123         pw.println("threshold=$threshold")
124         pw.println("faceMessageDeferUpdateFlagEnabled=${faceMessageDeferUpdate()}")
125         if (faceMessageDeferUpdate()) {
126             pw.println("windowToAnalyzeLastNFrames(ms)=$windowToAnalyzeLastNFrames")
127         }
128     }
129 
130     /** Reset all saved counts. */
resetnull131     fun reset() {
132         totalFrames = 0
133         if (!faceMessageDeferUpdate()) {
134             mostFrequentAcquiredInfoToDefer = null
135             acquiredInfoToFrequency.clear()
136         }
137 
138         acquiredInfoToHelpString.clear()
139         logBuffer.reset()
140     }
141 
142     /** Updates the message associated with the acquiredInfo if it's a message we may defer. */
updateMessagenull143     fun updateMessage(acquiredInfo: Int, helpString: String) {
144         if (!messagesToDefer.contains(acquiredInfo)) {
145             return
146         }
147         if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) {
148             logBuffer.logUpdateMessage(acquiredInfo, helpString)
149             acquiredInfoToHelpString[acquiredInfo] = helpString
150         }
151     }
152 
153     /** Whether the given message should be deferred instead of being shown immediately. */
shouldDefernull154     fun shouldDefer(acquiredMsgId: Int): Boolean {
155         return messagesToDefer.contains(acquiredMsgId)
156     }
157 
158     /**
159      * Adds the acquiredInfo frame to the counts. We account for frames not included in
160      * acquiredInfoToIgnore.
161      */
processFramenull162     fun processFrame(acquiredInfo: Int) {
163         if (messagesToDefer.isEmpty()) {
164             return
165         }
166 
167         if (acquiredInfoToIgnore.contains(acquiredInfo)) {
168             logBuffer.logFrameIgnored(acquiredInfo)
169             return
170         }
171         totalFrames++
172 
173         if (faceMessageDeferUpdate()) {
174             faceHelpMessageDebouncer?.let {
175                 val helpFaceAuthStatus =
176                     HelpFaceAuthenticationStatus(
177                         msgId = acquiredInfo,
178                         msg = null,
179                         systemClock.get().elapsedRealtime()
180                     )
181                 if (totalFrames == 1) { // first frame
182                     it.startNewFaceAuthSession(helpFaceAuthStatus.createdAt)
183                 }
184                 it.addMessage(helpFaceAuthStatus)
185             }
186         } else {
187             val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
188             acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
189             if (
190                 messagesToDefer.contains(acquiredInfo) &&
191                     (mostFrequentAcquiredInfoToDefer == null ||
192                         newAcquiredInfoCount >
193                             acquiredInfoToFrequency.getOrDefault(
194                                 mostFrequentAcquiredInfoToDefer!!,
195                                 0
196                             ))
197             ) {
198                 mostFrequentAcquiredInfoToDefer = acquiredInfo
199             }
200         }
201 
202         logBuffer.logFrameProcessed(
203             acquiredInfo,
204             totalFrames,
205             if (faceMessageDeferUpdate()) {
206                 faceHelpMessageDebouncer
207                     ?.getMessageToShow(systemClock.get().elapsedRealtime())
208                     ?.msgId
209                     .toString()
210             } else {
211                 mostFrequentAcquiredInfoToDefer?.toString()
212             }
213         )
214     }
215 
216     /**
217      * Get the most frequent deferred message that meets the [threshold] percentage of processed
218      * frames.
219      *
220      * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the
221      *   [threshold] percentage.
222      */
getDeferredMessagenull223     fun getDeferredMessage(): CharSequence? {
224         if (faceMessageDeferUpdate()) {
225             faceHelpMessageDebouncer?.let {
226                 val helpFaceAuthStatus = it.getMessageToShow(systemClock.get().elapsedRealtime())
227                 return acquiredInfoToHelpString[helpFaceAuthStatus?.msgId]
228             }
229         } else {
230             mostFrequentAcquiredInfoToDefer?.let {
231                 if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
232                     return acquiredInfoToHelpString[it]
233                 }
234             }
235         }
236         return null
237     }
238 }
239