1/* 2 * Copyright (C) 2008 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 17package com.android.internal.policy.impl.keyguard_obsolete; 18 19import android.content.Context; 20import android.content.res.Configuration; 21import android.os.CountDownTimer; 22import android.os.SystemClock; 23import android.security.KeyStore; 24import android.view.LayoutInflater; 25import android.view.View; 26import android.view.MotionEvent; 27import android.widget.Button; 28import android.util.Log; 29import com.android.internal.R; 30import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; 31import com.android.internal.widget.LockPatternUtils; 32import com.android.internal.widget.LockPatternView; 33import com.android.internal.widget.LockPatternView.Cell; 34 35import java.util.List; 36 37/** 38 * This is the screen that shows the 9 circle unlock widget and instructs 39 * the user how to unlock their device, or make an emergency call. 40 */ 41class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient 42 implements KeyguardScreen { 43 44 private static final boolean DEBUG = false; 45 private static final String TAG = "UnlockScreen"; 46 47 // how long before we clear the wrong pattern 48 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; 49 50 // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK 51 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; 52 53 // how long we stay awake after the user hits the first dot. 54 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000; 55 56 // how many cells the user has to cross before we poke the wakelock 57 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; 58 59 private int mFailedPatternAttemptsSinceLastTimeout = 0; 60 private int mTotalFailedPatternAttempts = 0; 61 private CountDownTimer mCountdownTimer = null; 62 63 private LockPatternUtils mLockPatternUtils; 64 private KeyguardUpdateMonitor mUpdateMonitor; 65 private KeyguardScreenCallback mCallback; 66 67 /** 68 * whether there is a fallback option available when the pattern is forgotten. 69 */ 70 private boolean mEnableFallback; 71 72 private KeyguardStatusViewManager mKeyguardStatusViewManager; 73 private LockPatternView mLockPatternView; 74 75 /** 76 * Keeps track of the last time we poked the wake lock during dispatching 77 * of the touch event, initalized to something gauranteed to make us 78 * poke it when the user starts drawing the pattern. 79 * @see #dispatchTouchEvent(android.view.MotionEvent) 80 */ 81 private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; 82 83 /** 84 * Useful for clearing out the wrong pattern after a delay 85 */ 86 private Runnable mCancelPatternRunnable = new Runnable() { 87 public void run() { 88 mLockPatternView.clearPattern(); 89 } 90 }; 91 92 private final OnClickListener mForgotPatternClick = new OnClickListener() { 93 public void onClick(View v) { 94 mCallback.forgotPattern(true); 95 } 96 }; 97 98 private Button mForgotPatternButton; 99 private int mCreationOrientation; 100 101 enum FooterMode { 102 Normal, 103 ForgotLockPattern, 104 VerifyUnlocked 105 } 106 107 private void hideForgotPatternButton() { 108 mForgotPatternButton.setVisibility(View.GONE); 109 } 110 111 private void showForgotPatternButton() { 112 mForgotPatternButton.setVisibility(View.VISIBLE); 113 } 114 115 private void updateFooter(FooterMode mode) { 116 switch (mode) { 117 case Normal: 118 if (DEBUG) Log.d(TAG, "mode normal"); 119 hideForgotPatternButton(); 120 break; 121 case ForgotLockPattern: 122 if (DEBUG) Log.d(TAG, "mode ForgotLockPattern"); 123 showForgotPatternButton(); 124 break; 125 case VerifyUnlocked: 126 if (DEBUG) Log.d(TAG, "mode VerifyUnlocked"); 127 hideForgotPatternButton(); 128 } 129 } 130 131 /** 132 * @param context The context. 133 * @param configuration 134 * @param lockPatternUtils Used to lookup lock pattern settings. 135 * @param updateMonitor Used to lookup state affecting keyguard. 136 * @param callback Used to notify the manager when we're done, etc. 137 * @param totalFailedAttempts The current number of failed attempts. 138 * @param enableFallback True if a backup unlock option is available when the user has forgotten 139 * their pattern (e.g they have a google account so we can show them the account based 140 * backup option). 141 */ 142 PatternUnlockScreen(Context context, 143 Configuration configuration, LockPatternUtils lockPatternUtils, 144 KeyguardUpdateMonitor updateMonitor, 145 KeyguardScreenCallback callback, 146 int totalFailedAttempts) { 147 super(context); 148 mLockPatternUtils = lockPatternUtils; 149 mUpdateMonitor = updateMonitor; 150 mCallback = callback; 151 mTotalFailedPatternAttempts = totalFailedAttempts; 152 mFailedPatternAttemptsSinceLastTimeout = 153 totalFailedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; 154 155 if (DEBUG) Log.d(TAG, 156 "UnlockScreen() ctor: totalFailedAttempts=" 157 + totalFailedAttempts + ", mFailedPat...=" 158 + mFailedPatternAttemptsSinceLastTimeout 159 ); 160 161 mCreationOrientation = configuration.orientation; 162 163 LayoutInflater inflater = LayoutInflater.from(context); 164 165 if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) { 166 Log.d(TAG, "portrait mode"); 167 inflater.inflate(R.layout.keyguard_screen_unlock_portrait, this, true); 168 } else { 169 Log.d(TAG, "landscape mode"); 170 inflater.inflate(R.layout.keyguard_screen_unlock_landscape, this, true); 171 } 172 173 mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, mUpdateMonitor, 174 mLockPatternUtils, mCallback, true); 175 176 mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); 177 178 mForgotPatternButton = (Button) findViewById(R.id.forgotPatternButton); 179 mForgotPatternButton.setText(R.string.lockscreen_forgot_pattern_button_text); 180 mForgotPatternButton.setOnClickListener(mForgotPatternClick); 181 182 // make it so unhandled touch events within the unlock screen go to the 183 // lock pattern view. 184 setDefaultTouchRecepient(mLockPatternView); 185 186 mLockPatternView.setSaveEnabled(false); 187 mLockPatternView.setFocusable(false); 188 mLockPatternView.setOnPatternListener(new UnlockPatternListener()); 189 190 // stealth mode will be the same for the life of this screen 191 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled()); 192 193 // vibrate mode will be the same for the life of this screen 194 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 195 196 // assume normal footer mode for now 197 updateFooter(FooterMode.Normal); 198 199 setFocusableInTouchMode(true); 200 } 201 202 public void setEnableFallback(boolean state) { 203 if (DEBUG) Log.d(TAG, "setEnableFallback(" + state + ")"); 204 mEnableFallback = state; 205 } 206 207 @Override 208 public boolean dispatchTouchEvent(MotionEvent ev) { 209 // as long as the user is entering a pattern (i.e sending a touch 210 // event that was handled by this screen), keep poking the 211 // wake lock so that the screen will stay on. 212 final boolean result = super.dispatchTouchEvent(ev); 213 if (result && 214 ((SystemClock.elapsedRealtime() - mLastPokeTime) 215 > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { 216 mLastPokeTime = SystemClock.elapsedRealtime(); 217 } 218 return result; 219 } 220 221 @Override 222 protected void onAttachedToWindow() { 223 super.onAttachedToWindow(); 224 if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { 225 Log.v(TAG, "***** PATTERN ATTACHED TO WINDOW"); 226 Log.v(TAG, "Cur orient=" + mCreationOrientation 227 + ", new config=" + getResources().getConfiguration()); 228 } 229 if (getResources().getConfiguration().orientation != mCreationOrientation) { 230 mCallback.recreateMe(getResources().getConfiguration()); 231 } 232 } 233 234 235 /** {@inheritDoc} */ 236 @Override 237 protected void onConfigurationChanged(Configuration newConfig) { 238 super.onConfigurationChanged(newConfig); 239 if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { 240 Log.v(TAG, "***** PATTERN CONFIGURATION CHANGED"); 241 Log.v(TAG, "Cur orient=" + mCreationOrientation 242 + ", new config=" + getResources().getConfiguration()); 243 } 244 if (newConfig.orientation != mCreationOrientation) { 245 mCallback.recreateMe(newConfig); 246 } 247 } 248 249 /** {@inheritDoc} */ 250 public void onKeyboardChange(boolean isKeyboardOpen) {} 251 252 /** {@inheritDoc} */ 253 public boolean needsInput() { 254 return false; 255 } 256 257 /** {@inheritDoc} */ 258 public void onPause() { 259 if (mCountdownTimer != null) { 260 mCountdownTimer.cancel(); 261 mCountdownTimer = null; 262 } 263 mKeyguardStatusViewManager.onPause(); 264 } 265 266 /** {@inheritDoc} */ 267 public void onResume() { 268 // reset status 269 mKeyguardStatusViewManager.onResume(); 270 271 // reset lock pattern 272 mLockPatternView.enableInput(); 273 mLockPatternView.setEnabled(true); 274 mLockPatternView.clearPattern(); 275 276 // show "forgot pattern?" button if we have an alternate authentication method 277 if (mCallback.doesFallbackUnlockScreenExist()) { 278 showForgotPatternButton(); 279 } else { 280 hideForgotPatternButton(); 281 } 282 283 // if the user is currently locked out, enforce it. 284 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); 285 if (deadline != 0) { 286 handleAttemptLockout(deadline); 287 } 288 289 // the footer depends on how many total attempts the user has failed 290 if (mCallback.isVerifyUnlockOnly()) { 291 updateFooter(FooterMode.VerifyUnlocked); 292 } else if (mEnableFallback && 293 (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { 294 updateFooter(FooterMode.ForgotLockPattern); 295 } else { 296 updateFooter(FooterMode.Normal); 297 } 298 299 } 300 301 /** {@inheritDoc} */ 302 public void cleanUp() { 303 if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); 304 mUpdateMonitor.removeCallback(this); 305 mLockPatternUtils = null; 306 mUpdateMonitor = null; 307 mCallback = null; 308 mLockPatternView.setOnPatternListener(null); 309 } 310 311 @Override 312 public void onWindowFocusChanged(boolean hasWindowFocus) { 313 super.onWindowFocusChanged(hasWindowFocus); 314 if (hasWindowFocus) { 315 // when timeout dialog closes we want to update our state 316 onResume(); 317 } 318 } 319 320 private class UnlockPatternListener 321 implements LockPatternView.OnPatternListener { 322 323 public void onPatternStart() { 324 mLockPatternView.removeCallbacks(mCancelPatternRunnable); 325 } 326 327 public void onPatternCleared() { 328 } 329 330 public void onPatternCellAdded(List<Cell> pattern) { 331 // To guard against accidental poking of the wakelock, look for 332 // the user actually trying to draw a pattern of some minimal length. 333 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 334 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS); 335 } else { 336 // Give just a little extra time if they hit one of the first few dots 337 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS); 338 } 339 } 340 341 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 342 if (mLockPatternUtils.checkPattern(pattern)) { 343 mLockPatternView 344 .setDisplayMode(LockPatternView.DisplayMode.Correct); 345 mKeyguardStatusViewManager.setInstructionText(""); 346 mKeyguardStatusViewManager.updateStatusLines(true); 347 mCallback.keyguardDone(true); 348 mCallback.reportSuccessfulUnlockAttempt(); 349 KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern)); 350 } else { 351 boolean reportFailedAttempt = false; 352 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { 353 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS); 354 } 355 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 356 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 357 mTotalFailedPatternAttempts++; 358 mFailedPatternAttemptsSinceLastTimeout++; 359 reportFailedAttempt = true; 360 } 361 if (mFailedPatternAttemptsSinceLastTimeout 362 >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { 363 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); 364 handleAttemptLockout(deadline); 365 } else { 366 // TODO mUnlockIcon.setVisibility(View.VISIBLE); 367 mKeyguardStatusViewManager.setInstructionText( 368 getContext().getString(R.string.lockscreen_pattern_wrong)); 369 mKeyguardStatusViewManager.updateStatusLines(true); 370 mLockPatternView.postDelayed( 371 mCancelPatternRunnable, 372 PATTERN_CLEAR_TIMEOUT_MS); 373 } 374 375 // Because the following can result in cleanUp() being called on this screen, 376 // member variables reset in cleanUp() shouldn't be accessed after this call. 377 if (reportFailedAttempt) { 378 mCallback.reportFailedUnlockAttempt(); 379 } 380 } 381 } 382 } 383 384 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 385 mLockPatternView.clearPattern(); 386 mLockPatternView.setEnabled(false); 387 long elapsedRealtime = SystemClock.elapsedRealtime(); 388 mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { 389 390 @Override 391 public void onTick(long millisUntilFinished) { 392 int secondsRemaining = (int) (millisUntilFinished / 1000); 393 mKeyguardStatusViewManager.setInstructionText(getContext().getString( 394 R.string.lockscreen_too_many_failed_attempts_countdown, 395 secondsRemaining)); 396 mKeyguardStatusViewManager.updateStatusLines(true); 397 } 398 399 @Override 400 public void onFinish() { 401 mLockPatternView.setEnabled(true); 402 mKeyguardStatusViewManager.setInstructionText(getContext().getString( 403 R.string.lockscreen_pattern_instructions)); 404 mKeyguardStatusViewManager.updateStatusLines(true); 405 // TODO mUnlockIcon.setVisibility(View.VISIBLE); 406 mFailedPatternAttemptsSinceLastTimeout = 0; 407 if (mEnableFallback) { 408 updateFooter(FooterMode.ForgotLockPattern); 409 } else { 410 updateFooter(FooterMode.Normal); 411 } 412 } 413 }.start(); 414 } 415 416} 417