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