KeyguardPatternView.java revision 5673353559453ecb57fc767b4e7500dd46e44079
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; 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 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 62 private final AppearAnimationUtils mAppearAnimationUtils; 63 private final DisappearAnimationUtils mDisappearAnimationUtils; 64 65 private CountDownTimer mCountdownTimer = null; 66 private LockPatternUtils mLockPatternUtils; 67 private AsyncTask<?, ?, ?> mPendingLockCheck; 68 private LockPatternView mLockPatternView; 69 private KeyguardSecurityCallback mCallback; 70 71 /** 72 * Keeps track of the last time we poked the wake lock during dispatching of the touch event. 73 * Initialized to something guaranteed to make us poke the wakelock when the user starts 74 * drawing the pattern. 75 * @see #dispatchTouchEvent(android.view.MotionEvent) 76 */ 77 private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; 78 79 /** 80 * Useful for clearing out the wrong pattern after a delay 81 */ 82 private Runnable mCancelPatternRunnable = new Runnable() { 83 public void run() { 84 mLockPatternView.clearPattern(); 85 } 86 }; 87 private Rect mTempRect = new Rect(); 88 private SecurityMessageDisplay mSecurityMessageDisplay; 89 private View mEcaView; 90 private ViewGroup mContainer; 91 private KeyguardMessageArea mHelpMessage; 92 private int mDisappearYTranslation; 93 94 enum FooterMode { 95 Normal, 96 ForgotLockPattern, 97 VerifyUnlocked 98 } 99 100 public KeyguardPatternView(Context context) { 101 this(context, null); 102 } 103 104 public KeyguardPatternView(Context context, AttributeSet attrs) { 105 super(context, attrs); 106 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 107 mAppearAnimationUtils = new AppearAnimationUtils(context, 108 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, 109 2.0f /* delayScale */, AnimationUtils.loadInterpolator( 110 mContext, android.R.interpolator.linear_out_slow_in)); 111 mDisappearAnimationUtils = new DisappearAnimationUtils(context, 112 125, 1.2f /* translationScale */, 113 0.8f /* delayScale */, AnimationUtils.loadInterpolator( 114 mContext, android.R.interpolator.fast_out_linear_in)); 115 mDisappearYTranslation = getResources().getDimensionPixelSize( 116 R.dimen.disappear_y_translation); 117 } 118 119 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 120 mCallback = callback; 121 } 122 123 public void setLockPatternUtils(LockPatternUtils utils) { 124 mLockPatternUtils = utils; 125 } 126 127 @Override 128 protected void onFinishInflate() { 129 super.onFinishInflate(); 130 mLockPatternUtils = mLockPatternUtils == null 131 ? new LockPatternUtils(mContext) : mLockPatternUtils; 132 133 mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView); 134 mLockPatternView.setSaveEnabled(false); 135 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 136 137 // stealth mode will be the same for the life of this screen 138 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 139 KeyguardUpdateMonitor.getCurrentUser())); 140 141 // vibrate mode will be the same for the life of this screen 142 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 143 144 mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(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 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 228 mLockPatternView.enableInput(); 229 onPatternChecked(pattern, false, 0); 230 return; 231 } 232 233 mPendingLockCheck = LockPatternChecker.checkPattern( 234 mLockPatternUtils, 235 pattern, 236 KeyguardUpdateMonitor.getCurrentUser(), 237 new LockPatternChecker.OnCheckCallback() { 238 @Override 239 public void onChecked(boolean matched, int timeoutMs) { 240 mLockPatternView.enableInput(); 241 mPendingLockCheck = null; 242 onPatternChecked(pattern, matched, timeoutMs); 243 } 244 }); 245 } 246 247 private void onPatternChecked(List<LockPatternView.Cell> pattern, boolean matched, 248 int timeoutMs) { 249 if (matched) { 250 mCallback.reportUnlockAttempt(true, 0); 251 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); 252 mCallback.dismiss(true); 253 } else { 254 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 255 mCallback.userActivity(); 256 } 257 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 258 mCallback.reportUnlockAttempt(false, timeoutMs); 259 int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts(); 260 if (timeoutMs > 0) { 261 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 262 KeyguardUpdateMonitor.getCurrentUser(), timeoutMs); 263 handleAttemptLockout(deadline); 264 } else { 265 mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true); 266 mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); 267 } 268 } 269 } 270 } 271 272 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 273 mLockPatternView.clearPattern(); 274 mLockPatternView.setEnabled(false); 275 final long elapsedRealtime = SystemClock.elapsedRealtime(); 276 277 mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { 278 279 @Override 280 public void onTick(long millisUntilFinished) { 281 final int secondsRemaining = (int) (millisUntilFinished / 1000); 282 mSecurityMessageDisplay.setMessage( 283 R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining); 284 } 285 286 @Override 287 public void onFinish() { 288 mLockPatternView.setEnabled(true); 289 displayDefaultSecurityMessage(); 290 } 291 292 }.start(); 293 } 294 295 @Override 296 public boolean needsInput() { 297 return false; 298 } 299 300 @Override 301 public void onPause() { 302 if (mCountdownTimer != null) { 303 mCountdownTimer.cancel(); 304 mCountdownTimer = null; 305 } 306 if (mPendingLockCheck != null) { 307 mPendingLockCheck.cancel(false); 308 mPendingLockCheck = null; 309 } 310 } 311 312 @Override 313 public void onResume(int reason) { 314 reset(); 315 } 316 317 @Override 318 public KeyguardSecurityCallback getCallback() { 319 return mCallback; 320 } 321 322 @Override 323 public void startAppearAnimation() { 324 enableClipping(false); 325 setAlpha(1f); 326 setTranslationY(mAppearAnimationUtils.getStartTranslation()); 327 animate() 328 .setDuration(500) 329 .setInterpolator(mAppearAnimationUtils.getInterpolator()) 330 .translationY(0); 331 mAppearAnimationUtils.startAnimation2d( 332 mLockPatternView.getCellStates(), 333 new Runnable() { 334 @Override 335 public void run() { 336 enableClipping(true); 337 } 338 }, 339 this); 340 if (!TextUtils.isEmpty(mHelpMessage.getText())) { 341 mAppearAnimationUtils.createAnimation(mHelpMessage, 0, 342 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 343 mAppearAnimationUtils.getStartTranslation(), 344 true /* appearing */, 345 mAppearAnimationUtils.getInterpolator(), 346 null /* finishRunnable */); 347 } 348 } 349 350 @Override 351 public boolean startDisappearAnimation(final Runnable finishRunnable) { 352 mLockPatternView.clearPattern(); 353 enableClipping(false); 354 setTranslationY(0); 355 animate() 356 .setDuration(300) 357 .setInterpolator(mDisappearAnimationUtils.getInterpolator()) 358 .translationY(-mDisappearAnimationUtils.getStartTranslation()); 359 mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), 360 new Runnable() { 361 @Override 362 public void run() { 363 enableClipping(true); 364 if (finishRunnable != null) { 365 finishRunnable.run(); 366 } 367 } 368 }, KeyguardPatternView.this); 369 if (!TextUtils.isEmpty(mHelpMessage.getText())) { 370 mDisappearAnimationUtils.createAnimation(mHelpMessage, 0, 371 200, 372 - mDisappearAnimationUtils.getStartTranslation() * 3, 373 false /* appearing */, 374 mDisappearAnimationUtils.getInterpolator(), 375 null /* finishRunnable */); 376 } 377 return true; 378 } 379 380 private void enableClipping(boolean enable) { 381 setClipChildren(enable); 382 mContainer.setClipToPadding(enable); 383 mContainer.setClipChildren(enable); 384 } 385 386 @Override 387 public void createAnimation(final LockPatternView.CellState animatedCell, long delay, 388 long duration, float translationY, final boolean appearing, 389 Interpolator interpolator, 390 final Runnable finishListener) { 391 if (appearing) { 392 animatedCell.scale = 0.0f; 393 animatedCell.alpha = 1.0f; 394 } 395 animatedCell.translateY = appearing ? translationY : 0; 396 ValueAnimator animator = ValueAnimator.ofFloat(animatedCell.translateY, 397 appearing ? 0 : translationY); 398 animator.setInterpolator(interpolator); 399 animator.setDuration(duration); 400 animator.setStartDelay(delay); 401 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 402 @Override 403 public void onAnimationUpdate(ValueAnimator animation) { 404 float animatedFraction = animation.getAnimatedFraction(); 405 if (appearing) { 406 animatedCell.scale = animatedFraction; 407 } else { 408 animatedCell.alpha = 1 - animatedFraction; 409 } 410 animatedCell.translateY = (float) animation.getAnimatedValue(); 411 mLockPatternView.invalidate(); 412 } 413 }); 414 if (finishListener != null) { 415 animator.addListener(new AnimatorListenerAdapter() { 416 @Override 417 public void onAnimationEnd(Animator animation) { 418 finishListener.run(); 419 } 420 }); 421 422 // Also animate the Emergency call 423 mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY, 424 appearing, interpolator, null); 425 } 426 animator.start(); 427 mLockPatternView.invalidate(); 428 } 429 430 @Override 431 public boolean hasOverlappingRendering() { 432 return false; 433 } 434} 435