• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.res.ColorStateList;
20 import android.content.res.Resources;
21 import android.os.UserHandle;
22 import android.text.Editable;
23 import android.text.InputType;
24 import android.text.TextUtils;
25 import android.text.TextWatcher;
26 import android.text.method.TextKeyListener;
27 import android.view.KeyEvent;
28 import android.view.View;
29 import android.view.ViewGroup.MarginLayoutParams;
30 import android.view.WindowInsets;
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.EditText;
36 import android.widget.ImageView;
37 import android.widget.TextView.OnEditorActionListener;
38 
39 import com.android.internal.util.LatencyTracker;
40 import com.android.internal.widget.LockPatternUtils;
41 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
42 import com.android.settingslib.Utils;
43 import com.android.systemui.R;
44 import com.android.systemui.classifier.FalsingCollector;
45 import com.android.systemui.dagger.qualifiers.Main;
46 import com.android.systemui.util.concurrency.DelayableExecutor;
47 
48 import java.util.List;
49 
50 public class KeyguardPasswordViewController
51         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
52 
53     private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
54 
55     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
56     private final InputMethodManager mInputMethodManager;
57     private final DelayableExecutor mMainExecutor;
58     private final boolean mShowImeAtScreenOn;
59     private EditText mPasswordEntry;
60     private ImageView mSwitchImeButton;
61 
62     private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
63         // Check if this was the result of hitting the enter key
64         final boolean isSoftImeEvent = event == null
65                 && (actionId == EditorInfo.IME_NULL
66                 || actionId == EditorInfo.IME_ACTION_DONE
67                 || actionId == EditorInfo.IME_ACTION_NEXT);
68         final boolean isKeyboardEnterKey = event != null
69                 && KeyEvent.isConfirmKey(event.getKeyCode())
70                 && event.getAction() == KeyEvent.ACTION_DOWN;
71         if (isSoftImeEvent || isKeyboardEnterKey) {
72             verifyPasswordAndUnlock();
73             return true;
74         }
75         return false;
76     };
77 
78     private final TextWatcher mTextWatcher = new TextWatcher() {
79         @Override
80         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
81             mKeyguardSecurityCallback.userActivity();
82         }
83 
84         @Override
85         public void onTextChanged(CharSequence s, int start, int before, int count) {
86         }
87 
88         @Override
89         public void afterTextChanged(Editable s) {
90             if (!TextUtils.isEmpty(s)) {
91                 onUserInput();
92             }
93         }
94     };
95 
96     @Override
reloadColors()97     public void reloadColors() {
98         super.reloadColors();
99         int textColor = Utils.getColorAttr(mView.getContext(),
100                 android.R.attr.textColorPrimary).getDefaultColor();
101         mPasswordEntry.setTextColor(textColor);
102         mPasswordEntry.setHighlightColor(textColor);
103         mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(textColor));
104         mPasswordEntry.setForegroundTintList(ColorStateList.valueOf(textColor));
105         mSwitchImeButton.setImageTintList(ColorStateList.valueOf(textColor));
106     }
107 
KeyguardPasswordViewController(KeyguardPasswordView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, InputMethodManager inputMethodManager, EmergencyButtonController emergencyButtonController, @Main DelayableExecutor mainExecutor, @Main Resources resources, FalsingCollector falsingCollector)108     protected KeyguardPasswordViewController(KeyguardPasswordView view,
109             KeyguardUpdateMonitor keyguardUpdateMonitor,
110             SecurityMode securityMode,
111             LockPatternUtils lockPatternUtils,
112             KeyguardSecurityCallback keyguardSecurityCallback,
113             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
114             LatencyTracker latencyTracker,
115             InputMethodManager inputMethodManager,
116             EmergencyButtonController emergencyButtonController,
117             @Main DelayableExecutor mainExecutor,
118             @Main Resources resources,
119             FalsingCollector falsingCollector) {
120         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
121                 messageAreaControllerFactory, latencyTracker, falsingCollector,
122                 emergencyButtonController);
123         mKeyguardSecurityCallback = keyguardSecurityCallback;
124         mInputMethodManager = inputMethodManager;
125         mMainExecutor = mainExecutor;
126         mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
127         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
128         mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
129     }
130 
131     @Override
onViewAttached()132     protected void onViewAttached() {
133         super.onViewAttached();
134         mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
135         mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
136         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
137                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
138 
139         // Set selected property on so the view can send accessibility events.
140         mPasswordEntry.setSelected(true);
141         mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
142         mPasswordEntry.addTextChangedListener(mTextWatcher);
143         // Poke the wakelock any time the text is selected or modified
144         mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
145 
146         mSwitchImeButton.setOnClickListener(v -> {
147             mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
148             // Do not show auxiliary subtypes in password lock screen.
149             mInputMethodManager.showInputMethodPickerFromSystem(false,
150                     mView.getContext().getDisplayId());
151         });
152 
153         View cancelBtn = mView.findViewById(R.id.cancel_button);
154         if (cancelBtn != null) {
155             cancelBtn.setOnClickListener(view -> {
156                 mKeyguardSecurityCallback.reset();
157                 mKeyguardSecurityCallback.onCancelClicked();
158             });
159         }
160 
161         // If there's more than one IME, enable the IME switcher button
162         updateSwitchImeButton();
163 
164         // When we the current user is switching, InputMethodManagerService sometimes has not
165         // switched internal state yet here. As a quick workaround, we check the keyboard state
166         // again.
167         // TODO: Remove this workaround by ensuring such a race condition never happens.
168         mMainExecutor.executeDelayed(
169                 this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
170     }
171 
172     @Override
onViewDetached()173     protected void onViewDetached() {
174         super.onViewDetached();
175         mPasswordEntry.setOnEditorActionListener(null);
176     }
177 
178     @Override
needsInput()179     public boolean needsInput() {
180         return true;
181     }
182 
183     @Override
resetState()184     void resetState() {
185         mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
186         mMessageAreaController.setMessage("");
187         final boolean wasDisabled = mPasswordEntry.isEnabled();
188         mView.setPasswordEntryEnabled(true);
189         mView.setPasswordEntryInputEnabled(true);
190         // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
191         if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
192             return;
193         }
194         if (wasDisabled) {
195             showInput();
196         }
197     }
198 
199     @Override
onResume(int reason)200     public void onResume(int reason) {
201         super.onResume(reason);
202         if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
203             showInput();
204         }
205     }
206 
showInput()207     private void showInput() {
208         mView.post(() -> {
209             if (mView.isShown()) {
210                 mPasswordEntry.requestFocus();
211                 mInputMethodManager.showSoftInput(
212                         mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
213             }
214         });
215     }
216 
217     @Override
onPause()218     public void onPause() {
219         if (!mPasswordEntry.isVisibleToUser()) {
220             // Reset all states directly and then hide IME when the screen turned off.
221             super.onPause();
222         } else {
223             // In order not to break the IME hide animation by resetting states too early after
224             // the password checked, make sure resetting states after the IME hiding animation
225             // finished.
226             mView.setOnFinishImeAnimationRunnable(() -> {
227                 mPasswordEntry.clearFocus();
228                 super.onPause();
229             });
230         }
231         if (mPasswordEntry.isAttachedToWindow()) {
232             mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
233         }
234     }
235 
236     @Override
onStartingToHide()237     public void onStartingToHide() {
238         if (mPasswordEntry.isAttachedToWindow()) {
239             mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
240         }
241     }
242 
updateSwitchImeButton()243     private void updateSwitchImeButton() {
244         // If there's more than one IME, enable the IME switcher button
245         final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
246         final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(
247                 mInputMethodManager, false);
248         if (wasVisible != shouldBeVisible) {
249             mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
250         }
251 
252         // TODO: Check if we still need this hack.
253         // If no icon is visible, reset the start margin on the password field so the text is
254         // still centered.
255         if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
256             android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
257             if (params instanceof MarginLayoutParams) {
258                 final MarginLayoutParams mlp = (MarginLayoutParams) params;
259                 mlp.setMarginStart(0);
260                 mPasswordEntry.setLayoutParams(params);
261             }
262         }
263     }
264 
265     /**
266      * Method adapted from com.android.inputmethod.latin.Utils
267      *
268      * @param imm The input method manager
269      * @param shouldIncludeAuxiliarySubtypes
270      * @return true if we have multiple IMEs to choose from
271      */
hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, final boolean shouldIncludeAuxiliarySubtypes)272     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
273             final boolean shouldIncludeAuxiliarySubtypes) {
274         final List<InputMethodInfo> enabledImis =
275                 imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
276 
277         // Number of the filtered IMEs
278         int filteredImisCount = 0;
279 
280         for (InputMethodInfo imi : enabledImis) {
281             // We can return true immediately after we find two or more filtered IMEs.
282             if (filteredImisCount > 1) return true;
283             final List<InputMethodSubtype> subtypes =
284                     imm.getEnabledInputMethodSubtypeList(imi, true);
285             // IMEs that have no subtypes should be counted.
286             if (subtypes.isEmpty()) {
287                 ++filteredImisCount;
288                 continue;
289             }
290 
291             int auxCount = 0;
292             for (InputMethodSubtype subtype : subtypes) {
293                 if (subtype.isAuxiliary()) {
294                     ++auxCount;
295                 }
296             }
297             final int nonAuxCount = subtypes.size() - auxCount;
298 
299             // IMEs that have one or more non-auxiliary subtypes should be counted.
300             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
301             // subtypes should be counted as well.
302             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
303                 ++filteredImisCount;
304                 continue;
305             }
306         }
307 
308         return filteredImisCount > 1
309                 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's
310                 //enabled input method subtype (The current IME should be LatinIME.)
311                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
312     }
313 }
314