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