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