KeyguardPatternView.java revision 258341c377b6aa9f1bd29a9b507a97967e432dfe
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.internal.policy.impl.keyguard; 17 18import android.accounts.Account; 19import android.accounts.AccountManager; 20import android.accounts.AccountManagerCallback; 21import android.accounts.AccountManagerFuture; 22import android.accounts.AuthenticatorException; 23import android.accounts.OperationCanceledException; 24import android.content.Context; 25import android.os.Bundle; 26import android.os.CountDownTimer; 27import android.os.SystemClock; 28import android.security.KeyStore; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.view.MotionEvent; 32import android.view.View; 33import android.widget.Button; 34import android.widget.LinearLayout; 35 36import com.android.internal.widget.LockPatternUtils; 37import com.android.internal.widget.LockPatternView; 38import com.android.internal.R; 39 40import java.io.IOException; 41import java.util.List; 42 43public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView { 44 45 private static final String TAG = "SecurityPatternView"; 46 private static final boolean DEBUG = false; 47 48 // how long before we clear the wrong pattern 49 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; 50 51 // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK 52 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; 53 54 // how long we stay awake after the user hits the first dot. 55 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000; 56 57 // how many cells the user has to cross before we poke the wakelock 58 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; 59 60 private int mFailedPatternAttemptsSinceLastTimeout = 0; 61 private int mTotalFailedPatternAttempts = 0; 62 private CountDownTimer mCountdownTimer = null; 63 private LockPatternUtils mLockPatternUtils; 64 private LockPatternView mLockPatternView; 65 private Button mForgotPatternButton; 66 private KeyguardSecurityCallback mCallback; 67 private boolean mEnableFallback; 68 private KeyguardNavigationManager mNavigationManager; 69 70 /** 71 * Keeps track of the last time we poked the wake lock during dispatching of the touch event. 72 * Initialized to something guaranteed to make us poke the wakelock when the user starts 73 * drawing the pattern. 74 * @see #dispatchTouchEvent(android.view.MotionEvent) 75 */ 76 private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; 77 78 /** 79 * Useful for clearing out the wrong pattern after a delay 80 */ 81 private Runnable mCancelPatternRunnable = new Runnable() { 82 public void run() { 83 mLockPatternView.clearPattern(); 84 } 85 }; 86 87 enum FooterMode { 88 Normal, 89 ForgotLockPattern, 90 VerifyUnlocked 91 } 92 93 public KeyguardPatternView(Context context) { 94 this(context, null); 95 } 96 97 public KeyguardPatternView(Context context, AttributeSet attrs) { 98 super(context, attrs); 99 } 100 101 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 102 mCallback = callback; 103 } 104 105 public void setLockPatternUtils(LockPatternUtils utils) { 106 mLockPatternUtils = utils; 107 } 108 109 @Override 110 protected void onFinishInflate() { 111 super.onFinishInflate(); 112 113 mNavigationManager = new KeyguardNavigationManager(this); 114 mLockPatternUtils = mLockPatternUtils == null 115 ? new LockPatternUtils(mContext) : mLockPatternUtils; 116 117 mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView); 118 mLockPatternView.setSaveEnabled(false); 119 mLockPatternView.setFocusable(false); 120 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 121 122 // stealth mode will be the same for the life of this screen 123 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled()); 124 125 // vibrate mode will be the same for the life of this screen 126 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 127 128 mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button); 129 mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text); 130 mForgotPatternButton.setOnClickListener(new OnClickListener() { 131 public void onClick(View v) { 132 mCallback.showBackupSecurity(); 133 } 134 }); 135 136 setFocusableInTouchMode(true); 137 138 maybeEnableFallback(mContext); 139 } 140 141 private void updateFooter(FooterMode mode) { 142 switch (mode) { 143 case Normal: 144 if (DEBUG) Log.d(TAG, "mode normal"); 145 mForgotPatternButton.setVisibility(View.GONE); 146 break; 147 case ForgotLockPattern: 148 if (DEBUG) Log.d(TAG, "mode ForgotLockPattern"); 149 mForgotPatternButton.setVisibility(View.VISIBLE); 150 break; 151 case VerifyUnlocked: 152 if (DEBUG) Log.d(TAG, "mode VerifyUnlocked"); 153 mForgotPatternButton.setVisibility(View.GONE); 154 } 155 } 156 157 @Override 158 public boolean dispatchTouchEvent(MotionEvent ev) { 159 final boolean result = super.dispatchTouchEvent(ev); 160 // as long as the user is entering a pattern (i.e sending a touch event that was handled 161 // by this screen), keep poking the wake lock so that the screen will stay on. 162 final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; 163 if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { 164 mLastPokeTime = SystemClock.elapsedRealtime(); 165 } 166 return result; 167 } 168 169 public void reset() { 170 // reset lock pattern 171 mLockPatternView.enableInput(); 172 mLockPatternView.setEnabled(true); 173 mLockPatternView.clearPattern(); 174 175 // if the user is currently locked out, enforce it. 176 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); 177 if (deadline != 0) { 178 handleAttemptLockout(deadline); 179 } else { 180 mNavigationManager.setMessage(R.string.kg_pattern_instructions); 181 } 182 183 // the footer depends on how many total attempts the user has failed 184 if (mCallback.isVerifyUnlockOnly()) { 185 updateFooter(FooterMode.VerifyUnlocked); 186 } else if (mEnableFallback && 187 (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { 188 updateFooter(FooterMode.ForgotLockPattern); 189 } else { 190 updateFooter(FooterMode.Normal); 191 } 192 193 } 194 195 /** TODO: hook this up */ 196 public void cleanUp() { 197 if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); 198 mLockPatternUtils = null; 199 mLockPatternView.setOnPatternListener(null); 200 } 201 202 @Override 203 public void onWindowFocusChanged(boolean hasWindowFocus) { 204 super.onWindowFocusChanged(hasWindowFocus); 205 if (hasWindowFocus) { 206 // when timeout dialog closes we want to update our state 207 reset(); 208 } 209 } 210 211 private class UnlockPatternListener implements LockPatternView.OnPatternListener { 212 213 public void onPatternStart() { 214 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 215 } 216 217 public void onPatternCleared() { 218 } 219 220 public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { 221 // To guard against accidental poking of the wakelock, look for 222 // the user actually trying to draw a pattern of some minimal length. 223 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 224 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); 225 } else { 226 // Give just a little extra time if they hit one of the first few dots 227 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS); 228 } 229 } 230 231 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 232 if (mLockPatternUtils.checkPattern(pattern)) { 233 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); 234 mCallback.dismiss(true); // keyguardDone(true) 235 KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern)); 236 mTotalFailedPatternAttempts = 0; 237 } else { 238 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 239 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); 240 } 241 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 242 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 243 mTotalFailedPatternAttempts++; 244 mFailedPatternAttemptsSinceLastTimeout++; 245 mCallback.reportFailedUnlockAttempt(); 246 } 247 if (mFailedPatternAttemptsSinceLastTimeout 248 >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { 249 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); 250 handleAttemptLockout(deadline); 251 } else { 252 mNavigationManager.setMessage(R.string.kg_wrong_pattern); 253 mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); 254 } 255 } 256 } 257 } 258 259 private void maybeEnableFallback(Context context) { 260 // Ask the account manager if we have an account that can be used as a 261 // fallback in case the user forgets his pattern. 262 AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context)); 263 accountAnalyzer.start(); 264 } 265 266 private class AccountAnalyzer implements AccountManagerCallback<Bundle> { 267 private final AccountManager mAccountManager; 268 private final Account[] mAccounts; 269 private int mAccountIndex; 270 271 private AccountAnalyzer(AccountManager accountManager) { 272 mAccountManager = accountManager; 273 mAccounts = accountManager.getAccountsByType("com.google"); 274 } 275 276 private void next() { 277 // if we are ready to enable the fallback or if we depleted the list of accounts 278 // then finish and get out 279 if (mAccountIndex >= mAccounts.length) { 280 mEnableFallback = true; 281 return; 282 } 283 284 // lookup the confirmCredentials intent for the current account 285 mAccountManager.confirmCredentials(mAccounts[mAccountIndex], null, null, this, null); 286 } 287 288 public void start() { 289 mEnableFallback = false; 290 mAccountIndex = 0; 291 next(); 292 } 293 294 public void run(AccountManagerFuture<Bundle> future) { 295 try { 296 Bundle result = future.getResult(); 297 if (result.getParcelable(AccountManager.KEY_INTENT) != null) { 298 mEnableFallback = true; 299 } 300 } catch (OperationCanceledException e) { 301 // just skip the account if we are unable to query it 302 } catch (IOException e) { 303 // just skip the account if we are unable to query it 304 } catch (AuthenticatorException e) { 305 // just skip the account if we are unable to query it 306 } finally { 307 mAccountIndex++; 308 next(); 309 } 310 } 311 } 312 313 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 314 mLockPatternView.clearPattern(); 315 mLockPatternView.setEnabled(false); 316 final long elapsedRealtime = SystemClock.elapsedRealtime(); 317 updateFooter(FooterMode.ForgotLockPattern); 318 319 mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { 320 321 @Override 322 public void onTick(long millisUntilFinished) { 323 final int secondsRemaining = (int) (millisUntilFinished / 1000); 324 mNavigationManager.setMessage( 325 R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); 326 } 327 328 @Override 329 public void onFinish() { 330 mLockPatternView.setEnabled(true); 331 mNavigationManager.setMessage(R.string.kg_pattern_instructions); 332 // TODO mUnlockIcon.setVisibility(View.VISIBLE); 333 mFailedPatternAttemptsSinceLastTimeout = 0; 334 if (mEnableFallback) { 335 updateFooter(FooterMode.ForgotLockPattern); 336 } else { 337 updateFooter(FooterMode.Normal); 338 } 339 } 340 341 }.start(); 342 } 343 344 @Override 345 public boolean needsInput() { 346 return false; 347 } 348 349 @Override 350 public void onPause() { 351 if (mCountdownTimer != null) { 352 mCountdownTimer.cancel(); 353 mCountdownTimer = null; 354 } 355 } 356 357 @Override 358 public void onResume() { 359 reset(); 360 } 361 362 @Override 363 public KeyguardSecurityCallback getCallback() { 364 return mCallback; 365 } 366 367} 368 369 370 371