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