• 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.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
20 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
21 
22 import android.content.res.ColorStateList;
23 import android.os.AsyncTask;
24 import android.os.CountDownTimer;
25 import android.os.SystemClock;
26 import android.view.MotionEvent;
27 import android.view.View;
28 
29 import com.android.internal.util.LatencyTracker;
30 import com.android.internal.widget.LockPatternChecker;
31 import com.android.internal.widget.LockPatternUtils;
32 import com.android.internal.widget.LockPatternView;
33 import com.android.internal.widget.LockPatternView.Cell;
34 import com.android.internal.widget.LockscreenCredential;
35 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
36 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
37 import com.android.settingslib.Utils;
38 import com.android.systemui.R;
39 import com.android.systemui.classifier.FalsingClassifier;
40 import com.android.systemui.classifier.FalsingCollector;
41 
42 import java.util.List;
43 
44 public class KeyguardPatternViewController
45         extends KeyguardInputViewController<KeyguardPatternView> {
46 
47     // how many cells the user has to cross before we poke the wakelock
48     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
49 
50     // how long before we clear the wrong pattern
51     private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
52 
53     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
54     private final LockPatternUtils mLockPatternUtils;
55     private final LatencyTracker mLatencyTracker;
56     private final FalsingCollector mFalsingCollector;
57     private final EmergencyButtonController mEmergencyButtonController;
58     private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
59 
60     private KeyguardMessageAreaController mMessageAreaController;
61     private LockPatternView mLockPatternView;
62     private CountDownTimer mCountdownTimer;
63     private AsyncTask<?, ?, ?> mPendingLockCheck;
64 
65     private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
66         @Override
67         public void onEmergencyButtonClickedWhenInCall() {
68             getKeyguardSecurityCallback().reset();
69         }
70     };
71 
72     /**
73      * Useful for clearing out the wrong pattern after a delay
74      */
75     private Runnable mCancelPatternRunnable = new Runnable() {
76         @Override
77         public void run() {
78             mLockPatternView.clearPattern();
79         }
80     };
81 
82     private class UnlockPatternListener implements LockPatternView.OnPatternListener {
83 
84         @Override
onPatternStart()85         public void onPatternStart() {
86             mLockPatternView.removeCallbacks(mCancelPatternRunnable);
87             mMessageAreaController.setMessage("");
88         }
89 
90         @Override
onPatternCleared()91         public void onPatternCleared() {
92         }
93 
94         @Override
onPatternCellAdded(List<Cell> pattern)95         public void onPatternCellAdded(List<Cell> pattern) {
96             getKeyguardSecurityCallback().userActivity();
97             getKeyguardSecurityCallback().onUserInput();
98         }
99 
100         @Override
onPatternDetected(final List<LockPatternView.Cell> pattern)101         public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
102             mKeyguardUpdateMonitor.setCredentialAttempted();
103             mLockPatternView.disableInput();
104             if (mPendingLockCheck != null) {
105                 mPendingLockCheck.cancel(false);
106             }
107 
108             final int userId = KeyguardUpdateMonitor.getCurrentUser();
109             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
110                 // Treat single-sized patterns as erroneous taps.
111                 if (pattern.size() == 1) {
112                     mFalsingCollector.updateFalseConfidence(FalsingClassifier.Result.falsed(
113                             0.7, getClass().getSimpleName(), "empty pattern input"));
114                 }
115                 mLockPatternView.enableInput();
116                 onPatternChecked(userId, false, 0, false /* not valid - too short */);
117                 return;
118             }
119 
120             mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
121             mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
122             mPendingLockCheck = LockPatternChecker.checkCredential(
123                     mLockPatternUtils,
124                     LockscreenCredential.createPattern(pattern),
125                     userId,
126                     new LockPatternChecker.OnCheckCallback() {
127 
128                         @Override
129                         public void onEarlyMatched() {
130                             mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
131                             onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
132                                     true /* isValidPattern */);
133                         }
134 
135                         @Override
136                         public void onChecked(boolean matched, int timeoutMs) {
137                             mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
138                             mLockPatternView.enableInput();
139                             mPendingLockCheck = null;
140                             if (!matched) {
141                                 onPatternChecked(userId, false /* matched */, timeoutMs,
142                                         true /* isValidPattern */);
143                             }
144                         }
145 
146                         @Override
147                         public void onCancelled() {
148                             // We already got dismissed with the early matched callback, so we
149                             // cancelled the check. However, we still need to note down the latency.
150                             mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
151                         }
152                     });
153             if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
154                 getKeyguardSecurityCallback().userActivity();
155                 getKeyguardSecurityCallback().onUserInput();
156             }
157         }
158 
onPatternChecked(int userId, boolean matched, int timeoutMs, boolean isValidPattern)159         private void onPatternChecked(int userId, boolean matched, int timeoutMs,
160                 boolean isValidPattern) {
161             boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
162             if (matched) {
163                 getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
164                 if (dismissKeyguard) {
165                     mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
166                     mLatencyTracker.onActionStart(LatencyTracker.ACTION_LOCKSCREEN_UNLOCK);
167                     getKeyguardSecurityCallback().dismiss(true, userId);
168                 }
169             } else {
170                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
171                 if (isValidPattern) {
172                     getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
173                     if (timeoutMs > 0) {
174                         long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
175                                 userId, timeoutMs);
176                         handleAttemptLockout(deadline);
177                     }
178                 }
179                 if (timeoutMs == 0) {
180                     mMessageAreaController.setMessage(R.string.kg_wrong_pattern);
181                     mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
182                 }
183             }
184         }
185     }
186 
KeyguardPatternViewController(KeyguardPatternView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, LatencyTracker latencyTracker, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, KeyguardMessageAreaController.Factory messageAreaControllerFactory)187     protected KeyguardPatternViewController(KeyguardPatternView view,
188             KeyguardUpdateMonitor keyguardUpdateMonitor,
189             SecurityMode securityMode,
190             LockPatternUtils lockPatternUtils,
191             KeyguardSecurityCallback keyguardSecurityCallback,
192             LatencyTracker latencyTracker,
193             FalsingCollector falsingCollector,
194             EmergencyButtonController emergencyButtonController,
195             KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
196         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
197         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
198         mLockPatternUtils = lockPatternUtils;
199         mLatencyTracker = latencyTracker;
200         mFalsingCollector = falsingCollector;
201         mEmergencyButtonController = emergencyButtonController;
202         mMessageAreaControllerFactory = messageAreaControllerFactory;
203         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
204         mMessageAreaController = mMessageAreaControllerFactory.create(kma);
205         mLockPatternView = mView.findViewById(R.id.lockPatternView);
206     }
207 
208     @Override
onInit()209     public void onInit() {
210         super.onInit();
211         mMessageAreaController.init();
212     }
213 
214     @Override
onViewAttached()215     protected void onViewAttached() {
216         super.onViewAttached();
217         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
218         mLockPatternView.setSaveEnabled(false);
219         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
220                 KeyguardUpdateMonitor.getCurrentUser()));
221         // vibrate mode will be the same for the life of this screen
222         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
223         mLockPatternView.setOnTouchListener((v, event) -> {
224             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
225                 mFalsingCollector.avoidGesture();
226             }
227             return false;
228         });
229         mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
230 
231         View cancelBtn = mView.findViewById(R.id.cancel_button);
232         if (cancelBtn != null) {
233             cancelBtn.setOnClickListener(view -> {
234                 getKeyguardSecurityCallback().reset();
235                 getKeyguardSecurityCallback().onCancelClicked();
236             });
237         }
238     }
239 
240     @Override
onViewDetached()241     protected void onViewDetached() {
242         super.onViewDetached();
243         mLockPatternView.setOnPatternListener(null);
244         mLockPatternView.setOnTouchListener(null);
245         mEmergencyButtonController.setEmergencyButtonCallback(null);
246         View cancelBtn = mView.findViewById(R.id.cancel_button);
247         if (cancelBtn != null) {
248             cancelBtn.setOnClickListener(null);
249         }
250     }
251 
252     @Override
reset()253     public void reset() {
254         // reset lock pattern
255         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
256                 KeyguardUpdateMonitor.getCurrentUser()));
257         mLockPatternView.enableInput();
258         mLockPatternView.setEnabled(true);
259         mLockPatternView.clearPattern();
260 
261         // if the user is currently locked out, enforce it.
262         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
263                 KeyguardUpdateMonitor.getCurrentUser());
264         if (deadline != 0) {
265             handleAttemptLockout(deadline);
266         } else {
267             displayDefaultSecurityMessage();
268         }
269     }
270 
271     @Override
reloadColors()272     public void reloadColors() {
273         super.reloadColors();
274         mMessageAreaController.reloadColors();
275         int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
276                 android.R.attr.textColorPrimary).getDefaultColor();
277         int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
278         mLockPatternView.setColors(textColor, textColor, errorColor);
279     }
280 
281     @Override
onPause()282     public void onPause() {
283         super.onPause();
284 
285         if (mCountdownTimer != null) {
286             mCountdownTimer.cancel();
287             mCountdownTimer = null;
288         }
289 
290         if (mPendingLockCheck != null) {
291             mPendingLockCheck.cancel(false);
292             mPendingLockCheck = null;
293         }
294         displayDefaultSecurityMessage();
295     }
296 
297     @Override
needsInput()298     public boolean needsInput() {
299         return false;
300     }
301 
302     @Override
showPromptReason(int reason)303     public void showPromptReason(int reason) {
304         /// TODO: move all this logic into the MessageAreaController?
305         switch (reason) {
306             case PROMPT_REASON_RESTART:
307                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
308                 break;
309             case PROMPT_REASON_TIMEOUT:
310                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
311                 break;
312             case PROMPT_REASON_DEVICE_ADMIN:
313                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
314                 break;
315             case PROMPT_REASON_USER_REQUEST:
316                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
317                 break;
318             case PROMPT_REASON_PREPARE_FOR_UPDATE:
319                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
320                 break;
321             case PROMPT_REASON_NONE:
322                 break;
323             default:
324                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
325                 break;
326         }
327     }
328 
329     @Override
showMessage(CharSequence message, ColorStateList colorState)330     public void showMessage(CharSequence message, ColorStateList colorState) {
331         if (colorState != null) {
332             mMessageAreaController.setNextMessageColor(colorState);
333         }
334         mMessageAreaController.setMessage(message);
335     }
336 
337     @Override
startAppearAnimation()338     public void startAppearAnimation() {
339         super.startAppearAnimation();
340     }
341 
342     @Override
startDisappearAnimation(Runnable finishRunnable)343     public boolean startDisappearAnimation(Runnable finishRunnable) {
344         return mView.startDisappearAnimation(
345                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
346     }
347 
displayDefaultSecurityMessage()348     private void displayDefaultSecurityMessage() {
349         mMessageAreaController.setMessage("");
350     }
351 
handleAttemptLockout(long elapsedRealtimeDeadline)352     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
353         mLockPatternView.clearPattern();
354         mLockPatternView.setEnabled(false);
355         final long elapsedRealtime = SystemClock.elapsedRealtime();
356         final long secondsInFuture = (long) Math.ceil(
357                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
358         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
359 
360             @Override
361             public void onTick(long millisUntilFinished) {
362                 final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
363                 mMessageAreaController.setMessage(mView.getResources().getQuantityString(
364                         R.plurals.kg_too_many_failed_attempts_countdown,
365                         secondsRemaining, secondsRemaining));
366             }
367 
368             @Override
369             public void onFinish() {
370                 mLockPatternView.setEnabled(true);
371                 displayDefaultSecurityMessage();
372             }
373 
374         }.start();
375     }
376 }
377