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