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