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