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