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