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 17 package com.android.keyguard; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.text.Editable; 22 import android.text.InputType; 23 import android.text.TextUtils; 24 import android.text.TextWatcher; 25 import android.text.method.TextKeyListener; 26 import android.util.AttributeSet; 27 import android.view.KeyEvent; 28 import android.view.View; 29 import android.view.animation.AnimationUtils; 30 import android.view.animation.Interpolator; 31 import android.view.inputmethod.EditorInfo; 32 import android.view.inputmethod.InputMethodInfo; 33 import android.view.inputmethod.InputMethodManager; 34 import android.view.inputmethod.InputMethodSubtype; 35 import android.widget.TextView; 36 import android.widget.TextView.OnEditorActionListener; 37 38 import com.android.internal.widget.TextViewInputDisabler; 39 40 import java.util.List; 41 /** 42 * Displays an alphanumeric (latin-1) key entry for the user to enter 43 * an unlock password 44 */ 45 46 public class KeyguardPasswordView extends KeyguardAbsKeyInputView 47 implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { 48 49 private final boolean mShowImeAtScreenOn; 50 private final int mDisappearYTranslation; 51 52 InputMethodManager mImm; 53 private TextView mPasswordEntry; 54 private TextViewInputDisabler mPasswordEntryDisabler; 55 56 private Interpolator mLinearOutSlowInInterpolator; 57 private Interpolator mFastOutLinearInInterpolator; 58 KeyguardPasswordView(Context context)59 public KeyguardPasswordView(Context context) { 60 this(context, null); 61 } 62 KeyguardPasswordView(Context context, AttributeSet attrs)63 public KeyguardPasswordView(Context context, AttributeSet attrs) { 64 super(context, attrs); 65 mShowImeAtScreenOn = context.getResources(). 66 getBoolean(R.bool.kg_show_ime_at_screen_on); 67 mDisappearYTranslation = getResources().getDimensionPixelSize( 68 R.dimen.disappear_y_translation); 69 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( 70 context, android.R.interpolator.linear_out_slow_in); 71 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( 72 context, android.R.interpolator.fast_out_linear_in); 73 } 74 75 @Override resetState()76 protected void resetState() { 77 mSecurityMessageDisplay.setMessage(R.string.kg_password_instructions, false); 78 final boolean wasDisabled = mPasswordEntry.isEnabled(); 79 setPasswordEntryEnabled(true); 80 setPasswordEntryInputEnabled(true); 81 if (wasDisabled) { 82 mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); 83 } 84 } 85 86 @Override getPasswordTextViewId()87 protected int getPasswordTextViewId() { 88 return R.id.passwordEntry; 89 } 90 91 @Override needsInput()92 public boolean needsInput() { 93 return true; 94 } 95 96 @Override onResume(final int reason)97 public void onResume(final int reason) { 98 super.onResume(reason); 99 100 // Wait a bit to focus the field so the focusable flag on the window is already set then. 101 post(new Runnable() { 102 @Override 103 public void run() { 104 if (isShown() && mPasswordEntry.isEnabled()) { 105 mPasswordEntry.requestFocus(); 106 if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { 107 mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); 108 } 109 } 110 } 111 }); 112 } 113 114 @Override getPromtReasonStringRes(int reason)115 protected int getPromtReasonStringRes(int reason) { 116 switch (reason) { 117 case PROMPT_REASON_RESTART: 118 return R.string.kg_prompt_reason_restart_password; 119 case PROMPT_REASON_TIMEOUT: 120 return R.string.kg_prompt_reason_timeout_password; 121 case PROMPT_REASON_DEVICE_ADMIN: 122 return R.string.kg_prompt_reason_device_admin; 123 case PROMPT_REASON_USER_REQUEST: 124 return R.string.kg_prompt_reason_user_request; 125 case PROMPT_REASON_NONE: 126 return 0; 127 default: 128 return R.string.kg_prompt_reason_timeout_password; 129 } 130 } 131 132 @Override onPause()133 public void onPause() { 134 super.onPause(); 135 mImm.hideSoftInputFromWindow(getWindowToken(), 0); 136 } 137 138 @Override reset()139 public void reset() { 140 super.reset(); 141 mPasswordEntry.requestFocus(); 142 } 143 144 @Override onFinishInflate()145 protected void onFinishInflate() { 146 super.onFinishInflate(); 147 148 boolean imeOrDeleteButtonVisible = false; 149 150 mImm = (InputMethodManager) getContext().getSystemService( 151 Context.INPUT_METHOD_SERVICE); 152 153 mPasswordEntry = (TextView) findViewById(getPasswordTextViewId()); 154 mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); 155 mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); 156 mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT 157 | InputType.TYPE_TEXT_VARIATION_PASSWORD); 158 mPasswordEntry.setOnEditorActionListener(this); 159 mPasswordEntry.addTextChangedListener(this); 160 161 // Poke the wakelock any time the text is selected or modified 162 mPasswordEntry.setOnClickListener(new OnClickListener() { 163 @Override 164 public void onClick(View v) { 165 mCallback.userActivity(); 166 } 167 }); 168 169 // Set selected property on so the view can send accessibility events. 170 mPasswordEntry.setSelected(true); 171 172 mPasswordEntry.requestFocus(); 173 174 // If there's more than one IME, enable the IME switcher button 175 View switchImeButton = findViewById(R.id.switch_ime_button); 176 if (switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(mImm, false)) { 177 switchImeButton.setVisibility(View.VISIBLE); 178 imeOrDeleteButtonVisible = true; 179 switchImeButton.setOnClickListener(new OnClickListener() { 180 @Override 181 public void onClick(View v) { 182 mCallback.userActivity(); // Leave the screen on a bit longer 183 // Do not show auxiliary subtypes in password lock screen. 184 mImm.showInputMethodPicker(false /* showAuxiliarySubtypes */); 185 } 186 }); 187 } 188 189 // If no icon is visible, reset the start margin on the password field so the text is 190 // still centered. 191 if (!imeOrDeleteButtonVisible) { 192 android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); 193 if (params instanceof MarginLayoutParams) { 194 final MarginLayoutParams mlp = (MarginLayoutParams) params; 195 mlp.setMarginStart(0); 196 mPasswordEntry.setLayoutParams(params); 197 } 198 } 199 } 200 201 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)202 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 203 // send focus to the password field 204 return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); 205 } 206 207 @Override resetPasswordText(boolean animate, boolean announce)208 protected void resetPasswordText(boolean animate, boolean announce) { 209 mPasswordEntry.setText(""); 210 } 211 212 @Override getPasswordText()213 protected String getPasswordText() { 214 return mPasswordEntry.getText().toString(); 215 } 216 217 @Override setPasswordEntryEnabled(boolean enabled)218 protected void setPasswordEntryEnabled(boolean enabled) { 219 mPasswordEntry.setEnabled(enabled); 220 } 221 222 @Override setPasswordEntryInputEnabled(boolean enabled)223 protected void setPasswordEntryInputEnabled(boolean enabled) { 224 mPasswordEntryDisabler.setInputEnabled(enabled); 225 } 226 227 /** 228 * Method adapted from com.android.inputmethod.latin.Utils 229 * 230 * @param imm The input method manager 231 * @param shouldIncludeAuxiliarySubtypes 232 * @return true if we have multiple IMEs to choose from 233 */ hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, final boolean shouldIncludeAuxiliarySubtypes)234 private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, 235 final boolean shouldIncludeAuxiliarySubtypes) { 236 final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); 237 238 // Number of the filtered IMEs 239 int filteredImisCount = 0; 240 241 for (InputMethodInfo imi : enabledImis) { 242 // We can return true immediately after we find two or more filtered IMEs. 243 if (filteredImisCount > 1) return true; 244 final List<InputMethodSubtype> subtypes = 245 imm.getEnabledInputMethodSubtypeList(imi, true); 246 // IMEs that have no subtypes should be counted. 247 if (subtypes.isEmpty()) { 248 ++filteredImisCount; 249 continue; 250 } 251 252 int auxCount = 0; 253 for (InputMethodSubtype subtype : subtypes) { 254 if (subtype.isAuxiliary()) { 255 ++auxCount; 256 } 257 } 258 final int nonAuxCount = subtypes.size() - auxCount; 259 260 // IMEs that have one or more non-auxiliary subtypes should be counted. 261 // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary 262 // subtypes should be counted as well. 263 if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { 264 ++filteredImisCount; 265 continue; 266 } 267 } 268 269 return filteredImisCount > 1 270 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled 271 // input method subtype (The current IME should be LatinIME.) 272 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; 273 } 274 275 @Override showUsabilityHint()276 public void showUsabilityHint() { 277 } 278 279 @Override getWrongPasswordStringId()280 public int getWrongPasswordStringId() { 281 return R.string.kg_wrong_password; 282 } 283 284 @Override startAppearAnimation()285 public void startAppearAnimation() { 286 setAlpha(0f); 287 setTranslationY(0f); 288 animate() 289 .alpha(1) 290 .withLayer() 291 .setDuration(300) 292 .setInterpolator(mLinearOutSlowInInterpolator); 293 } 294 295 @Override startDisappearAnimation(Runnable finishRunnable)296 public boolean startDisappearAnimation(Runnable finishRunnable) { 297 animate() 298 .alpha(0f) 299 .translationY(mDisappearYTranslation) 300 .setInterpolator(mFastOutLinearInInterpolator) 301 .setDuration(100) 302 .withEndAction(finishRunnable); 303 return true; 304 } 305 306 @Override beforeTextChanged(CharSequence s, int start, int count, int after)307 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 308 if (mCallback != null) { 309 mCallback.userActivity(); 310 } 311 } 312 313 @Override onTextChanged(CharSequence s, int start, int before, int count)314 public void onTextChanged(CharSequence s, int start, int before, int count) { 315 } 316 317 @Override afterTextChanged(Editable s)318 public void afterTextChanged(Editable s) { 319 // Poor man's user edit detection, assuming empty text is programmatic and everything else 320 // is from the user. 321 if (!TextUtils.isEmpty(s)) { 322 onUserInput(); 323 } 324 } 325 326 @Override onEditorAction(TextView v, int actionId, KeyEvent event)327 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 328 // Check if this was the result of hitting the enter key 329 final boolean isSoftImeEvent = event == null 330 && (actionId == EditorInfo.IME_NULL 331 || actionId == EditorInfo.IME_ACTION_DONE 332 || actionId == EditorInfo.IME_ACTION_NEXT); 333 final boolean isKeyboardEnterKey = event != null 334 && KeyEvent.isConfirmKey(event.getKeyCode()) 335 && event.getAction() == KeyEvent.ACTION_DOWN; 336 if (isSoftImeEvent || isKeyboardEnterKey) { 337 verifyPasswordAndUnlock(); 338 return true; 339 } 340 return false; 341 } 342 } 343