1 /*
2  * Copyright (C) 2020 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.keyguard;
18 
19 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
20 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
21 import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
22 
23 import android.content.res.ColorStateList;
24 import android.os.AsyncTask;
25 import android.os.CountDownTimer;
26 import android.os.SystemClock;
27 import android.util.PluralsMessageFormatter;
28 import android.view.KeyEvent;
29 
30 import com.android.internal.util.LatencyTracker;
31 import com.android.internal.widget.LockPatternChecker;
32 import com.android.internal.widget.LockPatternUtils;
33 import com.android.internal.widget.LockscreenCredential;
34 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
35 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
36 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
37 import com.android.systemui.R;
38 import com.android.systemui.classifier.FalsingClassifier;
39 import com.android.systemui.classifier.FalsingCollector;
40 import com.android.systemui.flags.FeatureFlags;
41 
42 import java.util.HashMap;
43 import java.util.Map;
44 
45 public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView>
46         extends KeyguardInputViewController<T> {
47     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
48     protected final LockPatternUtils mLockPatternUtils;
49     private final LatencyTracker mLatencyTracker;
50     private final FalsingCollector mFalsingCollector;
51     private final EmergencyButtonController mEmergencyButtonController;
52     private CountDownTimer mCountdownTimer;
53     private boolean mDismissing;
54     protected AsyncTask<?, ?, ?> mPendingLockCheck;
55     protected boolean mResumed;
56     protected boolean mLockedOut;
57 
58     private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> {
59         // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
60         // We don't want to consider it valid user input because the UI
61         // will already respond to the event.
62         if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
63             onUserInput();
64         }
65         return false;
66     };
67 
68     private final EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
69         @Override
70         public void onEmergencyButtonClickedWhenInCall() {
71             getKeyguardSecurityCallback().reset();
72         }
73     };
74 
KeyguardAbsKeyInputViewController(T view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags)75     protected KeyguardAbsKeyInputViewController(T view,
76             KeyguardUpdateMonitor keyguardUpdateMonitor,
77             SecurityMode securityMode,
78             LockPatternUtils lockPatternUtils,
79             KeyguardSecurityCallback keyguardSecurityCallback,
80             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
81             LatencyTracker latencyTracker, FalsingCollector falsingCollector,
82             EmergencyButtonController emergencyButtonController,
83             FeatureFlags featureFlags) {
84         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
85                 messageAreaControllerFactory, featureFlags);
86         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
87         mLockPatternUtils = lockPatternUtils;
88         mLatencyTracker = latencyTracker;
89         mFalsingCollector = falsingCollector;
90         mEmergencyButtonController = emergencyButtonController;
91     }
92 
resetState()93     abstract void resetState();
94 
95     @Override
onInit()96     public void onInit() {
97         super.onInit();
98     }
99 
100     @Override
onViewAttached()101     protected void onViewAttached() {
102         super.onViewAttached();
103         mView.setKeyDownListener(mKeyDownListener);
104         mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
105         // if the user is currently locked out, enforce it.
106         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
107                 KeyguardUpdateMonitor.getCurrentUser());
108         if (shouldLockout(deadline)) {
109             handleAttemptLockout(deadline);
110         }
111     }
112 
113     @Override
reset()114     public void reset() {
115         super.reset();
116         // start fresh
117         mDismissing = false;
118         mView.resetPasswordText(false /* animate */, false /* announce */);
119         resetState();
120     }
121 
122     @Override
needsInput()123     public boolean needsInput() {
124         return false;
125     }
126 
127     @Override
showMessage(CharSequence message, ColorStateList colorState, boolean animated)128     public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) {
129         if (mMessageAreaController == null) {
130             return;
131         }
132 
133         if (colorState != null) {
134             mMessageAreaController.setNextMessageColor(colorState);
135         }
136         mMessageAreaController.setMessage(message, animated);
137     }
138 
139     // Allow subclasses to override this behavior
shouldLockout(long deadline)140     protected boolean shouldLockout(long deadline) {
141         return deadline != 0;
142     }
143 
144     // Prevent user from using the PIN/Password entry until scheduled deadline.
handleAttemptLockout(long elapsedRealtimeDeadline)145     protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
146         mView.setPasswordEntryEnabled(false);
147         mView.setPasswordEntryInputEnabled(false);
148         mLockedOut = true;
149         long elapsedRealtime = SystemClock.elapsedRealtime();
150         long secondsInFuture = (long) Math.ceil(
151                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
152         getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture);
153         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
154 
155             @Override
156             public void onTick(long millisUntilFinished) {
157                 int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
158                 Map<String, Object> arguments = new HashMap<>();
159                 arguments.put("count", secondsRemaining);
160                 mMessageAreaController.setMessage(
161                         PluralsMessageFormatter.format(
162                             mView.getResources(),
163                             arguments,
164                             R.string.kg_too_many_failed_attempts_countdown),
165                         /* animate= */ false);
166             }
167 
168             @Override
169             public void onFinish() {
170                 mMessageAreaController.setMessage("");
171                 mLockedOut = false;
172                 resetState();
173             }
174         }.start();
175     }
176 
onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword)177     void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
178         boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
179         if (matched) {
180             getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
181             if (dismissKeyguard) {
182                 mDismissing = true;
183                 mLatencyTracker.onActionStart(LatencyTracker.ACTION_LOCKSCREEN_UNLOCK);
184                 getKeyguardSecurityCallback().dismiss(true, userId, getSecurityMode());
185             }
186         } else {
187             mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
188             if (isValidPassword) {
189                 getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
190                 if (timeoutMs > 0) {
191                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
192                             userId, timeoutMs);
193                     handleAttemptLockout(deadline);
194                 }
195             }
196             if (timeoutMs == 0) {
197                 mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
198             }
199             startErrorAnimation();
200         }
201     }
202 
startErrorAnimation()203     protected void startErrorAnimation() { /* no-op */ }
204 
verifyPasswordAndUnlock()205     protected void verifyPasswordAndUnlock() {
206         if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
207         if (mLockedOut) return;
208 
209         final LockscreenCredential password = mView.getEnteredCredential();
210         mView.setPasswordEntryInputEnabled(false);
211         if (mPendingLockCheck != null) {
212             mPendingLockCheck.cancel(false);
213         }
214 
215         final int userId = KeyguardUpdateMonitor.getCurrentUser();
216         if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
217             // to avoid accidental lockout, only count attempts that are long enough to be a
218             // real password. This may require some tweaking.
219             mView.setPasswordEntryInputEnabled(true);
220             onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
221             password.zeroize();
222             return;
223         }
224 
225         mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
226         mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
227 
228         mKeyguardUpdateMonitor.setCredentialAttempted();
229         mPendingLockCheck = LockPatternChecker.checkCredential(
230                 mLockPatternUtils,
231                 password,
232                 userId,
233                 new LockPatternChecker.OnCheckCallback() {
234 
235                     @Override
236                     public void onEarlyMatched() {
237                         mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
238 
239                         onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
240                                 true /* isValidPassword */);
241                         password.zeroize();
242                     }
243 
244                     @Override
245                     public void onChecked(boolean matched, int timeoutMs) {
246                         mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
247                         mView.setPasswordEntryInputEnabled(true);
248                         mPendingLockCheck = null;
249                         if (!matched) {
250                             onPasswordChecked(userId, false /* matched */, timeoutMs,
251                                     true /* isValidPassword */);
252                         }
253                         password.zeroize();
254                     }
255 
256                     @Override
257                     public void onCancelled() {
258                         // We already got dismissed with the early matched callback, so we cancelled
259                         // the check. However, we still need to note down the latency.
260                         mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
261                         password.zeroize();
262                     }
263                 });
264     }
265 
266     @Override
showPromptReason(int reason)267     public void showPromptReason(int reason) {
268         if (reason != PROMPT_REASON_NONE) {
269             int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
270             if (promtReasonStringRes != 0) {
271                 mMessageAreaController.setMessage(
272                         mView.getResources().getString(promtReasonStringRes), false);
273             }
274         }
275     }
276 
onUserInput()277     protected void onUserInput() {
278         mFalsingCollector.updateFalseConfidence(FalsingClassifier.Result.passed(0.6));
279         getKeyguardSecurityCallback().userActivity();
280         getKeyguardSecurityCallback().onUserInput();
281         mMessageAreaController.setMessage("");
282     }
283 
284     @Override
onResume(int reason)285     public void onResume(int reason) {
286         mResumed = true;
287     }
288 
289     @Override
onPause()290     public void onPause() {
291         mResumed = false;
292 
293         if (mCountdownTimer != null) {
294             mCountdownTimer.cancel();
295             mCountdownTimer = null;
296         }
297         if (mPendingLockCheck != null) {
298             mPendingLockCheck.cancel(false);
299             mPendingLockCheck = null;
300         }
301         reset();
302     }
303 }
304