ChooseLockPassword.java revision 70d5c3a0139899e5f4d425c8ab2d68f0dfc5c6da
1/* 2 * Copyright (C) 2010 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 java.util.regex.Matcher; 20import java.util.regex.Pattern; 21 22import com.android.internal.widget.LockPatternUtils; 23import com.android.internal.widget.PasswordEntryKeyboardHelper; 24import com.android.internal.widget.PasswordEntryKeyboardView; 25import com.android.settings.ChooseLockPattern.LeftButtonMode; 26import com.android.settings.ChooseLockPattern.RightButtonMode; 27import com.android.settings.ChooseLockPattern.Stage; 28 29import android.app.Activity; 30import android.content.Intent; 31import android.content.pm.ActivityInfo; 32import android.graphics.PixelFormat; 33import android.inputmethodservice.KeyboardView; 34import android.os.Bundle; 35import android.os.Handler; 36import android.text.Editable; 37import android.text.Selection; 38import android.text.Spannable; 39import android.text.TextUtils; 40import android.text.TextWatcher; 41import android.view.KeyEvent; 42import android.view.View; 43import android.view.WindowManager; 44import android.view.View.OnClickListener; 45import android.view.inputmethod.EditorInfo; 46import android.widget.Button; 47import android.widget.TextView; 48import android.widget.TextView.OnEditorActionListener; 49 50 51public class ChooseLockPassword extends Activity implements OnClickListener, OnEditorActionListener, 52 TextWatcher { 53 private static final String KEY_FIRST_PIN = "first_pin"; 54 private static final String KEY_UI_STAGE = "ui_stage"; 55 private TextView mPasswordEntry; 56 private int mPasswordMinLength = 4; 57 private int mPasswordMaxLength = 16; 58 private LockPatternUtils mLockPatternUtils; 59 private int mRequestedMode = LockPatternUtils.MODE_PIN; 60 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 61 private com.android.settings.ChooseLockPassword.Stage mUiStage = Stage.Introduction; 62 private TextView mHeaderText; 63 private String mFirstPin; 64 private KeyboardView mKeyboardView; 65 private PasswordEntryKeyboardHelper mKeyboardHelper; 66 private boolean mIsAlphaMode; 67 private Button mCancelButton; 68 private Button mNextButton; 69 public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; 70 public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; 71 private static Handler mHandler = new Handler(); 72 private static final int CONFIRM_EXISTING_REQUEST = 58; 73 static final int RESULT_FINISHED = RESULT_FIRST_USER; 74 private static final long ERROR_MESSAGE_TIMEOUT = 3000; 75 76 /** 77 * Keep track internally of where the user is in choosing a pattern. 78 */ 79 protected enum Stage { 80 81 Introduction(R.string.lockpassword_choose_your_password_header, 82 R.string.lockpassword_choose_your_pin_header, 83 R.string.lockpassword_continue_label), 84 85 NeedToConfirm(R.string.lockpassword_confirm_your_password_header, 86 R.string.lockpassword_confirm_your_pin_header, 87 R.string.lockpassword_ok_label), 88 89 ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match, 90 R.string.lockpassword_confirm_pins_dont_match, 91 R.string.lockpassword_continue_label); 92 93 /** 94 * @param headerMessage The message displayed at the top. 95 */ 96 Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) { 97 this.alphaHint = hintInAlpha; 98 this.numericHint = hintInNumeric; 99 this.buttonText = nextButtonText; 100 } 101 102 public final int alphaHint; 103 public final int numericHint; 104 public final int buttonText; 105 } 106 107 @Override 108 protected void onCreate(Bundle savedInstanceState) { 109 super.onCreate(savedInstanceState); 110 mLockPatternUtils = new LockPatternUtils(this); 111 mRequestedMode = getIntent().getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedMode); 112 mPasswordMinLength = getIntent().getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength); 113 mPasswordMaxLength = getIntent().getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength); 114 int minMode = mLockPatternUtils.getRequestedPasswordMode(); 115 if (mRequestedMode < minMode) { 116 mRequestedMode = minMode; 117 } 118 int minLength = mLockPatternUtils.getRequestedMinimumPasswordLength(); 119 if (mPasswordMinLength < minLength) { 120 mPasswordMinLength = minLength; 121 } 122 initViews(); 123 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this); 124 if (savedInstanceState == null) { 125 updateStage(Stage.Introduction); 126 mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST); 127 } 128 } 129 130 private void initViews() { 131 setContentView(R.layout.choose_lock_password); 132 // Disable IME on our window since we provide our own keyboard 133 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 134 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 135 136 mCancelButton = (Button) findViewById(R.id.cancel_button); 137 mCancelButton.setOnClickListener(this); 138 mNextButton = (Button) findViewById(R.id.next_button); 139 mNextButton.setOnClickListener(this); 140 141 mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); 142 mPasswordEntry = (TextView) findViewById(R.id.password_entry); 143 mPasswordEntry.setOnEditorActionListener(this); 144 mPasswordEntry.addTextChangedListener(this); 145 146 mIsAlphaMode = LockPatternUtils.MODE_PASSWORD == mRequestedMode; 147 mKeyboardHelper = new PasswordEntryKeyboardHelper(this, mKeyboardView, mPasswordEntry); 148 mKeyboardHelper.setKeyboardMode(mIsAlphaMode ? 149 PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA 150 : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); 151 152 mHeaderText = (TextView) findViewById(R.id.headerText); 153 mKeyboardView.requestFocus(); 154 } 155 156 @Override 157 protected void onResume() { 158 super.onResume(); 159 updateStage(mUiStage); 160 mKeyboardView.requestFocus(); 161 } 162 163 @Override 164 protected void onSaveInstanceState(Bundle outState) { 165 super.onSaveInstanceState(outState); 166 outState.putString(KEY_UI_STAGE, mUiStage.name()); 167 outState.putString(KEY_FIRST_PIN, mFirstPin); 168 } 169 170 @Override 171 protected void onRestoreInstanceState(Bundle savedInstanceState) { 172 super.onRestoreInstanceState(savedInstanceState); 173 String state = savedInstanceState.getString(KEY_UI_STAGE); 174 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN); 175 if (state != null) { 176 mUiStage = Stage.valueOf(state); 177 updateStage(mUiStage); 178 } 179 } 180 181 @Override 182 protected void onActivityResult(int requestCode, int resultCode, 183 Intent data) { 184 super.onActivityResult(requestCode, resultCode, data); 185 switch (requestCode) { 186 case CONFIRM_EXISTING_REQUEST: 187 if (resultCode != Activity.RESULT_OK) { 188 setResult(RESULT_FINISHED); 189 finish(); 190 } 191 break; 192 } 193 } 194 195 protected void updateStage(Stage stage) { 196 mUiStage = stage; 197 updateUi(); 198 } 199 200 /** 201 * Validates PIN and returns a message to display if PIN fails test. 202 * @param pin 203 * @return message id to display to user 204 */ 205 private String validatePassword(String pin) { 206 if (pin.length() < mPasswordMinLength) { 207 return getString(mIsAlphaMode ? 208 R.string.lockpassword_password_too_short 209 : R.string.lockpassword_pin_too_short, mPasswordMinLength); 210 } 211 if (pin.length() > mPasswordMaxLength) { 212 return getString(mIsAlphaMode ? 213 R.string.lockpassword_password_too_long 214 : R.string.lockpassword_pin_too_long, mPasswordMaxLength); 215 } 216 boolean hasAlpha = false; 217 boolean hasDigit = false; 218 boolean hasSymbol = false; 219 for (int i = 0; i < pin.length(); i++) { 220 char c = pin.charAt(i); 221 // allow non white space Latin-1 characters only 222 if (c <= 32 || c > 127) { 223 return getString(R.string.lockpassword_illegal_character); 224 } 225 if (c >= '0' && c <= '9') { 226 hasDigit = true; 227 } else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { 228 hasAlpha = true; 229 } else { 230 hasSymbol = true; 231 } 232 } 233 if (LockPatternUtils.MODE_PIN == mRequestedMode && (hasAlpha | hasSymbol)) { 234 return getString(R.string.lockpassword_pin_contains_non_digits); 235 } else if (LockPatternUtils.MODE_PASSWORD == mRequestedMode && !hasAlpha) { 236 // require at least 1 alpha character 237 return getString(R.string.lockpassword_password_requires_alpha); 238 } 239 return null; 240 } 241 242 private void handleNext() { 243 final String pin = mPasswordEntry.getText().toString(); 244 if (TextUtils.isEmpty(pin)) { 245 return; 246 } 247 String errorMsg = null; 248 if (mUiStage == Stage.Introduction) { 249 errorMsg = validatePassword(pin); 250 if (errorMsg == null) { 251 mFirstPin = pin; 252 updateStage(Stage.NeedToConfirm); 253 mPasswordEntry.setText(""); 254 } 255 } else if (mUiStage == Stage.NeedToConfirm) { 256 if (mFirstPin.equals(pin)) { 257 mLockPatternUtils.clearLock(); 258 mLockPatternUtils.saveLockPassword(pin, mRequestedMode); 259 finish(); 260 } else { 261 updateStage(Stage.ConfirmWrong); 262 CharSequence tmp = mPasswordEntry.getText(); 263 if (tmp != null) { 264 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 265 } 266 } 267 } 268 if (errorMsg != null) { 269 showError(errorMsg, mUiStage); 270 } 271 } 272 273 public void onClick(View v) { 274 switch (v.getId()) { 275 case R.id.next_button: 276 handleNext(); 277 break; 278 279 case R.id.cancel_button: 280 finish(); 281 break; 282 } 283 } 284 285 private void showError(String msg, final Stage next) { 286 mHeaderText.setText(msg); 287 mHandler.postDelayed(new Runnable() { 288 public void run() { 289 updateStage(next); 290 } 291 }, ERROR_MESSAGE_TIMEOUT); 292 } 293 294 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 295 // Check if this was the result of hitting the enter key 296 if (actionId == EditorInfo.IME_NULL) { 297 handleNext(); 298 return true; 299 } 300 return false; 301 } 302 303 /** 304 * Update the hint based on current Stage and length of password entry 305 */ 306 private void updateUi() { 307 final int length = mPasswordEntry.getText().toString().length(); 308 if (mUiStage == Stage.Introduction && length > 0) { 309 if (length < mPasswordMinLength) { 310 String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short 311 : R.string.lockpassword_pin_too_short, mPasswordMinLength); 312 mHeaderText.setText(msg); 313 mNextButton.setEnabled(false); 314 } else { 315 mHeaderText.setText(R.string.lockpassword_press_continue); 316 mNextButton.setEnabled(true); 317 } 318 } else { 319 mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint); 320 mNextButton.setEnabled(length > 0); 321 } 322 mNextButton.setText(mUiStage.buttonText); 323 } 324 325 public void afterTextChanged(Editable s) { 326 // Changing the text while error displayed resets to NeedToConfirm state 327 if (mUiStage == Stage.ConfirmWrong) { 328 mUiStage = Stage.NeedToConfirm; 329 } 330 updateUi(); 331 } 332 333 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 334 335 } 336 337 public void onTextChanged(CharSequence s, int start, int before, int count) { 338 339 } 340} 341