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