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; 22import com.android.internal.widget.LockPatternView.Cell; 23 24import android.app.Activity; 25import android.app.Fragment; 26import android.content.Intent; 27import android.os.CountDownTimer; 28import android.os.SystemClock; 29import android.os.Bundle; 30import android.preference.PreferenceActivity; 31import android.widget.TextView; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.ViewGroup; 35 36import 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 */ 44public 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 184 switch (stage) { 185 case NeedToUnlock: 186 if (mHeaderText != null) { 187 mHeaderTextView.setText(mHeaderText); 188 } else { 189 mHeaderTextView.setText(R.string.lockpattern_need_to_unlock); 190 } 191 if (mFooterText != null) { 192 mFooterTextView.setText(mFooterText); 193 } else { 194 mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer); 195 } 196 197 mLockPatternView.setEnabled(true); 198 mLockPatternView.enableInput(); 199 break; 200 case NeedToUnlockWrong: 201 if (mHeaderWrongText != null) { 202 mHeaderTextView.setText(mHeaderWrongText); 203 } else { 204 mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong); 205 } 206 if (mFooterWrongText != null) { 207 mFooterTextView.setText(mFooterWrongText); 208 } else { 209 mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer); 210 } 211 212 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 213 mLockPatternView.setEnabled(true); 214 mLockPatternView.enableInput(); 215 break; 216 case LockedOut: 217 mLockPatternView.clearPattern(); 218 // enabled = false means: disable input, and have the 219 // appearance of being disabled. 220 mLockPatternView.setEnabled(false); // appearance of being disabled 221 break; 222 } 223 } 224 225 private Runnable mClearPatternRunnable = new Runnable() { 226 public void run() { 227 mLockPatternView.clearPattern(); 228 } 229 }; 230 231 // clear the wrong pattern unless they have started a new one 232 // already 233 private void postClearPatternRunnable() { 234 mLockPatternView.removeCallbacks(mClearPatternRunnable); 235 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); 236 } 237 238 /** 239 * The pattern listener that responds according to a user confirming 240 * an existing lock pattern. 241 */ 242 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener 243 = new LockPatternView.OnPatternListener() { 244 245 public void onPatternStart() { 246 mLockPatternView.removeCallbacks(mClearPatternRunnable); 247 } 248 249 public void onPatternCleared() { 250 mLockPatternView.removeCallbacks(mClearPatternRunnable); 251 } 252 253 public void onPatternCellAdded(List<Cell> pattern) { 254 255 } 256 257 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 258 if (mLockPatternUtils.checkPattern(pattern)) { 259 260 Intent intent = new Intent(); 261 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, 262 LockPatternUtils.patternToString(pattern)); 263 264 getActivity().setResult(Activity.RESULT_OK, intent); 265 getActivity().finish(); 266 } else { 267 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL && 268 ++mNumWrongConfirmAttempts 269 >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { 270 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); 271 handleAttemptLockout(deadline); 272 } else { 273 updateStage(Stage.NeedToUnlockWrong); 274 postClearPatternRunnable(); 275 } 276 } 277 } 278 }; 279 280 281 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 282 updateStage(Stage.LockedOut); 283 long elapsedRealtime = SystemClock.elapsedRealtime(); 284 mCountdownTimer = new CountDownTimer( 285 elapsedRealtimeDeadline - elapsedRealtime, 286 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 287 288 @Override 289 public void onTick(long millisUntilFinished) { 290 mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header); 291 final int secondsCountdown = (int) (millisUntilFinished / 1000); 292 mFooterTextView.setText(getString( 293 R.string.lockpattern_too_many_failed_confirmation_attempts_footer, 294 secondsCountdown)); 295 } 296 297 @Override 298 public void onFinish() { 299 mNumWrongConfirmAttempts = 0; 300 updateStage(Stage.NeedToUnlock); 301 } 302 }.start(); 303 } 304 } 305} 306