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