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