KeyguardPasswordView.java revision ac8a59d3d3a6ddb32a293e4dfc74fb55a0ea6e82
1/* 2 * Copyright (C) 2012 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.internal.policy.impl.keyguard; 18 19import android.content.Context; 20import android.util.AttributeSet; 21import android.view.View; 22 23import com.android.internal.R; 24import com.android.internal.widget.LockPatternUtils; 25import java.util.List; 26 27import android.app.admin.DevicePolicyManager; 28import android.content.res.Configuration; 29import android.graphics.Rect; 30 31import com.android.internal.widget.PasswordEntryKeyboardView; 32 33import android.os.CountDownTimer; 34import android.os.SystemClock; 35import android.text.Editable; 36import android.text.InputType; 37import android.text.TextWatcher; 38import android.text.method.DigitsKeyListener; 39import android.text.method.TextKeyListener; 40import android.view.KeyEvent; 41import android.view.inputmethod.EditorInfo; 42import android.view.inputmethod.InputMethodInfo; 43import android.view.inputmethod.InputMethodManager; 44import android.view.inputmethod.InputMethodSubtype; 45import android.widget.EditText; 46import android.widget.LinearLayout; 47import android.widget.TextView; 48import android.widget.TextView.OnEditorActionListener; 49 50import com.android.internal.widget.PasswordEntryKeyboardHelper; 51/** 52 * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter 53 * an unlock password 54 */ 55 56public class KeyguardPasswordView extends LinearLayout 57 implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { 58 private KeyguardSecurityCallback mCallback; 59 private EditText mPasswordEntry; 60 private LockPatternUtils mLockPatternUtils; 61 private PasswordEntryKeyboardView mKeyboardView; 62 private PasswordEntryKeyboardHelper mKeyboardHelper; 63 private boolean mIsAlpha; 64 private SecurityMessageDisplay mSecurityMessageDisplay; 65 66 // To avoid accidental lockout due to events while the device in in the pocket, ignore 67 // any passwords with length less than or equal to this length. 68 private static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; 69 70 // Enable this if we want to hide the on-screen PIN keyboard when a physical one is showing 71 private static final boolean ENABLE_HIDE_KEYBOARD = false; 72 73 public KeyguardPasswordView(Context context) { 74 super(context); 75 } 76 77 public KeyguardPasswordView(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 } 80 81 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 82 mCallback = callback; 83 } 84 85 public void setLockPatternUtils(LockPatternUtils utils) { 86 mLockPatternUtils = utils; 87 } 88 89 @Override 90 public void onWindowFocusChanged(boolean hasWindowFocus) { 91 if (hasWindowFocus) { 92 reset(); 93 } 94 } 95 96 public void reset() { 97 // start fresh 98 mPasswordEntry.setText(""); 99 mPasswordEntry.requestFocus(); 100 101 // if the user is currently locked out, enforce it. 102 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); 103 if (deadline != 0) { 104 handleAttemptLockout(deadline); 105 } else { 106 resetState(); 107 } 108 } 109 110 private void resetState() { 111 mSecurityMessageDisplay.setMessage( 112 mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions, false); 113 mPasswordEntry.setEnabled(true); 114 mKeyboardView.setEnabled(true); 115 } 116 117 @Override 118 protected void onFinishInflate() { 119 // We always set a dummy NavigationManager to avoid null checks 120 mSecurityMessageDisplay = new KeyguardNavigationManager(null); 121 122 mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one 123 124 final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(); 125 mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality 126 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality 127 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality; 128 129 mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); 130 mPasswordEntry = (EditText) findViewById(R.id.passwordEntry); 131 mPasswordEntry.setOnEditorActionListener(this); 132 mPasswordEntry.addTextChangedListener(this); 133 134 mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false, 135 new int[] { 136 R.xml.kg_password_kbd_numeric, 137 com.android.internal.R.xml.password_kbd_qwerty, 138 com.android.internal.R.xml.password_kbd_qwerty_shifted, 139 com.android.internal.R.xml.password_kbd_symbols, 140 com.android.internal.R.xml.password_kbd_symbols_shift 141 } 142 ); 143 mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); 144 145 boolean imeOrDeleteButtonVisible = false; 146 if (mIsAlpha) { 147 // We always use the system IME for alpha keyboard, so hide lockscreen's soft keyboard 148 mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); 149 mKeyboardView.setVisibility(View.GONE); 150 } else { 151 mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); 152 153 // Use lockscreen's numeric keyboard if the physical keyboard isn't showing 154 boolean hardKeyboardVisible = getResources().getConfiguration().hardKeyboardHidden 155 == Configuration.HARDKEYBOARDHIDDEN_NO; 156 mKeyboardView.setVisibility( 157 (ENABLE_HIDE_KEYBOARD && hardKeyboardVisible) ? View.INVISIBLE : View.VISIBLE); 158 159 // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts, 160 // not a separate view 161 View pinDelete = findViewById(R.id.delete_button); 162 if (pinDelete != null) { 163 pinDelete.setVisibility(View.VISIBLE); 164 imeOrDeleteButtonVisible = true; 165 pinDelete.setOnClickListener(new OnClickListener() { 166 public void onClick(View v) { 167 mKeyboardHelper.handleBackspace(); 168 } 169 }); 170 } 171 } 172 173 mPasswordEntry.requestFocus(); 174 175 // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys. 176 if (mIsAlpha) { 177 mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); 178 mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT 179 | InputType.TYPE_TEXT_VARIATION_PASSWORD); 180 } else { 181 mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance()); 182 mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER 183 | InputType.TYPE_NUMBER_VARIATION_PASSWORD); 184 } 185 186 // Poke the wakelock any time the text is selected or modified 187 mPasswordEntry.setOnClickListener(new OnClickListener() { 188 public void onClick(View v) { 189 mCallback.userActivity(0); // TODO: customize timeout for text? 190 } 191 }); 192 193 mPasswordEntry.addTextChangedListener(new TextWatcher() { 194 public void onTextChanged(CharSequence s, int start, int before, int count) { 195 } 196 197 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 198 } 199 200 public void afterTextChanged(Editable s) { 201 if (mCallback != null) { 202 mCallback.userActivity(0); 203 } 204 } 205 }); 206 207 // If there's more than one IME, enable the IME switcher button 208 View switchImeButton = findViewById(R.id.switch_ime_button); 209 final InputMethodManager imm = (InputMethodManager) getContext().getSystemService( 210 Context.INPUT_METHOD_SERVICE); 211 if (mIsAlpha && switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) { 212 switchImeButton.setVisibility(View.VISIBLE); 213 imeOrDeleteButtonVisible = true; 214 switchImeButton.setOnClickListener(new OnClickListener() { 215 public void onClick(View v) { 216 mCallback.userActivity(0); // Leave the screen on a bit longer 217 imm.showInputMethodPicker(); 218 } 219 }); 220 } 221 222 // If no icon is visible, reset the start margin on the password field so the text is 223 // still centered. 224 if (!imeOrDeleteButtonVisible) { 225 android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); 226 if (params instanceof MarginLayoutParams) { 227 final MarginLayoutParams mlp = (MarginLayoutParams) params; 228 mlp.setMarginStart(0); 229 mPasswordEntry.setLayoutParams(params); 230 } 231 } 232 } 233 234 /** 235 * Method adapted from com.android.inputmethod.latin.Utils 236 * 237 * @param imm The input method manager 238 * @param shouldIncludeAuxiliarySubtypes 239 * @return true if we have multiple IMEs to choose from 240 */ 241 private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, 242 final boolean shouldIncludeAuxiliarySubtypes) { 243 final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); 244 245 // Number of the filtered IMEs 246 int filteredImisCount = 0; 247 248 for (InputMethodInfo imi : enabledImis) { 249 // We can return true immediately after we find two or more filtered IMEs. 250 if (filteredImisCount > 1) return true; 251 final List<InputMethodSubtype> subtypes = 252 imm.getEnabledInputMethodSubtypeList(imi, true); 253 // IMEs that have no subtypes should be counted. 254 if (subtypes.isEmpty()) { 255 ++filteredImisCount; 256 continue; 257 } 258 259 int auxCount = 0; 260 for (InputMethodSubtype subtype : subtypes) { 261 if (subtype.isAuxiliary()) { 262 ++auxCount; 263 } 264 } 265 final int nonAuxCount = subtypes.size() - auxCount; 266 267 // IMEs that have one or more non-auxiliary subtypes should be counted. 268 // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary 269 // subtypes should be counted as well. 270 if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { 271 ++filteredImisCount; 272 continue; 273 } 274 } 275 276 return filteredImisCount > 1 277 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled 278 // input method subtype (The current IME should be LatinIME.) 279 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; 280 } 281 282 @Override 283 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 284 // send focus to the password field 285 return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); 286 } 287 288 private void verifyPasswordAndUnlock() { 289 String entry = mPasswordEntry.getText().toString(); 290 if (mLockPatternUtils.checkPassword(entry)) { 291 mCallback.reportSuccessfulUnlockAttempt(); 292 mCallback.dismiss(true); 293 } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { 294 // to avoid accidental lockout, only count attempts that are long enough to be a 295 // real password. This may require some tweaking. 296 mCallback.reportFailedUnlockAttempt(); 297 if (0 == (mCallback.getFailedAttempts() 298 % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { 299 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); 300 handleAttemptLockout(deadline); 301 } 302 mSecurityMessageDisplay.setMessage( 303 mIsAlpha ? R.string.kg_wrong_password : R.string.kg_wrong_pin, true); 304 } 305 mPasswordEntry.setText(""); 306 } 307 308 // Prevent user from using the PIN/Password entry until scheduled deadline. 309 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 310 mPasswordEntry.setEnabled(false); 311 mKeyboardView.setEnabled(false); 312 long elapsedRealtime = SystemClock.elapsedRealtime(); 313 new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { 314 315 @Override 316 public void onTick(long millisUntilFinished) { 317 int secondsRemaining = (int) (millisUntilFinished / 1000); 318 mSecurityMessageDisplay.setMessage( 319 R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining); 320 } 321 322 @Override 323 public void onFinish() { 324 resetState(); 325 } 326 }.start(); 327 } 328 329 @Override 330 public boolean onKeyDown(int keyCode, KeyEvent event) { 331 mCallback.userActivity(0); 332 return false; 333 } 334 335 @Override 336 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 337 // Check if this was the result of hitting the enter key 338 if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE 339 || actionId == EditorInfo.IME_ACTION_NEXT) { 340 verifyPasswordAndUnlock(); 341 return true; 342 } 343 return false; 344 } 345 346 @Override 347 public boolean needsInput() { 348 return mIsAlpha; 349 } 350 351 @Override 352 public void onPause() { 353 354 } 355 356 @Override 357 public void onResume() { 358 reset(); 359 } 360 361 @Override 362 public KeyguardSecurityCallback getCallback() { 363 return mCallback; 364 } 365 366 @Override 367 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 368 if (mCallback != null) { 369 mCallback.userActivity(KeyguardViewManager.DIGIT_PRESS_WAKE_MILLIS); 370 } 371 } 372 373 @Override 374 public void onTextChanged(CharSequence s, int start, int before, int count) { 375 } 376 377 @Override 378 public void afterTextChanged(Editable s) { 379 } 380 381 @Override 382 public void setSecurityMessageDisplay(SecurityMessageDisplay display) { 383 mSecurityMessageDisplay = display; 384 reset(); 385 } 386} 387 388