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 22 import android.content.res.ColorStateList; 23 import android.os.AsyncTask; 24 import android.os.CountDownTimer; 25 import android.os.SystemClock; 26 import android.util.PluralsMessageFormatter; 27 import android.view.MotionEvent; 28 import android.view.View; 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.LockPatternView; 34 import com.android.internal.widget.LockPatternView.Cell; 35 import com.android.internal.widget.LockscreenCredential; 36 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; 37 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 38 import com.android.systemui.R; 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.statusbar.policy.DevicePostureController; 43 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 48 public class KeyguardPatternViewController 49 extends KeyguardInputViewController<KeyguardPatternView> { 50 51 // how many cells the user has to cross before we poke the wakelock 52 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; 53 54 // how long before we clear the wrong pattern 55 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; 56 57 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 58 private final LockPatternUtils mLockPatternUtils; 59 private final LatencyTracker mLatencyTracker; 60 private final FalsingCollector mFalsingCollector; 61 private final EmergencyButtonController mEmergencyButtonController; 62 private final DevicePostureController mPostureController; 63 private final DevicePostureController.Callback mPostureCallback = 64 posture -> mView.onDevicePostureChanged(posture); 65 private LockPatternView mLockPatternView; 66 private CountDownTimer mCountdownTimer; 67 private AsyncTask<?, ?, ?> mPendingLockCheck; 68 69 private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { 70 @Override 71 public void onEmergencyButtonClickedWhenInCall() { 72 getKeyguardSecurityCallback().reset(); 73 } 74 }; 75 76 /** 77 * Useful for clearing out the wrong pattern after a delay 78 */ 79 private Runnable mCancelPatternRunnable = new Runnable() { 80 @Override 81 public void run() { 82 mLockPatternView.clearPattern(); 83 } 84 }; 85 86 private class UnlockPatternListener implements LockPatternView.OnPatternListener { 87 88 @Override onPatternStart()89 public void onPatternStart() { 90 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 91 mMessageAreaController.setMessage(""); 92 } 93 94 @Override onPatternCleared()95 public void onPatternCleared() { 96 } 97 98 @Override onPatternCellAdded(List<Cell> pattern)99 public void onPatternCellAdded(List<Cell> pattern) { 100 getKeyguardSecurityCallback().userActivity(); 101 getKeyguardSecurityCallback().onUserInput(); 102 } 103 104 @Override onPatternDetected(final List<LockPatternView.Cell> pattern)105 public void onPatternDetected(final List<LockPatternView.Cell> pattern) { 106 mKeyguardUpdateMonitor.setCredentialAttempted(); 107 mLockPatternView.disableInput(); 108 if (mPendingLockCheck != null) { 109 mPendingLockCheck.cancel(false); 110 } 111 112 final int userId = KeyguardUpdateMonitor.getCurrentUser(); 113 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 114 // Treat single-sized patterns as erroneous taps. 115 if (pattern.size() == 1) { 116 mFalsingCollector.updateFalseConfidence(FalsingClassifier.Result.falsed( 117 0.7, getClass().getSimpleName(), "empty pattern input")); 118 } 119 mLockPatternView.enableInput(); 120 onPatternChecked(userId, false, 0, false /* not valid - too short */); 121 return; 122 } 123 124 mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL); 125 mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); 126 mPendingLockCheck = LockPatternChecker.checkCredential( 127 mLockPatternUtils, 128 LockscreenCredential.createPattern(pattern), 129 userId, 130 new LockPatternChecker.OnCheckCallback() { 131 132 @Override 133 public void onEarlyMatched() { 134 mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL); 135 onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, 136 true /* isValidPattern */); 137 } 138 139 @Override 140 public void onChecked(boolean matched, int timeoutMs) { 141 mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); 142 mLockPatternView.enableInput(); 143 mPendingLockCheck = null; 144 if (!matched) { 145 onPatternChecked(userId, false /* matched */, timeoutMs, 146 true /* isValidPattern */); 147 } 148 } 149 150 @Override 151 public void onCancelled() { 152 // We already got dismissed with the early matched callback, so we 153 // cancelled the check. However, we still need to note down the latency. 154 mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); 155 } 156 }); 157 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 158 getKeyguardSecurityCallback().userActivity(); 159 getKeyguardSecurityCallback().onUserInput(); 160 } 161 } 162 onPatternChecked(int userId, boolean matched, int timeoutMs, boolean isValidPattern)163 private void onPatternChecked(int userId, boolean matched, int timeoutMs, 164 boolean isValidPattern) { 165 boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; 166 if (matched) { 167 getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); 168 if (dismissKeyguard) { 169 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); 170 mLatencyTracker.onActionStart(LatencyTracker.ACTION_LOCKSCREEN_UNLOCK); 171 getKeyguardSecurityCallback().dismiss(true, userId, SecurityMode.Pattern); 172 } 173 } else { 174 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 175 if (isValidPattern) { 176 getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); 177 if (timeoutMs > 0) { 178 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 179 userId, timeoutMs); 180 handleAttemptLockout(deadline); 181 } 182 } 183 if (timeoutMs == 0) { 184 mMessageAreaController.setMessage(R.string.kg_wrong_pattern); 185 mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); 186 } 187 } 188 } 189 } 190 KeyguardPatternViewController(KeyguardPatternView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, LatencyTracker latencyTracker, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, KeyguardMessageAreaController.Factory messageAreaControllerFactory, DevicePostureController postureController, FeatureFlags featureFlags)191 protected KeyguardPatternViewController(KeyguardPatternView view, 192 KeyguardUpdateMonitor keyguardUpdateMonitor, 193 SecurityMode securityMode, 194 LockPatternUtils lockPatternUtils, 195 KeyguardSecurityCallback keyguardSecurityCallback, 196 LatencyTracker latencyTracker, 197 FalsingCollector falsingCollector, 198 EmergencyButtonController emergencyButtonController, 199 KeyguardMessageAreaController.Factory messageAreaControllerFactory, 200 DevicePostureController postureController, FeatureFlags featureFlags) { 201 super(view, securityMode, keyguardSecurityCallback, emergencyButtonController, 202 messageAreaControllerFactory, featureFlags); 203 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 204 mLockPatternUtils = lockPatternUtils; 205 mLatencyTracker = latencyTracker; 206 mFalsingCollector = falsingCollector; 207 mEmergencyButtonController = emergencyButtonController; 208 mLockPatternView = mView.findViewById(R.id.lockPatternView); 209 mPostureController = postureController; 210 } 211 212 @Override onInit()213 public void onInit() { 214 super.onInit(); 215 } 216 217 @Override onViewAttached()218 protected void onViewAttached() { 219 super.onViewAttached(); 220 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 221 mLockPatternView.setSaveEnabled(false); 222 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 223 KeyguardUpdateMonitor.getCurrentUser())); 224 mLockPatternView.setOnTouchListener((v, event) -> { 225 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 226 mFalsingCollector.avoidGesture(); 227 } 228 return false; 229 }); 230 mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback); 231 232 View cancelBtn = mView.findViewById(R.id.cancel_button); 233 if (cancelBtn != null) { 234 cancelBtn.setOnClickListener(view -> { 235 getKeyguardSecurityCallback().reset(); 236 getKeyguardSecurityCallback().onCancelClicked(); 237 }); 238 } 239 mView.onDevicePostureChanged(mPostureController.getDevicePosture()); 240 mPostureController.addCallback(mPostureCallback); 241 // if the user is currently locked out, enforce it. 242 long deadline = mLockPatternUtils.getLockoutAttemptDeadline( 243 KeyguardUpdateMonitor.getCurrentUser()); 244 if (deadline != 0) { 245 handleAttemptLockout(deadline); 246 } 247 } 248 249 @Override onViewDetached()250 protected void onViewDetached() { 251 super.onViewDetached(); 252 mLockPatternView.setOnPatternListener(null); 253 mLockPatternView.setOnTouchListener(null); 254 mEmergencyButtonController.setEmergencyButtonCallback(null); 255 View cancelBtn = mView.findViewById(R.id.cancel_button); 256 if (cancelBtn != null) { 257 cancelBtn.setOnClickListener(null); 258 } 259 mPostureController.removeCallback(mPostureCallback); 260 } 261 262 @Override reset()263 public void reset() { 264 // reset lock pattern 265 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 266 KeyguardUpdateMonitor.getCurrentUser())); 267 mLockPatternView.enableInput(); 268 mLockPatternView.setEnabled(true); 269 mLockPatternView.clearPattern(); 270 271 displayDefaultSecurityMessage(); 272 } 273 274 @Override onResume(int reason)275 public void onResume(int reason) { 276 super.onResume(reason); 277 } 278 279 @Override onPause()280 public void onPause() { 281 super.onPause(); 282 283 if (mCountdownTimer != null) { 284 mCountdownTimer.cancel(); 285 mCountdownTimer = null; 286 } 287 288 if (mPendingLockCheck != null) { 289 mPendingLockCheck.cancel(false); 290 mPendingLockCheck = null; 291 } 292 displayDefaultSecurityMessage(); 293 } 294 295 @Override needsInput()296 public boolean needsInput() { 297 return false; 298 } 299 300 @Override showPromptReason(int reason)301 public void showPromptReason(int reason) { 302 /// TODO: move all this logic into the MessageAreaController? 303 int resId = 0; 304 switch (reason) { 305 case PROMPT_REASON_RESTART: 306 resId = R.string.kg_prompt_reason_restart_pattern; 307 break; 308 case PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE: 309 resId = R.string.kg_prompt_after_update_pattern; 310 break; 311 case PROMPT_REASON_TIMEOUT: 312 resId = R.string.kg_prompt_reason_timeout_pattern; 313 break; 314 case PROMPT_REASON_DEVICE_ADMIN: 315 resId = R.string.kg_prompt_reason_device_admin; 316 break; 317 case PROMPT_REASON_USER_REQUEST: 318 resId = R.string.kg_prompt_after_user_lockdown_pattern; 319 break; 320 case PROMPT_REASON_PREPARE_FOR_UPDATE: 321 resId = R.string.kg_prompt_unattended_update_pattern; 322 break; 323 case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: 324 resId = R.string.kg_prompt_reason_timeout_pattern; 325 break; 326 case PROMPT_REASON_TRUSTAGENT_EXPIRED: 327 resId = R.string.kg_prompt_reason_timeout_pattern; 328 break; 329 case PROMPT_REASON_NONE: 330 break; 331 default: 332 resId = R.string.kg_prompt_reason_timeout_pattern; 333 break; 334 } 335 if (resId != 0) { 336 mMessageAreaController.setMessage(getResources().getText(resId), /* animate= */ false); 337 } 338 } 339 340 @Override showMessage(CharSequence message, ColorStateList colorState, boolean animated)341 public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) { 342 if (mMessageAreaController == null) { 343 return; 344 } 345 if (colorState != null) { 346 mMessageAreaController.setNextMessageColor(colorState); 347 } 348 mMessageAreaController.setMessage(message, animated); 349 } 350 351 @Override startAppearAnimation()352 public void startAppearAnimation() { 353 super.startAppearAnimation(); 354 } 355 356 @Override startDisappearAnimation(Runnable finishRunnable)357 public boolean startDisappearAnimation(Runnable finishRunnable) { 358 return mView.startDisappearAnimation( 359 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); 360 } 361 displayDefaultSecurityMessage()362 private void displayDefaultSecurityMessage() { 363 mMessageAreaController.setMessage(getInitialMessageResId()); 364 } 365 handleAttemptLockout(long elapsedRealtimeDeadline)366 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 367 mLockPatternView.clearPattern(); 368 mLockPatternView.setEnabled(false); 369 final long elapsedRealtime = SystemClock.elapsedRealtime(); 370 final long secondsInFuture = (long) Math.ceil( 371 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); 372 getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture); 373 mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { 374 375 @Override 376 public void onTick(long millisUntilFinished) { 377 final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); 378 Map<String, Object> arguments = new HashMap<>(); 379 arguments.put("count", secondsRemaining); 380 381 mMessageAreaController.setMessage( 382 PluralsMessageFormatter.format( 383 mView.getResources(), 384 arguments, 385 R.string.kg_too_many_failed_attempts_countdown), 386 /* animate= */ false 387 ); 388 } 389 390 @Override 391 public void onFinish() { 392 mLockPatternView.setEnabled(true); 393 displayDefaultSecurityMessage(); 394 } 395 396 }.start(); 397 } 398 399 @Override getInitialMessageResId()400 protected int getInitialMessageResId() { 401 return R.string.keyguard_enter_your_pattern; 402 } 403 } 404