• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.Context;
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.AttributeSet;
28 import android.view.HapticFeedbackConstants;
29 import android.view.KeyEvent;
30 import android.view.View;
31 import android.widget.LinearLayout;
32 
33 import com.android.internal.util.LatencyTracker;
34 import com.android.internal.widget.LockPatternChecker;
35 import com.android.internal.widget.LockPatternUtils;
36 
37 import java.util.Arrays;
38 
39 /**
40  * Base class for PIN and password unlock screens.
41  */
42 public abstract class KeyguardAbsKeyInputView extends LinearLayout
43         implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
44     protected KeyguardSecurityCallback mCallback;
45     protected LockPatternUtils mLockPatternUtils;
46     protected AsyncTask<?, ?, ?> mPendingLockCheck;
47     protected SecurityMessageDisplay mSecurityMessageDisplay;
48     protected View mEcaView;
49     protected boolean mEnableHaptics;
50     private boolean mDismissing;
51     protected boolean mResumed;
52     private CountDownTimer mCountdownTimer = null;
53 
54     // To avoid accidental lockout due to events while the device in in the pocket, ignore
55     // any passwords with length less than or equal to this length.
56     protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
57 
KeyguardAbsKeyInputView(Context context)58     public KeyguardAbsKeyInputView(Context context) {
59         this(context, null);
60     }
61 
KeyguardAbsKeyInputView(Context context, AttributeSet attrs)62     public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
63         super(context, attrs);
64     }
65 
66     @Override
setKeyguardCallback(KeyguardSecurityCallback callback)67     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
68         mCallback = callback;
69     }
70 
71     @Override
setLockPatternUtils(LockPatternUtils utils)72     public void setLockPatternUtils(LockPatternUtils utils) {
73         mLockPatternUtils = utils;
74         mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
75     }
76 
77     @Override
reset()78     public void reset() {
79         // start fresh
80         mDismissing = false;
81         resetPasswordText(false /* animate */, false /* announce */);
82         // if the user is currently locked out, enforce it.
83         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
84                 KeyguardUpdateMonitor.getCurrentUser());
85         if (shouldLockout(deadline)) {
86             handleAttemptLockout(deadline);
87         } else {
88             resetState();
89         }
90     }
91 
92     // Allow subclasses to override this behavior
shouldLockout(long deadline)93     protected boolean shouldLockout(long deadline) {
94         return deadline != 0;
95     }
96 
getPasswordTextViewId()97     protected abstract int getPasswordTextViewId();
resetState()98     protected abstract void resetState();
99 
100     @Override
onFinishInflate()101     protected void onFinishInflate() {
102         mLockPatternUtils = new LockPatternUtils(mContext);
103         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
104 
105         EmergencyButton button = findViewById(R.id.emergency_call_button);
106         if (button != null) {
107             button.setCallback(this);
108         }
109     }
110 
111     @Override
onAttachedToWindow()112     protected void onAttachedToWindow() {
113         super.onAttachedToWindow();
114         mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
115     }
116 
117     @Override
onEmergencyButtonClickedWhenInCall()118     public void onEmergencyButtonClickedWhenInCall() {
119         mCallback.reset();
120     }
121 
122     /*
123      * Override this if you have a different string for "wrong password"
124      *
125      * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this
126      */
getWrongPasswordStringId()127     protected int getWrongPasswordStringId() {
128         return R.string.kg_wrong_password;
129     }
130 
verifyPasswordAndUnlock()131     protected void verifyPasswordAndUnlock() {
132         if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
133 
134         final byte[] entry = getPasswordText();
135         setPasswordEntryInputEnabled(false);
136         if (mPendingLockCheck != null) {
137             mPendingLockCheck.cancel(false);
138         }
139 
140         final int userId = KeyguardUpdateMonitor.getCurrentUser();
141         if (entry.length <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
142             // to avoid accidental lockout, only count attempts that are long enough to be a
143             // real password. This may require some tweaking.
144             setPasswordEntryInputEnabled(true);
145             onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
146             Arrays.fill(entry, (byte) 0);
147             return;
148         }
149 
150         if (LatencyTracker.isEnabled(mContext)) {
151             LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
152             LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
153         }
154         mPendingLockCheck = LockPatternChecker.checkPassword(
155                 mLockPatternUtils,
156                 entry,
157                 userId,
158                 new LockPatternChecker.OnCheckCallback() {
159 
160                     @Override
161                     public void onEarlyMatched() {
162                         if (LatencyTracker.isEnabled(mContext)) {
163                             LatencyTracker.getInstance(mContext).onActionEnd(
164                                     ACTION_CHECK_CREDENTIAL);
165                         }
166                         onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
167                                 true /* isValidPassword */);
168                         Arrays.fill(entry, (byte) 0);
169                     }
170 
171                     @Override
172                     public void onChecked(boolean matched, int timeoutMs) {
173                         if (LatencyTracker.isEnabled(mContext)) {
174                             LatencyTracker.getInstance(mContext).onActionEnd(
175                                     ACTION_CHECK_CREDENTIAL_UNLOCKED);
176                         }
177                         setPasswordEntryInputEnabled(true);
178                         mPendingLockCheck = null;
179                         if (!matched) {
180                             onPasswordChecked(userId, false /* matched */, timeoutMs,
181                                     true /* isValidPassword */);
182                         }
183                         Arrays.fill(entry, (byte) 0);
184                     }
185 
186                     @Override
187                     public void onCancelled() {
188                         // We already got dismissed with the early matched callback, so we cancelled
189                         // the check. However, we still need to note down the latency.
190                         if (LatencyTracker.isEnabled(mContext)) {
191                             LatencyTracker.getInstance(mContext).onActionEnd(
192                                     ACTION_CHECK_CREDENTIAL_UNLOCKED);
193                         }
194                         Arrays.fill(entry, (byte) 0);
195                     }
196                 });
197     }
198 
onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword)199     private void onPasswordChecked(int userId, boolean matched, int timeoutMs,
200             boolean isValidPassword) {
201         boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
202         if (matched) {
203             mCallback.reportUnlockAttempt(userId, true, 0);
204             if (dismissKeyguard) {
205                 mDismissing = true;
206                 mCallback.dismiss(true, userId);
207             }
208         } else {
209             if (isValidPassword) {
210                 mCallback.reportUnlockAttempt(userId, false, timeoutMs);
211                 if (timeoutMs > 0) {
212                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
213                             userId, timeoutMs);
214                     handleAttemptLockout(deadline);
215                 }
216             }
217             if (timeoutMs == 0) {
218                 mSecurityMessageDisplay.setMessage(getWrongPasswordStringId());
219             }
220         }
221         resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
222     }
223 
resetPasswordText(boolean animate, boolean announce)224     protected abstract void resetPasswordText(boolean animate, boolean announce);
getPasswordText()225     protected abstract byte[] getPasswordText();
setPasswordEntryEnabled(boolean enabled)226     protected abstract void setPasswordEntryEnabled(boolean enabled);
setPasswordEntryInputEnabled(boolean enabled)227     protected abstract void setPasswordEntryInputEnabled(boolean enabled);
228 
229     // Prevent user from using the PIN/Password entry until scheduled deadline.
handleAttemptLockout(long elapsedRealtimeDeadline)230     protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
231         setPasswordEntryEnabled(false);
232         long elapsedRealtime = SystemClock.elapsedRealtime();
233         long secondsInFuture = (long) Math.ceil(
234                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
235         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
236 
237             @Override
238             public void onTick(long millisUntilFinished) {
239                 int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
240                 mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
241                         R.plurals.kg_too_many_failed_attempts_countdown,
242                         secondsRemaining, secondsRemaining));
243             }
244 
245             @Override
246             public void onFinish() {
247                 mSecurityMessageDisplay.setMessage("");
248                 resetState();
249             }
250         }.start();
251     }
252 
onUserInput()253     protected void onUserInput() {
254         if (mCallback != null) {
255             mCallback.userActivity();
256         }
257         mSecurityMessageDisplay.setMessage("");
258     }
259 
260     @Override
onKeyDown(int keyCode, KeyEvent event)261     public boolean onKeyDown(int keyCode, KeyEvent event) {
262         // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
263         // We don't want to consider it valid user input because the UI
264         // will already respond to the event.
265         if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
266             onUserInput();
267         }
268         return false;
269     }
270 
271     @Override
needsInput()272     public boolean needsInput() {
273         return false;
274     }
275 
276     @Override
onPause()277     public void onPause() {
278         mResumed = false;
279 
280         if (mCountdownTimer != null) {
281             mCountdownTimer.cancel();
282             mCountdownTimer = null;
283         }
284         if (mPendingLockCheck != null) {
285             mPendingLockCheck.cancel(false);
286             mPendingLockCheck = null;
287         }
288         reset();
289     }
290 
291     @Override
onResume(int reason)292     public void onResume(int reason) {
293         mResumed = true;
294     }
295 
296     @Override
getCallback()297     public KeyguardSecurityCallback getCallback() {
298         return mCallback;
299     }
300 
301     @Override
showPromptReason(int reason)302     public void showPromptReason(int reason) {
303         if (reason != PROMPT_REASON_NONE) {
304             int promtReasonStringRes = getPromptReasonStringRes(reason);
305             if (promtReasonStringRes != 0) {
306                 mSecurityMessageDisplay.setMessage(promtReasonStringRes);
307             }
308         }
309     }
310 
311     @Override
showMessage(CharSequence message, ColorStateList colorState)312     public void showMessage(CharSequence message, ColorStateList colorState) {
313         mSecurityMessageDisplay.setNextMessageColor(colorState);
314         mSecurityMessageDisplay.setMessage(message);
315     }
316 
getPromptReasonStringRes(int reason)317     protected abstract int getPromptReasonStringRes(int reason);
318 
319     // Cause a VIRTUAL_KEY vibration
doHapticKeyClick()320     public void doHapticKeyClick() {
321         if (mEnableHaptics) {
322             performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
323                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
324                     | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
325         }
326     }
327 
328     @Override
startDisappearAnimation(Runnable finishRunnable)329     public boolean startDisappearAnimation(Runnable finishRunnable) {
330         return false;
331     }
332 }
333 
334