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.TextWatcher; 24 import android.text.method.TextKeyListener; 25 import android.util.AttributeSet; 26 import android.view.KeyEvent; 27 import android.view.View; 28 import android.view.animation.AnimationUtils; 29 import android.view.animation.Interpolator; 30 import android.view.inputmethod.EditorInfo; 31 import android.view.inputmethod.InputMethodInfo; 32 import android.view.inputmethod.InputMethodManager; 33 import android.view.inputmethod.InputMethodSubtype; 34 import android.widget.TextView; 35 import android.widget.TextView.OnEditorActionListener; 36 37 import java.util.List; 38 /** 39 * Displays an alphanumeric (latin-1) key entry for the user to enter 40 * an unlock password 41 */ 42 43 public class KeyguardPasswordView extends KeyguardAbsKeyInputView 44 implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { 45 46 private final boolean mShowImeAtScreenOn; 47 private final int mDisappearYTranslation; 48 49 InputMethodManager mImm; 50 private TextView mPasswordEntry; 51 private Interpolator mLinearOutSlowInInterpolator; 52 private Interpolator mFastOutLinearInInterpolator; 53 KeyguardPasswordView(Context context)54 public KeyguardPasswordView(Context context) { 55 this(context, null); 56 } 57 KeyguardPasswordView(Context context, AttributeSet attrs)58 public KeyguardPasswordView(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 mShowImeAtScreenOn = context.getResources(). 61 getBoolean(R.bool.kg_show_ime_at_screen_on); 62 mDisappearYTranslation = getResources().getDimensionPixelSize( 63 R.dimen.disappear_y_translation); 64 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( 65 context, android.R.interpolator.linear_out_slow_in); 66 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( 67 context, android.R.interpolator.fast_out_linear_in); 68 } 69 resetState()70 protected void resetState() { 71 mSecurityMessageDisplay.setMessage(R.string.kg_password_instructions, false); 72 mPasswordEntry.setEnabled(true); 73 } 74 75 @Override getPasswordTextViewId()76 protected int getPasswordTextViewId() { 77 return R.id.passwordEntry; 78 } 79 80 @Override needsInput()81 public boolean needsInput() { 82 return true; 83 } 84 85 @Override onResume(final int reason)86 public void onResume(final int reason) { 87 super.onResume(reason); 88 89 // Wait a bit to focus the field so the focusable flag on the window is already set then. 90 post(new Runnable() { 91 @Override 92 public void run() { 93 if (isShown()) { 94 mPasswordEntry.requestFocus(); 95 if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { 96 mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); 97 } 98 } 99 } 100 }); 101 } 102 103 @Override onPause()104 public void onPause() { 105 super.onPause(); 106 mImm.hideSoftInputFromWindow(getWindowToken(), 0); 107 } 108 109 @Override reset()110 public void reset() { 111 super.reset(); 112 mPasswordEntry.requestFocus(); 113 } 114 115 @Override onFinishInflate()116 protected void onFinishInflate() { 117 super.onFinishInflate(); 118 119 boolean imeOrDeleteButtonVisible = false; 120 121 mImm = (InputMethodManager) getContext().getSystemService( 122 Context.INPUT_METHOD_SERVICE); 123 124 mPasswordEntry = (TextView) findViewById(getPasswordTextViewId()); 125 mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); 126 mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT 127 | InputType.TYPE_TEXT_VARIATION_PASSWORD); 128 mPasswordEntry.setOnEditorActionListener(this); 129 mPasswordEntry.addTextChangedListener(this); 130 131 // Poke the wakelock any time the text is selected or modified 132 mPasswordEntry.setOnClickListener(new OnClickListener() { 133 public void onClick(View v) { 134 mCallback.userActivity(); 135 } 136 }); 137 138 // Set selected property on so the view can send accessibility events. 139 mPasswordEntry.setSelected(true); 140 141 mPasswordEntry.addTextChangedListener(new TextWatcher() { 142 public void onTextChanged(CharSequence s, int start, int before, int count) { 143 } 144 145 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 146 } 147 148 public void afterTextChanged(Editable s) { 149 if (mCallback != null) { 150 mCallback.userActivity(); 151 } 152 } 153 }); 154 155 mPasswordEntry.requestFocus(); 156 157 // If there's more than one IME, enable the IME switcher button 158 View switchImeButton = findViewById(R.id.switch_ime_button); 159 if (switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(mImm, false)) { 160 switchImeButton.setVisibility(View.VISIBLE); 161 imeOrDeleteButtonVisible = true; 162 switchImeButton.setOnClickListener(new OnClickListener() { 163 public void onClick(View v) { 164 mCallback.userActivity(); // Leave the screen on a bit longer 165 mImm.showInputMethodPicker(); 166 } 167 }); 168 } 169 170 // If no icon is visible, reset the start margin on the password field so the text is 171 // still centered. 172 if (!imeOrDeleteButtonVisible) { 173 android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); 174 if (params instanceof MarginLayoutParams) { 175 final MarginLayoutParams mlp = (MarginLayoutParams) params; 176 mlp.setMarginStart(0); 177 mPasswordEntry.setLayoutParams(params); 178 } 179 } 180 } 181 182 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)183 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 184 // send focus to the password field 185 return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); 186 } 187 188 @Override resetPasswordText(boolean animate)189 protected void resetPasswordText(boolean animate) { 190 mPasswordEntry.setText(""); 191 } 192 193 @Override getPasswordText()194 protected String getPasswordText() { 195 return mPasswordEntry.getText().toString(); 196 } 197 198 @Override setPasswordEntryEnabled(boolean enabled)199 protected void setPasswordEntryEnabled(boolean enabled) { 200 mPasswordEntry.setEnabled(enabled); 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 */ hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, final boolean shouldIncludeAuxiliarySubtypes)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 showUsabilityHint()252 public void showUsabilityHint() { 253 } 254 255 @Override getWrongPasswordStringId()256 public int getWrongPasswordStringId() { 257 return R.string.kg_wrong_password; 258 } 259 260 @Override startAppearAnimation()261 public void startAppearAnimation() { 262 setAlpha(0f); 263 setTranslationY(0f); 264 animate() 265 .alpha(1) 266 .withLayer() 267 .setDuration(300) 268 .setInterpolator(mLinearOutSlowInInterpolator); 269 } 270 271 @Override startDisappearAnimation(Runnable finishRunnable)272 public boolean startDisappearAnimation(Runnable finishRunnable) { 273 animate() 274 .alpha(0f) 275 .translationY(mDisappearYTranslation) 276 .setInterpolator(mFastOutLinearInInterpolator) 277 .setDuration(100) 278 .withEndAction(finishRunnable); 279 return true; 280 } 281 282 @Override beforeTextChanged(CharSequence s, int start, int count, int after)283 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 284 if (mCallback != null) { 285 mCallback.userActivity(); 286 } 287 } 288 289 @Override onTextChanged(CharSequence s, int start, int before, int count)290 public void onTextChanged(CharSequence s, int start, int before, int count) { 291 } 292 293 @Override afterTextChanged(Editable s)294 public void afterTextChanged(Editable s) { 295 } 296 297 @Override onEditorAction(TextView v, int actionId, KeyEvent event)298 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 299 // Check if this was the result of hitting the enter key 300 final boolean isSoftImeEvent = event == null 301 && (actionId == EditorInfo.IME_NULL 302 || actionId == EditorInfo.IME_ACTION_DONE 303 || actionId == EditorInfo.IME_ACTION_NEXT); 304 final boolean isKeyboardEnterKey = event != null 305 && KeyEvent.isConfirmKey(event.getKeyCode()) 306 && event.getAction() == KeyEvent.ACTION_DOWN; 307 if (isSoftImeEvent || isKeyboardEnterKey) { 308 verifyPasswordAndUnlock(); 309 return true; 310 } 311 return false; 312 } 313 } 314