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