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