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