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 KeyguardPatternView(Context context)102 public KeyguardPatternView(Context context) { 103 this(context, null); 104 } 105 KeyguardPatternView(Context context, AttributeSet attrs)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 setKeyguardCallback(KeyguardSecurityCallback callback)122 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 123 mCallback = callback; 124 } 125 126 @Override setLockPatternUtils(LockPatternUtils utils)127 public void setLockPatternUtils(LockPatternUtils utils) { 128 mLockPatternUtils = utils; 129 } 130 131 @Override onFinishInflate()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 onEmergencyButtonClickedWhenInCall()160 public void onEmergencyButtonClickedWhenInCall() { 161 mCallback.reset(); 162 } 163 164 @Override onTouchEvent(MotionEvent ev)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 reset()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 displayDefaultSecurityMessage()198 private void displayDefaultSecurityMessage() { 199 mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false); 200 } 201 202 @Override showUsabilityHint()203 public void showUsabilityHint() { 204 } 205 206 /** TODO: hook this up */ cleanUp()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 onPatternStart()216 public void onPatternStart() { 217 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 218 mSecurityMessageDisplay.setMessage("", false); 219 } 220 221 @Override onPatternCleared()222 public void onPatternCleared() { 223 } 224 225 @Override onPatternCellAdded(List<LockPatternView.Cell> pattern)226 public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { 227 mCallback.userActivity(); 228 } 229 230 @Override onPatternDetected(final List<LockPatternView.Cell> pattern)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 onPatternChecked(boolean matched, int timeoutMs, boolean isValidPattern)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 handleAttemptLockout(long elapsedRealtimeDeadline)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 needsInput()307 public boolean needsInput() { 308 return false; 309 } 310 311 @Override onPause()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 onResume(int reason)324 public void onResume(int reason) { 325 reset(); 326 } 327 328 @Override getCallback()329 public KeyguardSecurityCallback getCallback() { 330 return mCallback; 331 } 332 333 @Override showPromptReason(int reason)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 startAppearAnimation()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 startDisappearAnimation(final Runnable finishRunnable)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 enableClipping(boolean enable)399 private void enableClipping(boolean enable) { 400 setClipChildren(enable); 401 mContainer.setClipToPadding(enable); 402 mContainer.setClipChildren(enable); 403 } 404 405 @Override createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)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 hasOverlappingRendering()423 public boolean hasOverlappingRendering() { 424 return false; 425 } 426 } 427