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.pinInputFieldStyledFocusState; 20 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; 21 22 import android.graphics.drawable.Drawable; 23 import android.graphics.drawable.GradientDrawable; 24 import android.graphics.drawable.StateListDrawable; 25 import android.util.TypedValue; 26 import android.view.KeyEvent; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.View.OnKeyListener; 30 import android.view.View.OnTouchListener; 31 import android.view.ViewGroup; 32 33 import com.android.internal.util.LatencyTracker; 34 import com.android.internal.widget.LockPatternUtils; 35 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 36 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor; 37 import com.android.systemui.classifier.FalsingCollector; 38 import com.android.systemui.flags.FeatureFlags; 39 import com.android.systemui.res.R; 40 import com.android.systemui.user.domain.interactor.SelectedUserInteractor; 41 42 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> 43 extends KeyguardAbsKeyInputViewController<T> { 44 45 private final LiftToActivateListener mLiftToActivateListener; 46 private final FalsingCollector mFalsingCollector; 47 private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; 48 protected PasswordTextView mPasswordEntry; 49 50 private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { 51 if (event.getAction() == KeyEvent.ACTION_DOWN) { 52 return mView.onKeyDown(keyCode, event); 53 } 54 if (event.getAction() == KeyEvent.ACTION_UP) { 55 return mView.onKeyUp(keyCode, event); 56 } 57 return false; 58 }; 59 60 private final OnTouchListener mActionButtonTouchListener = (v, event) -> { 61 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 62 mView.doHapticKeyClick(); 63 } 64 return false; 65 }; 66 KeyguardPinBasedInputViewController(T view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, KeyguardKeyboardInteractor keyguardKeyboardInteractor)67 protected KeyguardPinBasedInputViewController(T view, 68 KeyguardUpdateMonitor keyguardUpdateMonitor, 69 SecurityMode securityMode, 70 LockPatternUtils lockPatternUtils, 71 KeyguardSecurityCallback keyguardSecurityCallback, 72 KeyguardMessageAreaController.Factory messageAreaControllerFactory, 73 LatencyTracker latencyTracker, 74 LiftToActivateListener liftToActivateListener, 75 EmergencyButtonController emergencyButtonController, 76 FalsingCollector falsingCollector, 77 FeatureFlags featureFlags, 78 SelectedUserInteractor selectedUserInteractor, 79 KeyguardKeyboardInteractor keyguardKeyboardInteractor) { 80 super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, 81 messageAreaControllerFactory, latencyTracker, falsingCollector, 82 emergencyButtonController, featureFlags, selectedUserInteractor); 83 mLiftToActivateListener = liftToActivateListener; 84 mFalsingCollector = falsingCollector; 85 mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; 86 mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); 87 } 88 89 @Override onViewAttached()90 protected void onViewAttached() { 91 super.onViewAttached(); 92 93 boolean showAnimations = !mLockPatternUtils 94 .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId()); 95 mPasswordEntry.setShowPassword(showAnimations); 96 for (NumPadKey button : mView.getButtons()) { 97 button.setOnTouchListener((v, event) -> { 98 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 99 mFalsingCollector.avoidGesture(); 100 } 101 return false; 102 }); 103 button.setAnimationEnabled(showAnimations); 104 } 105 mPasswordEntry.setOnKeyListener(mOnKeyListener); 106 mPasswordEntry.setUserActivityListener(this::onUserInput); 107 108 View deleteButton = mView.findViewById(R.id.delete_button); 109 deleteButton.setOnTouchListener(mActionButtonTouchListener); 110 deleteButton.setOnClickListener(v -> { 111 // check for time-based lockouts 112 if (mPasswordEntry.isEnabled()) { 113 mPasswordEntry.deleteLastChar(); 114 } 115 }); 116 deleteButton.setOnLongClickListener(v -> { 117 // check for time-based lockouts 118 if (mPasswordEntry.isEnabled()) { 119 mView.resetPasswordText(true /* animate */, true /* announce */); 120 } 121 mView.doHapticKeyClick(); 122 return true; 123 }); 124 125 View okButton = mView.findViewById(R.id.key_enter); 126 if (okButton != null) { 127 okButton.setOnTouchListener(mActionButtonTouchListener); 128 okButton.setOnClickListener(v -> { 129 if (mPasswordEntry.isEnabled()) { 130 verifyPasswordAndUnlock(); 131 } 132 }); 133 okButton.setOnHoverListener(mLiftToActivateListener); 134 } 135 if (pinInputFieldStyledFocusState()) { 136 collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(), 137 this::setKeyboardBasedFocusOutline); 138 139 /** 140 * new UI Specs for PIN Input field have new dimensions go/pin-focus-states. 141 * However we want these changes behind a flag, and resource files cannot be flagged 142 * hence the dimension change in code. When the flags are removed these dimensions 143 * should be set in resources permanently and the code below removed. 144 */ 145 ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams(); 146 layoutParams.width = (int) getResources().getDimension( 147 R.dimen.keyguard_pin_field_width); 148 layoutParams.height = (int) getResources().getDimension( 149 R.dimen.keyguard_pin_field_height); 150 } 151 } 152 setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected)153 private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) { 154 Drawable background = mPasswordEntry.getBackground(); 155 if (!(background instanceof StateListDrawable)) return; 156 Drawable stateDrawable = ((StateListDrawable) background).getStateDrawable(0); 157 if (!(stateDrawable instanceof GradientDrawable gradientDrawable)) return; 158 159 int color = getResources().getColor(R.color.bouncer_password_focus_color); 160 if (!isAnyKeyboardConnected) { 161 gradientDrawable.setStroke(0, color); 162 } else { 163 int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, 164 getResources().getDisplayMetrics()); 165 gradientDrawable.setStroke(strokeWidthInDP, color); 166 } 167 } 168 169 170 @Override onViewDetached()171 protected void onViewDetached() { 172 super.onViewDetached(); 173 174 for (NumPadKey button : mView.getButtons()) { 175 button.setOnTouchListener(null); 176 } 177 } 178 179 @Override onResume(int reason)180 public void onResume(int reason) { 181 super.onResume(reason); 182 // It's possible to reach a state here where mPasswordEntry believes it is focused 183 // but it is not actually focused. This state will prevent the view from gaining focus, 184 // as requestFocus will no-op since the focus flag is already set. By clearing focus first, 185 // it's guaranteed that the view has focus. 186 mPasswordEntry.clearFocus(); 187 mPasswordEntry.requestFocus(); 188 } 189 190 @Override resetState()191 void resetState() { 192 mMessageAreaController.setMessage(getInitialMessageResId()); 193 mView.setPasswordEntryEnabled(true); 194 } 195 196 @Override startErrorAnimation()197 protected void startErrorAnimation() { 198 super.startErrorAnimation(); 199 mView.startErrorAnimation(); 200 } 201 202 @Override getInitialMessageResId()203 protected int getInitialMessageResId() { 204 return R.string.keyguard_enter_your_pin; 205 } 206 } 207