KeyguardPatternView.java revision 09eb0337b760c74d73a614edfdc7eaa6e083a29f
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 android.animation.Animator; 19import android.animation.AnimatorListenerAdapter; 20import android.animation.ValueAnimator; 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; 39 40import java.util.List; 41 42public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, 43 AppearAnimationCreator<LockPatternView.CellState>, 44 EmergencyButton.EmergencyButtonCallback { 45 46 private static final String TAG = "SecurityPatternView"; 47 private static final boolean DEBUG = KeyguardConstants.DEBUG; 48 49 // how long before we clear the wrong pattern 50 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; 51 52 // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK 53 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; 54 55 // how many cells the user has to cross before we poke the wakelock 56 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; 57 58 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 59 private final AppearAnimationUtils mAppearAnimationUtils; 60 private final DisappearAnimationUtils mDisappearAnimationUtils; 61 62 private CountDownTimer mCountdownTimer = null; 63 private LockPatternUtils mLockPatternUtils; 64 private AsyncTask<?, ?, ?> mPendingLockCheck; 65 private LockPatternView mLockPatternView; 66 private KeyguardSecurityCallback mCallback; 67 68 /** 69 * Keeps track of the last time we poked the wake lock during dispatching of the touch event. 70 * Initialized to something guaranteed to make us poke the wakelock when the user starts 71 * drawing the pattern. 72 * @see #dispatchTouchEvent(android.view.MotionEvent) 73 */ 74 private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; 75 76 /** 77 * Useful for clearing out the wrong pattern after a delay 78 */ 79 private Runnable mCancelPatternRunnable = new Runnable() { 80 public void run() { 81 mLockPatternView.clearPattern(); 82 } 83 }; 84 private Rect mTempRect = new Rect(); 85 private SecurityMessageDisplay mSecurityMessageDisplay; 86 private View mEcaView; 87 private ViewGroup mContainer; 88 private KeyguardMessageArea mHelpMessage; 89 private int mDisappearYTranslation; 90 91 enum FooterMode { 92 Normal, 93 ForgotLockPattern, 94 VerifyUnlocked 95 } 96 97 public KeyguardPatternView(Context context) { 98 this(context, null); 99 } 100 101 public KeyguardPatternView(Context context, AttributeSet attrs) { 102 super(context, attrs); 103 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 104 mAppearAnimationUtils = new AppearAnimationUtils(context, 105 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, 106 2.0f /* delayScale */, AnimationUtils.loadInterpolator( 107 mContext, android.R.interpolator.linear_out_slow_in)); 108 mDisappearAnimationUtils = new DisappearAnimationUtils(context, 109 125, 1.2f /* translationScale */, 110 0.8f /* delayScale */, AnimationUtils.loadInterpolator( 111 mContext, android.R.interpolator.fast_out_linear_in)); 112 mDisappearYTranslation = getResources().getDimensionPixelSize( 113 R.dimen.disappear_y_translation); 114 } 115 116 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 117 mCallback = callback; 118 } 119 120 public void setLockPatternUtils(LockPatternUtils utils) { 121 mLockPatternUtils = utils; 122 } 123 124 @Override 125 protected void onFinishInflate() { 126 super.onFinishInflate(); 127 mLockPatternUtils = mLockPatternUtils == null 128 ? new LockPatternUtils(mContext) : mLockPatternUtils; 129 130 mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView); 131 mLockPatternView.setSaveEnabled(false); 132 mLockPatternView.setFocusable(false); 133 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 134 135 // stealth mode will be the same for the life of this screen 136 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 137 KeyguardUpdateMonitor.getCurrentUser())); 138 139 // vibrate mode will be the same for the life of this screen 140 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 141 142 setFocusableInTouchMode(true); 143 144 mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this); 145 mEcaView = findViewById(R.id.keyguard_selector_fade_container); 146 mContainer = (ViewGroup) findViewById(R.id.container); 147 mHelpMessage = (KeyguardMessageArea) findViewById(R.id.keyguard_message_area); 148 149 EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button); 150 if (button != null) { 151 button.setCallback(this); 152 } 153 } 154 155 public void onEmergencyButtonClickedWhenInCall() { 156 mCallback.reset(); 157 } 158 159 @Override 160 public boolean onTouchEvent(MotionEvent ev) { 161 boolean result = super.onTouchEvent(ev); 162 // as long as the user is entering a pattern (i.e sending a touch event that was handled 163 // by this screen), keep poking the wake lock so that the screen will stay on. 164 final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; 165 if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { 166 mLastPokeTime = SystemClock.elapsedRealtime(); 167 } 168 mTempRect.set(0, 0, 0, 0); 169 offsetRectIntoDescendantCoords(mLockPatternView, mTempRect); 170 ev.offsetLocation(mTempRect.left, mTempRect.top); 171 result = mLockPatternView.dispatchTouchEvent(ev) || result; 172 ev.offsetLocation(-mTempRect.left, -mTempRect.top); 173 return result; 174 } 175 176 public void reset() { 177 // reset lock pattern 178 mLockPatternView.enableInput(); 179 mLockPatternView.setEnabled(true); 180 mLockPatternView.clearPattern(); 181 182 // if the user is currently locked out, enforce it. 183 long deadline = mLockPatternUtils.getLockoutAttemptDeadline( 184 KeyguardUpdateMonitor.getCurrentUser()); 185 if (deadline != 0) { 186 handleAttemptLockout(deadline); 187 } else { 188 displayDefaultSecurityMessage(); 189 } 190 } 191 192 private void displayDefaultSecurityMessage() { 193 mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false); 194 } 195 196 @Override 197 public void showUsabilityHint() { 198 } 199 200 /** TODO: hook this up */ 201 public void cleanUp() { 202 if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); 203 mLockPatternUtils = null; 204 mLockPatternView.setOnPatternListener(null); 205 } 206 207 private class UnlockPatternListener implements LockPatternView.OnPatternListener { 208 209 public void onPatternStart() { 210 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 211 mSecurityMessageDisplay.setMessage("", false); 212 } 213 214 public void onPatternCleared() { 215 } 216 217 public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { 218 mCallback.userActivity(); 219 } 220 221 public void onPatternDetected(final List<LockPatternView.Cell> pattern) { 222 mLockPatternView.disableInput(); 223 if (mPendingLockCheck != null) { 224 mPendingLockCheck.cancel(false); 225 } 226 227 mPendingLockCheck = LockPatternChecker.checkPattern( 228 mLockPatternUtils, 229 pattern, 230 KeyguardUpdateMonitor.getCurrentUser(), 231 new LockPatternChecker.OnCheckCallback() { 232 @Override 233 public void onChecked(boolean matched) { 234 mLockPatternView.enableInput(); 235 mPendingLockCheck = null; 236 onPatternChecked(pattern, matched); 237 } 238 }); 239 } 240 241 private void onPatternChecked(List<LockPatternView.Cell> pattern, boolean matched) { 242 if (matched) { 243 mCallback.reportUnlockAttempt(true); 244 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); 245 mCallback.dismiss(true); 246 } else { 247 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 248 mCallback.userActivity(); 249 } 250 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 251 boolean registeredAttempt = 252 pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL; 253 if (registeredAttempt) { 254 mCallback.reportUnlockAttempt(false); 255 } 256 int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts(); 257 if (registeredAttempt && 258 0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { 259 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 260 KeyguardUpdateMonitor.getCurrentUser()); 261 handleAttemptLockout(deadline); 262 } else { 263 mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true); 264 mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); 265 } 266 } 267 } 268 } 269 270 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 271 mLockPatternView.clearPattern(); 272 mLockPatternView.setEnabled(false); 273 final long elapsedRealtime = SystemClock.elapsedRealtime(); 274 275 mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { 276 277 @Override 278 public void onTick(long millisUntilFinished) { 279 final int secondsRemaining = (int) (millisUntilFinished / 1000); 280 mSecurityMessageDisplay.setMessage( 281 R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining); 282 } 283 284 @Override 285 public void onFinish() { 286 mLockPatternView.setEnabled(true); 287 displayDefaultSecurityMessage(); 288 } 289 290 }.start(); 291 } 292 293 @Override 294 public boolean needsInput() { 295 return false; 296 } 297 298 @Override 299 public void onPause() { 300 if (mCountdownTimer != null) { 301 mCountdownTimer.cancel(); 302 mCountdownTimer = null; 303 } 304 if (mPendingLockCheck != null) { 305 mPendingLockCheck.cancel(false); 306 mPendingLockCheck = null; 307 } 308 } 309 310 @Override 311 public void onResume(int reason) { 312 reset(); 313 } 314 315 @Override 316 public KeyguardSecurityCallback getCallback() { 317 return mCallback; 318 } 319 320 @Override 321 public void startAppearAnimation() { 322 enableClipping(false); 323 setAlpha(1f); 324 setTranslationY(mAppearAnimationUtils.getStartTranslation()); 325 animate() 326 .setDuration(500) 327 .setInterpolator(mAppearAnimationUtils.getInterpolator()) 328 .translationY(0); 329 mAppearAnimationUtils.startAnimation( 330 mLockPatternView.getCellStates(), 331 new Runnable() { 332 @Override 333 public void run() { 334 enableClipping(true); 335 } 336 }, 337 this); 338 if (!TextUtils.isEmpty(mHelpMessage.getText())) { 339 mAppearAnimationUtils.createAnimation(mHelpMessage, 0, 340 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 341 mAppearAnimationUtils.getStartTranslation(), 342 true /* appearing */, 343 mAppearAnimationUtils.getInterpolator(), 344 null /* finishRunnable */); 345 } 346 } 347 348 @Override 349 public boolean startDisappearAnimation(final Runnable finishRunnable) { 350 mLockPatternView.clearPattern(); 351 enableClipping(false); 352 setTranslationY(0); 353 animate() 354 .setDuration(300) 355 .setInterpolator(mDisappearAnimationUtils.getInterpolator()) 356 .translationY(-mDisappearAnimationUtils.getStartTranslation()); 357 mDisappearAnimationUtils.startAnimation(mLockPatternView.getCellStates(), 358 new Runnable() { 359 @Override 360 public void run() { 361 enableClipping(true); 362 if (finishRunnable != null) { 363 finishRunnable.run(); 364 } 365 } 366 }, KeyguardPatternView.this); 367 if (!TextUtils.isEmpty(mHelpMessage.getText())) { 368 mDisappearAnimationUtils.createAnimation(mHelpMessage, 0, 369 200, 370 - mDisappearAnimationUtils.getStartTranslation() * 3, 371 false /* appearing */, 372 mDisappearAnimationUtils.getInterpolator(), 373 null /* finishRunnable */); 374 } 375 return true; 376 } 377 378 private void enableClipping(boolean enable) { 379 setClipChildren(enable); 380 mContainer.setClipToPadding(enable); 381 mContainer.setClipChildren(enable); 382 } 383 384 @Override 385 public void createAnimation(final LockPatternView.CellState animatedCell, long delay, 386 long duration, float translationY, final boolean appearing, 387 Interpolator interpolator, 388 final Runnable finishListener) { 389 if (appearing) { 390 animatedCell.scale = 0.0f; 391 animatedCell.alpha = 1.0f; 392 } 393 animatedCell.translateY = appearing ? translationY : 0; 394 ValueAnimator animator = ValueAnimator.ofFloat(animatedCell.translateY, 395 appearing ? 0 : translationY); 396 animator.setInterpolator(interpolator); 397 animator.setDuration(duration); 398 animator.setStartDelay(delay); 399 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 400 @Override 401 public void onAnimationUpdate(ValueAnimator animation) { 402 float animatedFraction = animation.getAnimatedFraction(); 403 if (appearing) { 404 animatedCell.scale = animatedFraction; 405 } else { 406 animatedCell.alpha = 1 - animatedFraction; 407 } 408 animatedCell.translateY = (float) animation.getAnimatedValue(); 409 mLockPatternView.invalidate(); 410 } 411 }); 412 if (finishListener != null) { 413 animator.addListener(new AnimatorListenerAdapter() { 414 @Override 415 public void onAnimationEnd(Animator animation) { 416 finishListener.run(); 417 } 418 }); 419 420 // Also animate the Emergency call 421 mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY, 422 appearing, interpolator, null); 423 } 424 animator.start(); 425 mLockPatternView.invalidate(); 426 } 427 428 @Override 429 public boolean hasOverlappingRendering() { 430 return false; 431 } 432} 433