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.* 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.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), 46 logBuffer, 47 dumpManager 48 ) 49 50 /** 51 * @property messagesToDefer messages that shouldn't show immediately when received, but may be 52 * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] 53 * percentage of all passed acquired frames. 54 */ 55 open class BiometricMessageDeferral( 56 private val messagesToDefer: Set<Int>, 57 private val threshold: Float, 58 private val logBuffer: BiometricMessageDeferralLogger, 59 dumpManager: DumpManager 60 ) : Dumpable { 61 private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() 62 private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() 63 private var mostFrequentAcquiredInfoToDefer: Int? = null 64 private var totalFrames = 0 65 66 init { 67 dumpManager.registerDumpable(this.javaClass.name, this) 68 } 69 dumpnull70 override fun dump(pw: PrintWriter, args: Array<out String>) { 71 pw.println("messagesToDefer=$messagesToDefer") 72 pw.println("totalFrames=$totalFrames") 73 pw.println("threshold=$threshold") 74 } 75 76 /** Reset all saved counts. */ resetnull77 fun reset() { 78 totalFrames = 0 79 mostFrequentAcquiredInfoToDefer = null 80 acquiredInfoToFrequency.clear() 81 acquiredInfoToHelpString.clear() 82 logBuffer.reset() 83 } 84 85 /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ updateMessagenull86 fun updateMessage(acquiredInfo: Int, helpString: String) { 87 if (!messagesToDefer.contains(acquiredInfo)) { 88 return 89 } 90 if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { 91 logBuffer.logUpdateMessage(acquiredInfo, helpString) 92 acquiredInfoToHelpString[acquiredInfo] = helpString 93 } 94 } 95 96 /** Whether the given message should be deferred instead of being shown immediately. */ shouldDefernull97 fun shouldDefer(acquiredMsgId: Int): Boolean { 98 return messagesToDefer.contains(acquiredMsgId) 99 } 100 101 /** Adds the acquiredInfo frame to the counts. We account for all frames. */ processFramenull102 fun processFrame(acquiredInfo: Int) { 103 if (messagesToDefer.isEmpty()) { 104 return 105 } 106 107 totalFrames++ 108 109 val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 110 acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount 111 if ( 112 messagesToDefer.contains(acquiredInfo) && 113 (mostFrequentAcquiredInfoToDefer == null || 114 newAcquiredInfoCount > 115 acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) 116 ) { 117 mostFrequentAcquiredInfoToDefer = acquiredInfo 118 } 119 120 logBuffer.logFrameProcessed( 121 acquiredInfo, 122 totalFrames, 123 mostFrequentAcquiredInfoToDefer?.toString() 124 ) 125 } 126 127 /** 128 * Get the most frequent deferred message that meets the [threshold] percentage of processed 129 * frames. 130 * 131 * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the 132 * [threshold] percentage. 133 */ getDeferredMessagenull134 fun getDeferredMessage(): CharSequence? { 135 mostFrequentAcquiredInfoToDefer?.let { 136 if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { 137 return acquiredInfoToHelpString[it] 138 } 139 } 140 return null 141 } 142 } 143