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