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.settings.biometrics.fingerprint; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.hardware.fingerprint.FingerprintManager; 22 import android.os.Handler; 23 24 import java.time.Clock; 25 import java.util.ArrayDeque; 26 import java.util.Deque; 27 import java.util.HashMap; 28 29 /** 30 * Processes message provided from the enrollment callback and filters them based 31 * on the below configurable flags. This is primarily used to reduce the rate 32 * at which messages come through, which in turns eliminates UI flicker. 33 */ 34 public class MessageDisplayController extends FingerprintManager.EnrollmentCallback { 35 36 private final int mHelpMinimumDisplayTime; 37 private final int mProgressMinimumDisplayTime; 38 private final boolean mProgressPriorityOverHelp; 39 private final boolean mPrioritizeAcquireMessages; 40 private final int mCollectTime; 41 @NonNull 42 private final Deque<HelpMessage> mHelpMessageList; 43 @NonNull 44 private final Deque<ProgressMessage> mProgressMessageList; 45 @NonNull 46 private final Handler mHandler; 47 @NonNull 48 private final Clock mClock; 49 @NonNull 50 private final Runnable mDisplayMessageRunnable; 51 52 @Nullable 53 private ProgressMessage mLastProgressMessageDisplayed; 54 private boolean mMustDisplayProgress; 55 private boolean mWaitingForMessage; 56 @NonNull FingerprintManager.EnrollmentCallback mEnrollmentCallback; 57 58 private abstract static class Message { 59 long mTimeStamp = 0; display()60 abstract void display(); 61 } 62 63 private class HelpMessage extends Message { 64 private final int mHelpMsgId; 65 private final CharSequence mHelpString; 66 HelpMessage(int helpMsgId, CharSequence helpString)67 HelpMessage(int helpMsgId, CharSequence helpString) { 68 mHelpMsgId = helpMsgId; 69 mHelpString = helpString; 70 mTimeStamp = mClock.millis(); 71 } 72 73 @Override display()74 void display() { 75 mEnrollmentCallback.onEnrollmentHelp(mHelpMsgId, mHelpString); 76 mHandler.postDelayed(mDisplayMessageRunnable, mHelpMinimumDisplayTime); 77 } 78 } 79 80 private class ProgressMessage extends Message { 81 private final int mRemaining; 82 ProgressMessage(int remaining)83 ProgressMessage(int remaining) { 84 mRemaining = remaining; 85 mTimeStamp = mClock.millis(); 86 } 87 88 @Override display()89 void display() { 90 mEnrollmentCallback.onEnrollmentProgress(mRemaining); 91 mLastProgressMessageDisplayed = this; 92 mHandler.postDelayed(mDisplayMessageRunnable, mProgressMinimumDisplayTime); 93 } 94 } 95 96 /** 97 * Creating a MessageDisplayController object. 98 * @param handler main handler to run message queue 99 * @param enrollmentCallback callback to display messages 100 * @param clock real time system clock 101 * @param helpMinimumDisplayTime the minimum duration (in millis) that 102 * a help message needs to be displayed for 103 * @param progressMinimumDisplayTime the minimum duration (in millis) that 104 * a progress message needs to be displayed for 105 * @param progressPriorityOverHelp if true, then progress message is displayed 106 * when both help and progress message APIs have been called 107 * @param prioritizeAcquireMessages if true, then displays the help message 108 * which has occurred the most after the last display message 109 * @param collectTime the waiting time (in millis) to collect messages when it is idle 110 */ MessageDisplayController(@onNull Handler handler, FingerprintManager.EnrollmentCallback enrollmentCallback, @NonNull Clock clock, int helpMinimumDisplayTime, int progressMinimumDisplayTime, boolean progressPriorityOverHelp, boolean prioritizeAcquireMessages, int collectTime)111 public MessageDisplayController(@NonNull Handler handler, 112 FingerprintManager.EnrollmentCallback enrollmentCallback, 113 @NonNull Clock clock, int helpMinimumDisplayTime, int progressMinimumDisplayTime, 114 boolean progressPriorityOverHelp, boolean prioritizeAcquireMessages, 115 int collectTime) { 116 mClock = clock; 117 mWaitingForMessage = false; 118 mHelpMessageList = new ArrayDeque<>(); 119 mProgressMessageList = new ArrayDeque<>(); 120 mHandler = handler; 121 mEnrollmentCallback = enrollmentCallback; 122 123 mHelpMinimumDisplayTime = helpMinimumDisplayTime; 124 mProgressMinimumDisplayTime = progressMinimumDisplayTime; 125 mProgressPriorityOverHelp = progressPriorityOverHelp; 126 mPrioritizeAcquireMessages = prioritizeAcquireMessages; 127 mCollectTime = collectTime; 128 129 mDisplayMessageRunnable = () -> { 130 long timeStamp = mClock.millis(); 131 Message messageToDisplay = getMessageToDisplay(timeStamp); 132 133 if (messageToDisplay != null) { 134 messageToDisplay.display(); 135 } else { 136 mWaitingForMessage = true; 137 } 138 }; 139 140 mHandler.postDelayed(mDisplayMessageRunnable, 0); 141 } 142 143 /** 144 * Adds help message to the queue to be processed later. 145 * 146 * @param helpMsgId message Id associated with the help message 147 * @param helpString string associated with the help message 148 */ 149 @Override onEnrollmentHelp(int helpMsgId, CharSequence helpString)150 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { 151 mHelpMessageList.add(new HelpMessage(helpMsgId, helpString)); 152 153 if (mWaitingForMessage) { 154 mWaitingForMessage = false; 155 mHandler.postDelayed(mDisplayMessageRunnable, mCollectTime); 156 } 157 } 158 159 /** 160 * Adds progress change message to the queue to be processed later. 161 * 162 * @param remaining remaining number of steps to complete enrollment 163 */ 164 @Override onEnrollmentProgress(int remaining)165 public void onEnrollmentProgress(int remaining) { 166 mProgressMessageList.add(new ProgressMessage(remaining)); 167 168 if (mWaitingForMessage) { 169 mWaitingForMessage = false; 170 mHandler.postDelayed(mDisplayMessageRunnable, mCollectTime); 171 } 172 } 173 174 @Override onEnrollmentError(int errMsgId, CharSequence errString)175 public void onEnrollmentError(int errMsgId, CharSequence errString) { 176 mEnrollmentCallback.onEnrollmentError(errMsgId, errString); 177 } 178 getMessageToDisplay(long timeStamp)179 private Message getMessageToDisplay(long timeStamp) { 180 ProgressMessage progressMessageToDisplay = getProgressMessageToDisplay(timeStamp); 181 if (mMustDisplayProgress) { 182 mMustDisplayProgress = false; 183 if (progressMessageToDisplay != null) { 184 return progressMessageToDisplay; 185 } 186 if (mLastProgressMessageDisplayed != null) { 187 return mLastProgressMessageDisplayed; 188 } 189 } 190 191 Message helpMessageToDisplay = getHelpMessageToDisplay(timeStamp); 192 if (helpMessageToDisplay != null || progressMessageToDisplay != null) { 193 if (mProgressPriorityOverHelp && progressMessageToDisplay != null) { 194 return progressMessageToDisplay; 195 } else if (helpMessageToDisplay != null) { 196 if (progressMessageToDisplay != null) { 197 mMustDisplayProgress = true; 198 mLastProgressMessageDisplayed = progressMessageToDisplay; 199 } 200 return helpMessageToDisplay; 201 } else { 202 return progressMessageToDisplay; 203 } 204 } else { 205 return null; 206 } 207 } 208 getProgressMessageToDisplay(long timeStamp)209 private ProgressMessage getProgressMessageToDisplay(long timeStamp) { 210 ProgressMessage finalProgressMessage = null; 211 while (mProgressMessageList != null && !mProgressMessageList.isEmpty()) { 212 Message message = mProgressMessageList.peekFirst(); 213 if (message.mTimeStamp <= timeStamp) { 214 ProgressMessage progressMessage = mProgressMessageList.pollFirst(); 215 if (mLastProgressMessageDisplayed != null 216 && mLastProgressMessageDisplayed.mRemaining == progressMessage.mRemaining) { 217 continue; 218 } 219 finalProgressMessage = progressMessage; 220 } else { 221 break; 222 } 223 } 224 225 return finalProgressMessage; 226 } 227 getHelpMessageToDisplay(long timeStamp)228 private HelpMessage getHelpMessageToDisplay(long timeStamp) { 229 HashMap<CharSequence, Integer> messageCount = new HashMap<>(); 230 HelpMessage finalHelpMessage = null; 231 232 while (mHelpMessageList != null && !mHelpMessageList.isEmpty()) { 233 Message message = mHelpMessageList.peekFirst(); 234 if (message.mTimeStamp <= timeStamp) { 235 finalHelpMessage = mHelpMessageList.pollFirst(); 236 CharSequence errString = finalHelpMessage.mHelpString; 237 messageCount.put(errString, messageCount.getOrDefault(errString, 0) + 1); 238 } else { 239 break; 240 } 241 } 242 if (mPrioritizeAcquireMessages) { 243 finalHelpMessage = prioritizeHelpMessageByCount(messageCount); 244 } 245 246 return finalHelpMessage; 247 } 248 prioritizeHelpMessageByCount(HashMap<CharSequence, Integer> messageCount)249 private HelpMessage prioritizeHelpMessageByCount(HashMap<CharSequence, Integer> messageCount) { 250 int maxCount = 0; 251 CharSequence maxCountMessage = null; 252 253 for (CharSequence key : 254 messageCount.keySet()) { 255 if (maxCount < messageCount.get(key)) { 256 maxCountMessage = key; 257 maxCount = messageCount.get(key); 258 } 259 } 260 261 return maxCountMessage != null ? new HelpMessage(0 /* errMsgId */, 262 maxCountMessage) : null; 263 } 264 } 265