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.util.PluralsMessageFormatter;
27 import android.view.MotionEvent;
28 import android.view.View;
29 
30 import com.android.internal.util.LatencyTracker;
31 import com.android.internal.widget.LockPatternChecker;
32 import com.android.internal.widget.LockPatternUtils;
33 import com.android.internal.widget.LockPatternView;
34 import com.android.internal.widget.LockPatternView.Cell;
35 import com.android.internal.widget.LockscreenCredential;
36 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
37 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
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.flags.FeatureFlags;
42 import com.android.systemui.statusbar.policy.DevicePostureController;
43 
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 
48 public class KeyguardPatternViewController
49         extends KeyguardInputViewController<KeyguardPatternView> {
50 
51     // how many cells the user has to cross before we poke the wakelock
52     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
53 
54     // how long before we clear the wrong pattern
55     private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
56 
57     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
58     private final LockPatternUtils mLockPatternUtils;
59     private final LatencyTracker mLatencyTracker;
60     private final FalsingCollector mFalsingCollector;
61     private final EmergencyButtonController mEmergencyButtonController;
62     private final DevicePostureController mPostureController;
63     private final DevicePostureController.Callback mPostureCallback =
64             posture -> mView.onDevicePostureChanged(posture);
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, SecurityMode.Pattern);
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, FeatureFlags featureFlags)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, FeatureFlags featureFlags) {
201         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
202                 messageAreaControllerFactory, featureFlags);
203         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
204         mLockPatternUtils = lockPatternUtils;
205         mLatencyTracker = latencyTracker;
206         mFalsingCollector = falsingCollector;
207         mEmergencyButtonController = emergencyButtonController;
208         mLockPatternView = mView.findViewById(R.id.lockPatternView);
209         mPostureController = postureController;
210     }
211 
212     @Override
onInit()213     public void onInit() {
214         super.onInit();
215     }
216 
217     @Override
onViewAttached()218     protected void onViewAttached() {
219         super.onViewAttached();
220         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
221         mLockPatternView.setSaveEnabled(false);
222         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
223                 KeyguardUpdateMonitor.getCurrentUser()));
224         mLockPatternView.setOnTouchListener((v, event) -> {
225             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
226                 mFalsingCollector.avoidGesture();
227             }
228             return false;
229         });
230         mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
231 
232         View cancelBtn = mView.findViewById(R.id.cancel_button);
233         if (cancelBtn != null) {
234             cancelBtn.setOnClickListener(view -> {
235                 getKeyguardSecurityCallback().reset();
236                 getKeyguardSecurityCallback().onCancelClicked();
237             });
238         }
239         mView.onDevicePostureChanged(mPostureController.getDevicePosture());
240         mPostureController.addCallback(mPostureCallback);
241         // if the user is currently locked out, enforce it.
242         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
243                 KeyguardUpdateMonitor.getCurrentUser());
244         if (deadline != 0) {
245             handleAttemptLockout(deadline);
246         }
247     }
248 
249     @Override
onViewDetached()250     protected void onViewDetached() {
251         super.onViewDetached();
252         mLockPatternView.setOnPatternListener(null);
253         mLockPatternView.setOnTouchListener(null);
254         mEmergencyButtonController.setEmergencyButtonCallback(null);
255         View cancelBtn = mView.findViewById(R.id.cancel_button);
256         if (cancelBtn != null) {
257             cancelBtn.setOnClickListener(null);
258         }
259         mPostureController.removeCallback(mPostureCallback);
260     }
261 
262     @Override
reset()263     public void reset() {
264         // reset lock pattern
265         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
266                 KeyguardUpdateMonitor.getCurrentUser()));
267         mLockPatternView.enableInput();
268         mLockPatternView.setEnabled(true);
269         mLockPatternView.clearPattern();
270 
271         displayDefaultSecurityMessage();
272     }
273 
274     @Override
onResume(int reason)275     public void onResume(int reason) {
276         super.onResume(reason);
277     }
278 
279     @Override
onPause()280     public void onPause() {
281         super.onPause();
282 
283         if (mCountdownTimer != null) {
284             mCountdownTimer.cancel();
285             mCountdownTimer = null;
286         }
287 
288         if (mPendingLockCheck != null) {
289             mPendingLockCheck.cancel(false);
290             mPendingLockCheck = null;
291         }
292         displayDefaultSecurityMessage();
293     }
294 
295     @Override
needsInput()296     public boolean needsInput() {
297         return false;
298     }
299 
300     @Override
showPromptReason(int reason)301     public void showPromptReason(int reason) {
302         /// TODO: move all this logic into the MessageAreaController?
303         int resId =  0;
304         switch (reason) {
305             case PROMPT_REASON_RESTART:
306                 resId = R.string.kg_prompt_reason_restart_pattern;
307                 break;
308             case PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE:
309                 resId = R.string.kg_prompt_after_update_pattern;
310                 break;
311             case PROMPT_REASON_TIMEOUT:
312                 resId = R.string.kg_prompt_reason_timeout_pattern;
313                 break;
314             case PROMPT_REASON_DEVICE_ADMIN:
315                 resId = R.string.kg_prompt_reason_device_admin;
316                 break;
317             case PROMPT_REASON_USER_REQUEST:
318                 resId = R.string.kg_prompt_after_user_lockdown_pattern;
319                 break;
320             case PROMPT_REASON_PREPARE_FOR_UPDATE:
321                 resId = R.string.kg_prompt_unattended_update_pattern;
322                 break;
323             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
324                 resId = R.string.kg_prompt_reason_timeout_pattern;
325                 break;
326             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
327                 resId = R.string.kg_prompt_reason_timeout_pattern;
328                 break;
329             case PROMPT_REASON_NONE:
330                 break;
331             default:
332                 resId = R.string.kg_prompt_reason_timeout_pattern;
333                 break;
334         }
335         if (resId != 0) {
336             mMessageAreaController.setMessage(getResources().getText(resId), /* animate= */ false);
337         }
338     }
339 
340     @Override
showMessage(CharSequence message, ColorStateList colorState, boolean animated)341     public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) {
342         if (mMessageAreaController == null) {
343             return;
344         }
345         if (colorState != null) {
346             mMessageAreaController.setNextMessageColor(colorState);
347         }
348         mMessageAreaController.setMessage(message, animated);
349     }
350 
351     @Override
startAppearAnimation()352     public void startAppearAnimation() {
353         super.startAppearAnimation();
354     }
355 
356     @Override
startDisappearAnimation(Runnable finishRunnable)357     public boolean startDisappearAnimation(Runnable finishRunnable) {
358         return mView.startDisappearAnimation(
359                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
360     }
361 
displayDefaultSecurityMessage()362     private void displayDefaultSecurityMessage() {
363         mMessageAreaController.setMessage(getInitialMessageResId());
364     }
365 
handleAttemptLockout(long elapsedRealtimeDeadline)366     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
367         mLockPatternView.clearPattern();
368         mLockPatternView.setEnabled(false);
369         final long elapsedRealtime = SystemClock.elapsedRealtime();
370         final long secondsInFuture = (long) Math.ceil(
371                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
372         getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture);
373         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
374 
375             @Override
376             public void onTick(long millisUntilFinished) {
377                 final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
378                 Map<String, Object> arguments = new HashMap<>();
379                 arguments.put("count", secondsRemaining);
380 
381                 mMessageAreaController.setMessage(
382                         PluralsMessageFormatter.format(
383                             mView.getResources(),
384                             arguments,
385                             R.string.kg_too_many_failed_attempts_countdown),
386                         /* animate= */ false
387                 );
388             }
389 
390             @Override
391             public void onFinish() {
392                 mLockPatternView.setEnabled(true);
393                 displayDefaultSecurityMessage();
394             }
395 
396         }.start();
397     }
398 
399     @Override
getInitialMessageResId()400     protected int getInitialMessageResId() {
401         return R.string.keyguard_enter_your_pattern;
402     }
403 }
404