ConfirmLockPattern.java revision 477296898777d0bdf41fc49c047a6b9374f27fe1
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.settings; 18 19import com.android.internal.widget.LockPatternUtils; 20import com.android.internal.widget.LockPatternView; 21import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; 22 23import android.app.Activity; 24import android.content.Intent; 25import android.os.CountDownTimer; 26import android.os.SystemClock; 27import android.os.Bundle; 28import android.widget.TextView; 29import android.view.Window; 30 31import java.util.List; 32 33/** 34 * Launch this when you want the user to confirm their lock pattern. 35 * 36 * Sets an activity result of {@link Activity#RESULT_OK} when the user 37 * successfully confirmed their pattern. 38 */ 39public class ConfirmLockPattern extends Activity { 40 41 /** 42 * Names of {@link CharSequence} fields within the originating {@link Intent} 43 * that are used to configure the keyguard confirmation view's labeling. 44 * The view will use the system-defined resource strings for any labels that 45 * the caller does not supply. 46 */ 47 public static final String HEADER_TEXT = "com.android.settings.ConfirmLockPattern.header"; 48 public static final String FOOTER_TEXT = "com.android.settings.ConfirmLockPattern.footer"; 49 public static final String HEADER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.header_wrong"; 50 public static final String FOOTER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.footer_wrong"; 51 52 // how long we wait to clear a wrong pattern 53 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000; 54 55 private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts"; 56 57 private LockPatternView mLockPatternView; 58 private LockPatternUtils mLockPatternUtils; 59 private int mNumWrongConfirmAttempts; 60 private CountDownTimer mCountdownTimer; 61 62 private TextView mHeaderTextView; 63 private TextView mFooterTextView; 64 65 // caller-supplied text for various prompts 66 private CharSequence mHeaderText; 67 private CharSequence mFooterText; 68 private CharSequence mHeaderWrongText; 69 private CharSequence mFooterWrongText; 70 71 72 private enum Stage { 73 NeedToUnlock, 74 NeedToUnlockWrong, 75 LockedOut 76 } 77 78 @Override 79 protected void onCreate(Bundle savedInstanceState) { 80 super.onCreate(savedInstanceState); 81 82 mLockPatternUtils = new LockPatternUtils(getContentResolver()); 83 84 requestWindowFeature(Window.FEATURE_NO_TITLE); 85 setContentView(R.layout.confirm_lock_pattern); 86 87 mHeaderTextView = (TextView) findViewById(R.id.headerText); 88 mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); 89 mFooterTextView = (TextView) findViewById(R.id.footerText); 90 91 // make it so unhandled touch events within the unlock screen go to the 92 // lock pattern view. 93 final LinearLayoutWithDefaultTouchRecepient topLayout 94 = (LinearLayoutWithDefaultTouchRecepient) findViewById( 95 R.id.topLayout); 96 topLayout.setDefaultTouchRecepient(mLockPatternView); 97 98 Intent intent = getIntent(); 99 if (intent != null) { 100 mHeaderText = intent.getCharSequenceExtra(HEADER_TEXT); 101 mFooterText = intent.getCharSequenceExtra(FOOTER_TEXT); 102 mHeaderWrongText = intent.getCharSequenceExtra(HEADER_WRONG_TEXT); 103 mFooterWrongText = intent.getCharSequenceExtra(FOOTER_WRONG_TEXT); 104 } 105 106 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 107 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); 108 updateStage(Stage.NeedToUnlock); 109 110 if (savedInstanceState != null) { 111 mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS); 112 } else { 113 // on first launch, if no lock pattern is set, then finish with 114 // success (don't want user to get stuck confirming something that 115 // doesn't exist). 116 if (!mLockPatternUtils.savedPatternExists()) { 117 setResult(RESULT_OK); 118 finish(); 119 } 120 } 121 } 122 123 @Override 124 protected void onSaveInstanceState(Bundle outState) { 125 // deliberately not calling super since we are managing this in full 126 outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts); 127 } 128 129 @Override 130 protected void onPause() { 131 super.onPause(); 132 133 if (mCountdownTimer != null) { 134 mCountdownTimer.cancel(); 135 } 136 } 137 138 @Override 139 protected void onResume() { 140 super.onResume(); 141 142 // if the user is currently locked out, enforce it. 143 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); 144 if (deadline != 0) { 145 handleAttemptLockout(deadline); 146 } 147 } 148 149 private void updateStage(Stage stage) { 150 151 switch (stage) { 152 case NeedToUnlock: 153 if (mHeaderText != null) { 154 mHeaderTextView.setText(mHeaderText); 155 } else { 156 mHeaderTextView.setText(R.string.lockpattern_need_to_unlock); 157 } 158 if (mFooterText != null) { 159 mFooterTextView.setText(mFooterText); 160 } else { 161 mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer); 162 } 163 164 mLockPatternView.setEnabled(true); 165 mLockPatternView.enableInput(); 166 break; 167 case NeedToUnlockWrong: 168 if (mHeaderWrongText != null) { 169 mHeaderTextView.setText(mHeaderWrongText); 170 } else { 171 mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong); 172 } 173 if (mFooterWrongText != null) { 174 mFooterTextView.setText(mFooterWrongText); 175 } else { 176 mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer); 177 } 178 179 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 180 mLockPatternView.setEnabled(true); 181 mLockPatternView.enableInput(); 182 break; 183 case LockedOut: 184 mLockPatternView.clearPattern(); 185 // enabled = false means: disable input, and have the 186 // appearance of being disabled. 187 mLockPatternView.setEnabled(false); // appearance of being disabled 188 break; 189 } 190 } 191 192 private Runnable mClearPatternRunnable = new Runnable() { 193 public void run() { 194 mLockPatternView.clearPattern(); 195 } 196 }; 197 198 // clear the wrong pattern unless they have started a new one 199 // already 200 private void postClearPatternRunnable() { 201 mLockPatternView.removeCallbacks(mClearPatternRunnable); 202 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); 203 } 204 205 /** 206 * The pattern listener that responds according to a user confirming 207 * an existing lock pattern. 208 */ 209 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener = new LockPatternView.OnPatternListener() { 210 211 public void onPatternStart() { 212 mLockPatternView.removeCallbacks(mClearPatternRunnable); 213 } 214 215 public void onPatternCleared() { 216 mLockPatternView.removeCallbacks(mClearPatternRunnable); 217 } 218 219 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 220 if (mLockPatternUtils.checkPattern(pattern)) { 221 setResult(RESULT_OK); 222 finish(); 223 } else { 224 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL && 225 ++mNumWrongConfirmAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { 226 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); 227 handleAttemptLockout(deadline); 228 } else { 229 updateStage(Stage.NeedToUnlockWrong); 230 postClearPatternRunnable(); 231 } 232 } 233 } 234 }; 235 236 237 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 238 updateStage(Stage.LockedOut); 239 long elapsedRealtime = SystemClock.elapsedRealtime(); 240 mCountdownTimer = new CountDownTimer( 241 elapsedRealtimeDeadline - elapsedRealtime, 242 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 243 244 @Override 245 public void onTick(long millisUntilFinished) { 246 mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header); 247 final int secondsCountdown = (int) (millisUntilFinished / 1000); 248 mFooterTextView.setText(getString( 249 R.string.lockpattern_too_many_failed_confirmation_attempts_footer, 250 secondsCountdown)); 251 } 252 253 @Override 254 public void onFinish() { 255 mNumWrongConfirmAttempts = 0; 256 updateStage(Stage.NeedToUnlock); 257 } 258 }.start(); 259 } 260} 261