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