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