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