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