KeyguardPatternView.java revision 51efddbd3bb304de2dd47fa8cd1114ac555958bb
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 */ 16package com.android.keyguard; 17 18import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL; 19import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; 20 21import android.content.Context; 22import android.graphics.Rect; 23import android.os.AsyncTask; 24import android.os.CountDownTimer; 25import android.os.SystemClock; 26import android.text.TextUtils; 27import android.util.AttributeSet; 28import android.util.Log; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.animation.AnimationUtils; 33import android.view.animation.Interpolator; 34import android.widget.LinearLayout; 35 36import com.android.internal.widget.LockPatternChecker; 37import com.android.internal.widget.LockPatternUtils; 38import com.android.internal.widget.LockPatternView; 39import com.android.settingslib.animation.AppearAnimationCreator; 40import com.android.settingslib.animation.AppearAnimationUtils; 41import com.android.settingslib.animation.DisappearAnimationUtils; 42 43import java.util.List; 44 45public 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 104 public KeyguardPatternView(Context context) { 105 this(context, null); 106 } 107 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 128 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 129 mCallback = callback; 130 } 131 132 @Override 133 public void setLockPatternUtils(LockPatternUtils utils) { 134 mLockPatternUtils = utils; 135 } 136 137 @Override 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 162 public void onEmergencyButtonClickedWhenInCall() { 163 mCallback.reset(); 164 } 165 166 @Override 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 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 202 private void displayDefaultSecurityMessage() { 203 mSecurityMessageDisplay.setMessage(""); 204 } 205 206 @Override 207 public void showUsabilityHint() { 208 } 209 210 /** TODO: hook this up */ 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 220 public void onPatternStart() { 221 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 222 mSecurityMessageDisplay.setMessage(""); 223 } 224 225 @Override 226 public void onPatternCleared() { 227 } 228 229 @Override 230 public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { 231 mCallback.userActivity(); 232 } 233 234 @Override 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 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 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 349 public boolean needsInput() { 350 return false; 351 } 352 353 @Override 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 366 public void onResume(int reason) { 367 reset(); 368 } 369 370 @Override 371 public KeyguardSecurityCallback getCallback() { 372 return mCallback; 373 } 374 375 @Override 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 399 public void showMessage(String message, int color) { 400 mSecurityMessageDisplay.setNextMessageColor(color); 401 mSecurityMessageDisplay.setMessage(message); 402 } 403 404 @Override 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 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 465 private void enableClipping(boolean enable) { 466 setClipChildren(enable); 467 mContainer.setClipToPadding(enable); 468 mContainer.setClipChildren(enable); 469 } 470 471 @Override 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 489 public boolean hasOverlappingRendering() { 490 return false; 491 } 492} 493