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