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 com.android.keyguard.logging.BiometricMessageDeferralLogger
21 import com.android.keyguard.logging.FaceMessageDeferralLogger
22 import com.android.systemui.Dumpable
23 import com.android.systemui.R
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.dump.DumpManager
27 import java.io.PrintWriter
28 import java.util.Objects
29 import javax.inject.Inject
30 
31 /**
32  * Provides whether a face acquired help message should be shown immediately when its received or
33  * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage].
34  */
35 @SysUISingleton
36 class FaceHelpMessageDeferral
37 @Inject
38 constructor(
39     @Main resources: Resources,
40     logBuffer: FaceMessageDeferralLogger,
41     dumpManager: DumpManager
42 ) :
43     BiometricMessageDeferral(
44         resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
45         resources.getIntArray(R.array.config_face_help_msgs_ignore).toHashSet(),
46         resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
47         logBuffer,
48         dumpManager
49     )
50 
51 /**
52  * @property messagesToDefer messages that shouldn't show immediately when received, but may be
53  *   shown later if the message is the most frequent acquiredInfo processed and meets [threshold]
54  *   percentage of all acquired frames, excluding [acquiredInfoToIgnore].
55  */
56 open class BiometricMessageDeferral(
57     private val messagesToDefer: Set<Int>,
58     private val acquiredInfoToIgnore: Set<Int>,
59     private val threshold: Float,
60     private val logBuffer: BiometricMessageDeferralLogger,
61     dumpManager: DumpManager
62 ) : Dumpable {
63     private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
64     private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
65     private var mostFrequentAcquiredInfoToDefer: Int? = null
66     private var totalFrames = 0
67 
68     init {
69         dumpManager.registerDumpable(this.javaClass.name, this)
70     }
71 
72     override fun dump(pw: PrintWriter, args: Array<out String>) {
73         pw.println("messagesToDefer=$messagesToDefer")
74         pw.println("totalFrames=$totalFrames")
75         pw.println("threshold=$threshold")
76     }
77 
78     /** Reset all saved counts. */
79     fun reset() {
80         totalFrames = 0
81         mostFrequentAcquiredInfoToDefer = null
82         acquiredInfoToFrequency.clear()
83         acquiredInfoToHelpString.clear()
84         logBuffer.reset()
85     }
86 
87     /** Updates the message associated with the acquiredInfo if it's a message we may defer. */
88     fun updateMessage(acquiredInfo: Int, helpString: String) {
89         if (!messagesToDefer.contains(acquiredInfo)) {
90             return
91         }
92         if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) {
93             logBuffer.logUpdateMessage(acquiredInfo, helpString)
94             acquiredInfoToHelpString[acquiredInfo] = helpString
95         }
96     }
97 
98     /** Whether the given message should be deferred instead of being shown immediately. */
99     fun shouldDefer(acquiredMsgId: Int): Boolean {
100         return messagesToDefer.contains(acquiredMsgId)
101     }
102 
103     /**
104      * Adds the acquiredInfo frame to the counts. We account for frames not included in
105      * acquiredInfoToIgnore.
106      */
107     fun processFrame(acquiredInfo: Int) {
108         if (messagesToDefer.isEmpty()) {
109             return
110         }
111 
112         if (acquiredInfoToIgnore.contains(acquiredInfo)) {
113             logBuffer.logFrameIgnored(acquiredInfo)
114             return
115         }
116 
117         totalFrames++
118 
119         val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
120         acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
121         if (
122             messagesToDefer.contains(acquiredInfo) &&
123                 (mostFrequentAcquiredInfoToDefer == null ||
124                     newAcquiredInfoCount >
125                         acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0))
126         ) {
127             mostFrequentAcquiredInfoToDefer = acquiredInfo
128         }
129 
130         logBuffer.logFrameProcessed(
131             acquiredInfo,
132             totalFrames,
133             mostFrequentAcquiredInfoToDefer?.toString()
134         )
135     }
136 
137     /**
138      * Get the most frequent deferred message that meets the [threshold] percentage of processed
139      * frames.
140      *
141      * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the
142      *   [threshold] percentage.
143      */
144     fun getDeferredMessage(): CharSequence? {
145         mostFrequentAcquiredInfoToDefer?.let {
146             if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
147                 return acquiredInfoToHelpString[it]
148             }
149         }
150         return null
151     }
152 }
153