• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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