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