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