• 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 static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
20 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
21 
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.res.Resources;
25 import android.graphics.drawable.Drawable;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.text.Editable;
29 import android.text.InputType;
30 import android.text.TextUtils;
31 import android.text.TextWatcher;
32 import android.text.method.TextKeyListener;
33 import android.view.KeyEvent;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.ViewGroup.MarginLayoutParams;
37 import android.view.inputmethod.EditorInfo;
38 import android.view.inputmethod.InputMethodInfo;
39 import android.view.inputmethod.InputMethodManager;
40 import android.view.inputmethod.InputMethodSubtype;
41 import android.widget.EditText;
42 import android.widget.ImageView;
43 import android.widget.TextView.OnEditorActionListener;
44 
45 import com.android.internal.util.LatencyTracker;
46 import com.android.internal.widget.LockPatternUtils;
47 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
48 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
49 import com.android.systemui.Flags;
50 import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer;
51 import com.android.systemui.classifier.FalsingCollector;
52 import com.android.systemui.dagger.qualifiers.Main;
53 import com.android.systemui.flags.FeatureFlags;
54 import com.android.systemui.res.R;
55 import com.android.systemui.statusbar.policy.DevicePostureController;
56 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
57 import com.android.systemui.util.concurrency.DelayableExecutor;
58 
59 import java.util.List;
60 
61 public class KeyguardPasswordViewController
62         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
63 
64     private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
65     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
66     private final DevicePostureController mPostureController;
67     private final DevicePostureController.Callback mPostureCallback = posture ->
68             mView.onDevicePostureChanged(posture);
69     private final InputMethodManager mInputMethodManager;
70     private final DelayableExecutor mMainExecutor;
71     private final KeyguardViewController mKeyguardViewController;
72     private final boolean mShowImeAtScreenOn;
73     private Drawable mDefaultPasswordFieldBackground;
74     private Drawable mFocusedPasswordFieldBackground;
75     private EditText mPasswordEntry;
76     private ImageView mSwitchImeButton;
77     private boolean mPaused;
78 
79     private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
80         // Check if this was the result of hitting the IME done action
81         final boolean isSoftImeEvent = event == null
82                 && (actionId == EditorInfo.IME_NULL
83                 || actionId == EditorInfo.IME_ACTION_DONE
84                 || actionId == EditorInfo.IME_ACTION_NEXT);
85         if (isSoftImeEvent) {
86             verifyPasswordAndUnlock();
87             return true;
88         }
89         return false;
90     };
91 
92     private final View.OnKeyListener mKeyListener = (v, keyCode, keyEvent) -> {
93         // Ignore SPACE as a confirm key to allow the space character within passwords.
94         final boolean isKeyboardEnterKey = keyEvent != null
95                 && KeyEvent.isConfirmKey(keyCode) && keyCode != KeyEvent.KEYCODE_SPACE
96                 && keyEvent.getAction() == KeyEvent.ACTION_UP;
97         if (isKeyboardEnterKey) {
98             verifyPasswordAndUnlock();
99             return true;
100         }
101         return false;
102     };
103 
104     private final TextWatcher mTextWatcher = new TextWatcher() {
105         @Override
106         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
107             mKeyguardSecurityCallback.userActivity();
108         }
109 
110         @Override
111         public void onTextChanged(CharSequence s, int start, int before, int count) {
112         }
113 
114         @Override
115         public void afterTextChanged(Editable s) {
116             if (!TextUtils.isEmpty(s)) {
117                 onUserInput();
118             }
119         }
120     };
121 
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, DevicePostureController postureController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, KeyguardKeyboardInteractor keyguardKeyboardInteractor, BouncerHapticPlayer bouncerHapticPlayer, UserActivityNotifier userActivityNotifier)122     protected KeyguardPasswordViewController(KeyguardPasswordView view,
123             KeyguardUpdateMonitor keyguardUpdateMonitor,
124             SecurityMode securityMode,
125             LockPatternUtils lockPatternUtils,
126             KeyguardSecurityCallback keyguardSecurityCallback,
127             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
128             LatencyTracker latencyTracker,
129             InputMethodManager inputMethodManager,
130             EmergencyButtonController emergencyButtonController,
131             @Main DelayableExecutor mainExecutor,
132             @Main Resources resources,
133             FalsingCollector falsingCollector,
134             KeyguardViewController keyguardViewController,
135             DevicePostureController postureController,
136             FeatureFlags featureFlags,
137             SelectedUserInteractor selectedUserInteractor,
138             KeyguardKeyboardInteractor keyguardKeyboardInteractor,
139             BouncerHapticPlayer bouncerHapticPlayer,
140             UserActivityNotifier userActivityNotifier) {
141         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
142                 messageAreaControllerFactory, latencyTracker, falsingCollector,
143                 emergencyButtonController, featureFlags, selectedUserInteractor,
144                 bouncerHapticPlayer, userActivityNotifier);
145         mKeyguardSecurityCallback = keyguardSecurityCallback;
146         mInputMethodManager = inputMethodManager;
147         mPostureController = postureController;
148         mMainExecutor = mainExecutor;
149         mKeyguardViewController = keyguardViewController;
150         mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
151         if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
152             view.setIsLockScreenLandscapeEnabled();
153         }
154         mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
155         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
156         mDefaultPasswordFieldBackground = mPasswordEntry.getBackground();
157         mFocusedPasswordFieldBackground = getResources().getDrawable(
158                 R.drawable.bouncer_password_view_background);
159         mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
160     }
161 
162     @Override
onViewAttached()163     protected void onViewAttached() {
164         super.onViewAttached();
165         mPasswordEntry.setTextOperationUser(
166                 UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
167         mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
168         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
169                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
170 
171         mView.onDevicePostureChanged(mPostureController.getDevicePosture());
172 
173         mPostureController.addCallback(mPostureCallback);
174 
175         // Set selected property on so the view can send accessibility events.
176         mPasswordEntry.setSelected(true);
177         mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
178         mPasswordEntry.setOnKeyListener(mKeyListener);
179         mPasswordEntry.addTextChangedListener(mTextWatcher);
180 
181         // Poke the wakelock any time the text is selected or modified
182         // TODO(b/362362385): Revert to the previous onClickListener implementation once this bug is
183         //  fixed.
184         mPasswordEntry.setOnClickListener(new View.OnClickListener() {
185 
186             private final boolean mAutomotiveAndVisibleBackgroundUsers =
187                     isAutomotiveAndVisibleBackgroundUsers();
188 
189             @Override
190             public void onClick(View v) {
191                 if (mAutomotiveAndVisibleBackgroundUsers) {
192                     mInputMethodManager.restartInput(v);
193                 }
194                 mKeyguardSecurityCallback.userActivity();
195             }
196 
197             private boolean isAutomotiveAndVisibleBackgroundUsers() {
198                 final Context context = getContext();
199                 return context.getPackageManager().hasSystemFeature(
200                         PackageManager.FEATURE_AUTOMOTIVE)
201                         && UserManager.isVisibleBackgroundUsersEnabled()
202                         && context.getResources().getBoolean(
203                         android.R.bool.config_perDisplayFocusEnabled);
204             }
205         });
206 
207         mSwitchImeButton.setOnClickListener(v -> {
208             mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
209             // Do not show auxiliary subtypes in password lock screen.
210             mInputMethodManager.showInputMethodPickerFromSystem(false,
211                     mView.getContext().getDisplayId());
212         });
213 
214         View cancelBtn = mView.findViewById(R.id.cancel_button);
215         if (cancelBtn != null) {
216             cancelBtn.setOnClickListener(view -> {
217                 mKeyguardSecurityCallback.reset();
218                 mKeyguardSecurityCallback.onCancelClicked();
219             });
220         }
221 
222         // If there's more than one IME, enable the IME switcher button
223         updateSwitchImeButton();
224 
225         if (Flags.pinInputFieldStyledFocusState()) {
226             collectFlow(mPasswordEntry,
227                     mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
228                     this::setPasswordFieldFocusBackground);
229 
230             ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
231             layoutParams.height = (int) getResources()
232                     .getDimension(R.dimen.keyguard_password_field_height);
233             layoutParams.width = (int) getResources()
234                     .getDimension(R.dimen.keyguard_password_field_width);
235         }
236 
237     }
238 
setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected)239     private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) {
240         if (isAnyKeyboardConnected) {
241             mPasswordEntry.setBackground(mFocusedPasswordFieldBackground);
242         } else {
243             mPasswordEntry.setBackground(mDefaultPasswordFieldBackground);
244         }
245     }
246 
247     @Override
onViewDetached()248     protected void onViewDetached() {
249         super.onViewDetached();
250         mPasswordEntry.setOnEditorActionListener(null);
251         mPostureController.removeCallback(mPostureCallback);
252     }
253 
254     @Override
needsInput()255     public boolean needsInput() {
256         return true;
257     }
258 
259     @Override
resetState()260     void resetState() {
261         mPasswordEntry.setTextOperationUser(
262                 UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
263         mMessageAreaController.setMessage(getInitialMessageResId());
264         final boolean wasDisabled = mPasswordEntry.isEnabled();
265         mView.setPasswordEntryEnabled(true);
266         mView.setPasswordEntryInputEnabled(true);
267         // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
268         if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
269             return;
270         }
271         if (wasDisabled) {
272             showInput();
273         }
274     }
275 
276     @Override
onResume(int reason)277     public void onResume(int reason) {
278         super.onResume(reason);
279         mPaused = false;
280         if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
281             showInput();
282         }
283     }
284 
showInput()285     private void showInput() {
286         if (!mKeyguardViewController.isBouncerShowing()) {
287             return;
288         }
289 
290         if (mView.isShown()) {
291             mView.showKeyboard();
292         }
293     }
294 
295     @Override
onPause()296     public void onPause() {
297         if (mPaused) {
298             return;
299         }
300         mPaused = true;
301 
302         if (!mPasswordEntry.isVisibleToUser()) {
303             // Reset all states directly and then hide IME when the screen turned off.
304             super.onPause();
305         } else {
306             // In order not to break the IME hide animation by resetting states too early after
307             // the password checked, make sure resetting states after the IME hiding animation
308             // finished.
309             mView.setOnFinishImeAnimationRunnable(() -> {
310                 mPasswordEntry.clearFocus();
311                 super.onPause();
312             });
313         }
314         mView.hideKeyboard();
315     }
316 
317     @Override
onStartingToHide()318     public void onStartingToHide() {
319         mView.hideKeyboard();
320     }
321 
updateSwitchImeButton()322     private void updateSwitchImeButton() {
323         // If there's more than one IME, enable the IME switcher button
324         final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
325         final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(
326                 mInputMethodManager, false);
327         if (wasVisible != shouldBeVisible) {
328             mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
329         }
330 
331         // TODO: Check if we still need this hack.
332         // If no icon is visible, reset the start margin on the password field so the text is
333         // still centered.
334         if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
335             android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
336             if (params instanceof MarginLayoutParams) {
337                 final MarginLayoutParams mlp = (MarginLayoutParams) params;
338                 mlp.setMarginStart(0);
339                 mPasswordEntry.setLayoutParams(params);
340             }
341         }
342     }
343 
344     /**
345      * Method adapted from com.android.inputmethod.latin.Utils
346      *
347      * @param imm The input method manager
348      * @param shouldIncludeAuxiliarySubtypes
349      * @return true if we have multiple IMEs to choose from
350      */
hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, final boolean shouldIncludeAuxiliarySubtypes)351     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
352             final boolean shouldIncludeAuxiliarySubtypes) {
353         final List<InputMethodInfo> enabledImis =
354                 imm.getEnabledInputMethodListAsUser(
355                         UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
356 
357         // Number of the filtered IMEs
358         int filteredImisCount = 0;
359 
360         for (InputMethodInfo imi : enabledImis) {
361             // We can return true immediately after we find two or more filtered IMEs.
362             if (filteredImisCount > 1) return true;
363             final List<InputMethodSubtype> subtypes =
364                     imm.getEnabledInputMethodSubtypeList(imi, true);
365             // IMEs that have no subtypes should be counted.
366             if (subtypes.isEmpty()) {
367                 ++filteredImisCount;
368                 continue;
369             }
370 
371             int auxCount = 0;
372             for (InputMethodSubtype subtype : subtypes) {
373                 if (subtype.isAuxiliary()) {
374                     ++auxCount;
375                 }
376             }
377             final int nonAuxCount = subtypes.size() - auxCount;
378 
379             // IMEs that have one or more non-auxiliary subtypes should be counted.
380             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
381             // subtypes should be counted as well.
382             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
383                 ++filteredImisCount;
384                 continue;
385             }
386         }
387 
388         return filteredImisCount > 1
389                 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's
390                 //enabled input method subtype (The current IME should be LatinIME.)
391                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
392     }
393 
394     @Override
getInitialMessageResId()395     protected int getInitialMessageResId() {
396         return R.string.keyguard_enter_your_password;
397     }
398 }
399