1 /* 2 * Copyright (C) 2012 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 package com.android.keyguard; 17 18 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; 19 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; 20 21 import android.content.Context; 22 import android.content.res.ColorStateList; 23 import android.graphics.Rect; 24 import android.os.AsyncTask; 25 import android.os.CountDownTimer; 26 import android.os.SystemClock; 27 import android.text.TextUtils; 28 import android.util.AttributeSet; 29 import android.util.Log; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.animation.AnimationUtils; 34 import android.view.animation.Interpolator; 35 import android.widget.LinearLayout; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.util.LatencyTracker; 39 import com.android.internal.widget.LockPatternChecker; 40 import com.android.internal.widget.LockPatternUtils; 41 import com.android.internal.widget.LockPatternView; 42 import com.android.internal.widget.LockscreenCredential; 43 import com.android.settingslib.animation.AppearAnimationCreator; 44 import com.android.settingslib.animation.AppearAnimationUtils; 45 import com.android.settingslib.animation.DisappearAnimationUtils; 46 import com.android.systemui.Dependency; 47 import com.android.systemui.R; 48 49 import java.util.List; 50 51 public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, 52 AppearAnimationCreator<LockPatternView.CellState>, 53 EmergencyButton.EmergencyButtonCallback { 54 55 private static final String TAG = "SecurityPatternView"; 56 private static final boolean DEBUG = KeyguardConstants.DEBUG; 57 58 // how long before we clear the wrong pattern 59 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; 60 61 // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK 62 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; 63 64 // how many cells the user has to cross before we poke the wakelock 65 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; 66 67 // How much we scale up the duration of the disappear animation when the current user is locked 68 public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f; 69 70 // Extra padding, in pixels, that should eat touch events. 71 private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40; 72 73 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 74 private final AppearAnimationUtils mAppearAnimationUtils; 75 private final DisappearAnimationUtils mDisappearAnimationUtils; 76 private final DisappearAnimationUtils mDisappearAnimationUtilsLocked; 77 private final int[] mTmpPosition = new int[2]; 78 private final Rect mTempRect = new Rect(); 79 private final Rect mLockPatternScreenBounds = new Rect(); 80 81 private CountDownTimer mCountdownTimer = null; 82 private LockPatternUtils mLockPatternUtils; 83 private AsyncTask<?, ?, ?> mPendingLockCheck; 84 private LockPatternView mLockPatternView; 85 private KeyguardSecurityCallback mCallback; 86 87 /** 88 * Keeps track of the last time we poked the wake lock during dispatching of the touch event. 89 * Initialized to something guaranteed to make us poke the wakelock when the user starts 90 * drawing the pattern. 91 * @see #dispatchTouchEvent(android.view.MotionEvent) 92 */ 93 private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; 94 95 /** 96 * Useful for clearing out the wrong pattern after a delay 97 */ 98 private Runnable mCancelPatternRunnable = new Runnable() { 99 @Override 100 public void run() { 101 mLockPatternView.clearPattern(); 102 } 103 }; 104 @VisibleForTesting 105 KeyguardMessageArea mSecurityMessageDisplay; 106 private View mEcaView; 107 private ViewGroup mContainer; 108 private int mDisappearYTranslation; 109 110 enum FooterMode { 111 Normal, 112 ForgotLockPattern, 113 VerifyUnlocked 114 } 115 KeyguardPatternView(Context context)116 public KeyguardPatternView(Context context) { 117 this(context, null); 118 } 119 KeyguardPatternView(Context context, AttributeSet attrs)120 public KeyguardPatternView(Context context, AttributeSet attrs) { 121 super(context, attrs); 122 mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); 123 mAppearAnimationUtils = new AppearAnimationUtils(context, 124 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, 125 2.0f /* delayScale */, AnimationUtils.loadInterpolator( 126 mContext, android.R.interpolator.linear_out_slow_in)); 127 mDisappearAnimationUtils = new DisappearAnimationUtils(context, 128 125, 1.2f /* translationScale */, 129 0.6f /* delayScale */, AnimationUtils.loadInterpolator( 130 mContext, android.R.interpolator.fast_out_linear_in)); 131 mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context, 132 (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */, 133 0.6f /* delayScale */, AnimationUtils.loadInterpolator( 134 mContext, android.R.interpolator.fast_out_linear_in)); 135 mDisappearYTranslation = getResources().getDimensionPixelSize( 136 R.dimen.disappear_y_translation); 137 } 138 139 @Override setKeyguardCallback(KeyguardSecurityCallback callback)140 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 141 mCallback = callback; 142 } 143 144 @Override setLockPatternUtils(LockPatternUtils utils)145 public void setLockPatternUtils(LockPatternUtils utils) { 146 mLockPatternUtils = utils; 147 } 148 149 @Override onFinishInflate()150 protected void onFinishInflate() { 151 super.onFinishInflate(); 152 mLockPatternUtils = mLockPatternUtils == null 153 ? new LockPatternUtils(mContext) : mLockPatternUtils; 154 155 mLockPatternView = findViewById(R.id.lockPatternView); 156 mLockPatternView.setSaveEnabled(false); 157 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 158 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 159 KeyguardUpdateMonitor.getCurrentUser())); 160 161 // vibrate mode will be the same for the life of this screen 162 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 163 164 mEcaView = findViewById(R.id.keyguard_selector_fade_container); 165 mContainer = findViewById(R.id.container); 166 167 EmergencyButton button = findViewById(R.id.emergency_call_button); 168 if (button != null) { 169 button.setCallback(this); 170 } 171 172 View cancelBtn = findViewById(R.id.cancel_button); 173 if (cancelBtn != null) { 174 cancelBtn.setOnClickListener(view -> { 175 mCallback.reset(); 176 mCallback.onCancelClicked(); 177 }); 178 } 179 } 180 181 @Override onAttachedToWindow()182 protected void onAttachedToWindow() { 183 super.onAttachedToWindow(); 184 mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this); 185 } 186 187 @Override onEmergencyButtonClickedWhenInCall()188 public void onEmergencyButtonClickedWhenInCall() { 189 mCallback.reset(); 190 } 191 192 @Override onTouchEvent(MotionEvent ev)193 public boolean onTouchEvent(MotionEvent ev) { 194 boolean result = super.onTouchEvent(ev); 195 // as long as the user is entering a pattern (i.e sending a touch event that was handled 196 // by this screen), keep poking the wake lock so that the screen will stay on. 197 final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; 198 if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { 199 mLastPokeTime = SystemClock.elapsedRealtime(); 200 } 201 mTempRect.set(0, 0, 0, 0); 202 offsetRectIntoDescendantCoords(mLockPatternView, mTempRect); 203 ev.offsetLocation(mTempRect.left, mTempRect.top); 204 result = mLockPatternView.dispatchTouchEvent(ev) || result; 205 ev.offsetLocation(-mTempRect.left, -mTempRect.top); 206 return result; 207 } 208 209 @Override onLayout(boolean changed, int l, int t, int r, int b)210 protected void onLayout(boolean changed, int l, int t, int r, int b) { 211 super.onLayout(changed, l, t, r, b); 212 mLockPatternView.getLocationOnScreen(mTmpPosition); 213 mLockPatternScreenBounds.set(mTmpPosition[0] - PATTERNS_TOUCH_AREA_EXTENSION, 214 mTmpPosition[1] - PATTERNS_TOUCH_AREA_EXTENSION, 215 mTmpPosition[0] + mLockPatternView.getWidth() + PATTERNS_TOUCH_AREA_EXTENSION, 216 mTmpPosition[1] + mLockPatternView.getHeight() + PATTERNS_TOUCH_AREA_EXTENSION); 217 } 218 219 @Override reset()220 public void reset() { 221 // reset lock pattern 222 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 223 KeyguardUpdateMonitor.getCurrentUser())); 224 mLockPatternView.enableInput(); 225 mLockPatternView.setEnabled(true); 226 mLockPatternView.clearPattern(); 227 228 if (mSecurityMessageDisplay == null) { 229 return; 230 } 231 232 // if the user is currently locked out, enforce it. 233 long deadline = mLockPatternUtils.getLockoutAttemptDeadline( 234 KeyguardUpdateMonitor.getCurrentUser()); 235 if (deadline != 0) { 236 handleAttemptLockout(deadline); 237 } else { 238 displayDefaultSecurityMessage(); 239 } 240 } 241 displayDefaultSecurityMessage()242 private void displayDefaultSecurityMessage() { 243 if (mSecurityMessageDisplay != null) { 244 mSecurityMessageDisplay.setMessage(""); 245 } 246 } 247 248 @Override showUsabilityHint()249 public void showUsabilityHint() { 250 } 251 252 @Override disallowInterceptTouch(MotionEvent event)253 public boolean disallowInterceptTouch(MotionEvent event) { 254 return !mLockPatternView.isEmpty() 255 || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY()); 256 } 257 258 /** TODO: hook this up */ cleanUp()259 public void cleanUp() { 260 if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); 261 mLockPatternUtils = null; 262 mLockPatternView.setOnPatternListener(null); 263 } 264 265 private class UnlockPatternListener implements LockPatternView.OnPatternListener { 266 267 @Override onPatternStart()268 public void onPatternStart() { 269 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 270 mSecurityMessageDisplay.setMessage(""); 271 } 272 273 @Override onPatternCleared()274 public void onPatternCleared() { 275 } 276 277 @Override onPatternCellAdded(List<LockPatternView.Cell> pattern)278 public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { 279 mCallback.userActivity(); 280 mCallback.onUserInput(); 281 } 282 283 @Override onPatternDetected(final List<LockPatternView.Cell> pattern)284 public void onPatternDetected(final List<LockPatternView.Cell> pattern) { 285 mKeyguardUpdateMonitor.setCredentialAttempted(); 286 mLockPatternView.disableInput(); 287 if (mPendingLockCheck != null) { 288 mPendingLockCheck.cancel(false); 289 } 290 291 final int userId = KeyguardUpdateMonitor.getCurrentUser(); 292 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 293 mLockPatternView.enableInput(); 294 onPatternChecked(userId, false, 0, false /* not valid - too short */); 295 return; 296 } 297 298 if (LatencyTracker.isEnabled(mContext)) { 299 LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); 300 LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); 301 } 302 mPendingLockCheck = LockPatternChecker.checkCredential( 303 mLockPatternUtils, 304 LockscreenCredential.createPattern(pattern), 305 userId, 306 new LockPatternChecker.OnCheckCallback() { 307 308 @Override 309 public void onEarlyMatched() { 310 if (LatencyTracker.isEnabled(mContext)) { 311 LatencyTracker.getInstance(mContext).onActionEnd( 312 ACTION_CHECK_CREDENTIAL); 313 } 314 onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, 315 true /* isValidPattern */); 316 } 317 318 @Override 319 public void onChecked(boolean matched, int timeoutMs) { 320 if (LatencyTracker.isEnabled(mContext)) { 321 LatencyTracker.getInstance(mContext).onActionEnd( 322 ACTION_CHECK_CREDENTIAL_UNLOCKED); 323 } 324 mLockPatternView.enableInput(); 325 mPendingLockCheck = null; 326 if (!matched) { 327 onPatternChecked(userId, false /* matched */, timeoutMs, 328 true /* isValidPattern */); 329 } 330 } 331 332 @Override 333 public void onCancelled() { 334 // We already got dismissed with the early matched callback, so we 335 // cancelled the check. However, we still need to note down the latency. 336 if (LatencyTracker.isEnabled(mContext)) { 337 LatencyTracker.getInstance(mContext).onActionEnd( 338 ACTION_CHECK_CREDENTIAL_UNLOCKED); 339 } 340 } 341 }); 342 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 343 mCallback.userActivity(); 344 mCallback.onUserInput(); 345 } 346 } 347 onPatternChecked(int userId, boolean matched, int timeoutMs, boolean isValidPattern)348 private void onPatternChecked(int userId, boolean matched, int timeoutMs, 349 boolean isValidPattern) { 350 boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; 351 if (matched) { 352 mCallback.reportUnlockAttempt(userId, true, 0); 353 if (dismissKeyguard) { 354 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); 355 mCallback.dismiss(true, userId); 356 } 357 } else { 358 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 359 if (isValidPattern) { 360 mCallback.reportUnlockAttempt(userId, false, timeoutMs); 361 if (timeoutMs > 0) { 362 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 363 userId, timeoutMs); 364 handleAttemptLockout(deadline); 365 } 366 } 367 if (timeoutMs == 0) { 368 mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern); 369 mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); 370 } 371 } 372 } 373 } 374 handleAttemptLockout(long elapsedRealtimeDeadline)375 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 376 mLockPatternView.clearPattern(); 377 mLockPatternView.setEnabled(false); 378 final long elapsedRealtime = SystemClock.elapsedRealtime(); 379 final long secondsInFuture = (long) Math.ceil( 380 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); 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 mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( 387 R.plurals.kg_too_many_failed_attempts_countdown, 388 secondsRemaining, secondsRemaining)); 389 } 390 391 @Override 392 public void onFinish() { 393 mLockPatternView.setEnabled(true); 394 displayDefaultSecurityMessage(); 395 } 396 397 }.start(); 398 } 399 400 @Override needsInput()401 public boolean needsInput() { 402 return false; 403 } 404 405 @Override onPause()406 public void onPause() { 407 if (mCountdownTimer != null) { 408 mCountdownTimer.cancel(); 409 mCountdownTimer = null; 410 } 411 if (mPendingLockCheck != null) { 412 mPendingLockCheck.cancel(false); 413 mPendingLockCheck = null; 414 } 415 displayDefaultSecurityMessage(); 416 } 417 418 @Override onResume(int reason)419 public void onResume(int reason) { 420 } 421 422 @Override getCallback()423 public KeyguardSecurityCallback getCallback() { 424 return mCallback; 425 } 426 427 @Override showPromptReason(int reason)428 public void showPromptReason(int reason) { 429 switch (reason) { 430 case PROMPT_REASON_RESTART: 431 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern); 432 break; 433 case PROMPT_REASON_TIMEOUT: 434 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); 435 break; 436 case PROMPT_REASON_DEVICE_ADMIN: 437 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin); 438 break; 439 case PROMPT_REASON_USER_REQUEST: 440 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request); 441 break; 442 case PROMPT_REASON_PREPARE_FOR_UPDATE: 443 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); 444 break; 445 case PROMPT_REASON_NONE: 446 break; 447 default: 448 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); 449 break; 450 } 451 } 452 453 @Override showMessage(CharSequence message, ColorStateList colorState)454 public void showMessage(CharSequence message, ColorStateList colorState) { 455 if (colorState != null) { 456 mSecurityMessageDisplay.setNextMessageColor(colorState); 457 } 458 mSecurityMessageDisplay.setMessage(message); 459 } 460 461 @Override startAppearAnimation()462 public void startAppearAnimation() { 463 enableClipping(false); 464 setAlpha(1f); 465 setTranslationY(mAppearAnimationUtils.getStartTranslation()); 466 AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, 467 0, mAppearAnimationUtils.getInterpolator()); 468 mAppearAnimationUtils.startAnimation2d( 469 mLockPatternView.getCellStates(), 470 new Runnable() { 471 @Override 472 public void run() { 473 enableClipping(true); 474 } 475 }, 476 this); 477 if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { 478 mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, 479 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 480 mAppearAnimationUtils.getStartTranslation(), 481 true /* appearing */, 482 mAppearAnimationUtils.getInterpolator(), 483 null /* finishRunnable */); 484 } 485 } 486 487 @Override startDisappearAnimation(final Runnable finishRunnable)488 public boolean startDisappearAnimation(final Runnable finishRunnable) { 489 float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition() 490 ? DISAPPEAR_MULTIPLIER_LOCKED 491 : 1f; 492 mLockPatternView.clearPattern(); 493 enableClipping(false); 494 setTranslationY(0); 495 AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 496 (long) (300 * durationMultiplier), 497 -mDisappearAnimationUtils.getStartTranslation(), 498 mDisappearAnimationUtils.getInterpolator()); 499 500 DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor 501 .needsSlowUnlockTransition() 502 ? mDisappearAnimationUtilsLocked 503 : mDisappearAnimationUtils; 504 disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), 505 () -> { 506 enableClipping(true); 507 if (finishRunnable != null) { 508 finishRunnable.run(); 509 } 510 }, KeyguardPatternView.this); 511 if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { 512 mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, 513 (long) (200 * durationMultiplier), 514 - mDisappearAnimationUtils.getStartTranslation() * 3, 515 false /* appearing */, 516 mDisappearAnimationUtils.getInterpolator(), 517 null /* finishRunnable */); 518 } 519 return true; 520 } 521 enableClipping(boolean enable)522 private void enableClipping(boolean enable) { 523 setClipChildren(enable); 524 mContainer.setClipToPadding(enable); 525 mContainer.setClipChildren(enable); 526 } 527 528 @Override createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)529 public void createAnimation(final LockPatternView.CellState animatedCell, long delay, 530 long duration, float translationY, final boolean appearing, 531 Interpolator interpolator, 532 final Runnable finishListener) { 533 mLockPatternView.startCellStateAnimation(animatedCell, 534 1f, appearing ? 1f : 0f, /* alpha */ 535 appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */ 536 appearing ? 0f : 1f, 1f /* scale */, 537 delay, duration, interpolator, finishListener); 538 if (finishListener != null) { 539 // Also animate the Emergency call 540 mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY, 541 appearing, interpolator, null); 542 } 543 } 544 545 @Override hasOverlappingRendering()546 public boolean hasOverlappingRendering() { 547 return false; 548 } 549 550 @Override getTitle()551 public CharSequence getTitle() { 552 return getContext().getString( 553 com.android.internal.R.string.keyguard_accessibility_pattern_unlock); 554 } 555 } 556