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.view.MotionEvent; 27 import android.view.View; 28 29 import com.android.internal.util.LatencyTracker; 30 import com.android.internal.widget.LockPatternChecker; 31 import com.android.internal.widget.LockPatternUtils; 32 import com.android.internal.widget.LockPatternView; 33 import com.android.internal.widget.LockPatternView.Cell; 34 import com.android.internal.widget.LockscreenCredential; 35 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; 36 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 37 import com.android.settingslib.Utils; 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.statusbar.policy.DevicePostureController; 42 43 import java.util.List; 44 45 public class KeyguardPatternViewController 46 extends KeyguardInputViewController<KeyguardPatternView> { 47 48 // how many cells the user has to cross before we poke the wakelock 49 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; 50 51 // how long before we clear the wrong pattern 52 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; 53 54 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 55 private final LockPatternUtils mLockPatternUtils; 56 private final LatencyTracker mLatencyTracker; 57 private final FalsingCollector mFalsingCollector; 58 private final EmergencyButtonController mEmergencyButtonController; 59 private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; 60 private final DevicePostureController mPostureController; 61 private final DevicePostureController.Callback mPostureCallback = 62 posture -> mView.onDevicePostureChanged(posture); 63 64 private KeyguardMessageAreaController mMessageAreaController; 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); 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)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) { 201 super(view, securityMode, keyguardSecurityCallback, emergencyButtonController); 202 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 203 mLockPatternUtils = lockPatternUtils; 204 mLatencyTracker = latencyTracker; 205 mFalsingCollector = falsingCollector; 206 mEmergencyButtonController = emergencyButtonController; 207 mMessageAreaControllerFactory = messageAreaControllerFactory; 208 KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); 209 mMessageAreaController = mMessageAreaControllerFactory.create(kma); 210 mLockPatternView = mView.findViewById(R.id.lockPatternView); 211 mPostureController = postureController; 212 } 213 214 @Override onInit()215 public void onInit() { 216 super.onInit(); 217 mMessageAreaController.init(); 218 } 219 220 @Override onViewAttached()221 protected void onViewAttached() { 222 super.onViewAttached(); 223 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 224 mLockPatternView.setSaveEnabled(false); 225 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 226 KeyguardUpdateMonitor.getCurrentUser())); 227 // vibrate mode will be the same for the life of this screen 228 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 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 mPostureController.addCallback(mPostureCallback); 245 } 246 247 @Override onViewDetached()248 protected void onViewDetached() { 249 super.onViewDetached(); 250 mLockPatternView.setOnPatternListener(null); 251 mLockPatternView.setOnTouchListener(null); 252 mEmergencyButtonController.setEmergencyButtonCallback(null); 253 View cancelBtn = mView.findViewById(R.id.cancel_button); 254 if (cancelBtn != null) { 255 cancelBtn.setOnClickListener(null); 256 } 257 mPostureController.removeCallback(mPostureCallback); 258 } 259 260 @Override reset()261 public void reset() { 262 // reset lock pattern 263 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 264 KeyguardUpdateMonitor.getCurrentUser())); 265 mLockPatternView.enableInput(); 266 mLockPatternView.setEnabled(true); 267 mLockPatternView.clearPattern(); 268 269 // if the user is currently locked out, enforce it. 270 long deadline = mLockPatternUtils.getLockoutAttemptDeadline( 271 KeyguardUpdateMonitor.getCurrentUser()); 272 if (deadline != 0) { 273 handleAttemptLockout(deadline); 274 } else { 275 displayDefaultSecurityMessage(); 276 } 277 } 278 279 @Override reloadColors()280 public void reloadColors() { 281 super.reloadColors(); 282 mMessageAreaController.reloadColors(); 283 int textColor = Utils.getColorAttr(mLockPatternView.getContext(), 284 android.R.attr.textColorPrimary).getDefaultColor(); 285 int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor(); 286 mLockPatternView.setColors(textColor, textColor, errorColor); 287 } 288 289 @Override onPause()290 public void onPause() { 291 super.onPause(); 292 293 if (mCountdownTimer != null) { 294 mCountdownTimer.cancel(); 295 mCountdownTimer = null; 296 } 297 298 if (mPendingLockCheck != null) { 299 mPendingLockCheck.cancel(false); 300 mPendingLockCheck = null; 301 } 302 displayDefaultSecurityMessage(); 303 } 304 305 @Override needsInput()306 public boolean needsInput() { 307 return false; 308 } 309 310 @Override showPromptReason(int reason)311 public void showPromptReason(int reason) { 312 /// TODO: move all this logic into the MessageAreaController? 313 switch (reason) { 314 case PROMPT_REASON_RESTART: 315 mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern); 316 break; 317 case PROMPT_REASON_TIMEOUT: 318 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); 319 break; 320 case PROMPT_REASON_DEVICE_ADMIN: 321 mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin); 322 break; 323 case PROMPT_REASON_USER_REQUEST: 324 mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request); 325 break; 326 case PROMPT_REASON_PREPARE_FOR_UPDATE: 327 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); 328 break; 329 case PROMPT_REASON_NONE: 330 break; 331 default: 332 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); 333 break; 334 } 335 } 336 337 @Override showMessage(CharSequence message, ColorStateList colorState)338 public void showMessage(CharSequence message, ColorStateList colorState) { 339 if (colorState != null) { 340 mMessageAreaController.setNextMessageColor(colorState); 341 } 342 mMessageAreaController.setMessage(message); 343 } 344 345 @Override startAppearAnimation()346 public void startAppearAnimation() { 347 super.startAppearAnimation(); 348 } 349 350 @Override startDisappearAnimation(Runnable finishRunnable)351 public boolean startDisappearAnimation(Runnable finishRunnable) { 352 return mView.startDisappearAnimation( 353 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); 354 } 355 displayDefaultSecurityMessage()356 private void displayDefaultSecurityMessage() { 357 mMessageAreaController.setMessage(""); 358 } 359 handleAttemptLockout(long elapsedRealtimeDeadline)360 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 361 mLockPatternView.clearPattern(); 362 mLockPatternView.setEnabled(false); 363 final long elapsedRealtime = SystemClock.elapsedRealtime(); 364 final long secondsInFuture = (long) Math.ceil( 365 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); 366 mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { 367 368 @Override 369 public void onTick(long millisUntilFinished) { 370 final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); 371 mMessageAreaController.setMessage(mView.getResources().getQuantityString( 372 R.plurals.kg_too_many_failed_attempts_countdown, 373 secondsRemaining, secondsRemaining)); 374 } 375 376 @Override 377 public void onFinish() { 378 mLockPatternView.setEnabled(true); 379 displayDefaultSecurityMessage(); 380 } 381 382 }.start(); 383 } 384 } 385