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 android.content.Context;
20 import android.os.AsyncTask;
21 import android.os.CountDownTimer;
22 import android.os.SystemClock;
23 import android.util.AttributeSet;
24 import android.view.HapticFeedbackConstants;
25 import android.view.KeyEvent;
26 import android.view.View;
27 import android.widget.LinearLayout;
28 
29 import com.android.internal.widget.LockPatternChecker;
30 import com.android.internal.widget.LockPatternUtils;
31 
32 /**
33  * Base class for PIN and password unlock screens.
34  */
35 public abstract class KeyguardAbsKeyInputView extends LinearLayout
36         implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
37     protected KeyguardSecurityCallback mCallback;
38     protected LockPatternUtils mLockPatternUtils;
39     protected AsyncTask<?, ?, ?> mPendingLockCheck;
40     protected SecurityMessageDisplay mSecurityMessageDisplay;
41     protected View mEcaView;
42     protected boolean mEnableHaptics;
43 
44     // To avoid accidental lockout due to events while the device in in the pocket, ignore
45     // any passwords with length less than or equal to this length.
46     protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
47 
KeyguardAbsKeyInputView(Context context)48     public KeyguardAbsKeyInputView(Context context) {
49         this(context, null);
50     }
51 
KeyguardAbsKeyInputView(Context context, AttributeSet attrs)52     public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
53         super(context, attrs);
54     }
55 
56     @Override
setKeyguardCallback(KeyguardSecurityCallback callback)57     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
58         mCallback = callback;
59     }
60 
61     @Override
setLockPatternUtils(LockPatternUtils utils)62     public void setLockPatternUtils(LockPatternUtils utils) {
63         mLockPatternUtils = utils;
64         mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
65     }
66 
67     @Override
reset()68     public void reset() {
69         // start fresh
70         resetPasswordText(false /* animate */);
71         // if the user is currently locked out, enforce it.
72         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
73                 KeyguardUpdateMonitor.getCurrentUser());
74         if (shouldLockout(deadline)) {
75             handleAttemptLockout(deadline);
76         } else {
77             resetState();
78         }
79     }
80 
81     // Allow subclasses to override this behavior
shouldLockout(long deadline)82     protected boolean shouldLockout(long deadline) {
83         return deadline != 0;
84     }
85 
getPasswordTextViewId()86     protected abstract int getPasswordTextViewId();
resetState()87     protected abstract void resetState();
88 
89     @Override
onFinishInflate()90     protected void onFinishInflate() {
91         mLockPatternUtils = new LockPatternUtils(mContext);
92         mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
93         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
94 
95         EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button);
96         if (button != null) {
97             button.setCallback(this);
98         }
99     }
100 
101     @Override
onEmergencyButtonClickedWhenInCall()102     public void onEmergencyButtonClickedWhenInCall() {
103         mCallback.reset();
104     }
105 
106     /*
107      * Override this if you have a different string for "wrong password"
108      *
109      * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this
110      */
getWrongPasswordStringId()111     protected int getWrongPasswordStringId() {
112         return R.string.kg_wrong_password;
113     }
114 
verifyPasswordAndUnlock()115     protected void verifyPasswordAndUnlock() {
116         final String entry = getPasswordText();
117         setPasswordEntryInputEnabled(false);
118         if (mPendingLockCheck != null) {
119             mPendingLockCheck.cancel(false);
120         }
121 
122         if (entry.length() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
123             // to avoid accidental lockout, only count attempts that are long enough to be a
124             // real password. This may require some tweaking.
125             setPasswordEntryInputEnabled(true);
126             onPasswordChecked(false /* matched */, 0, false /* not valid - too short */);
127             return;
128         }
129 
130         mPendingLockCheck = LockPatternChecker.checkPassword(
131                 mLockPatternUtils,
132                 entry,
133                 KeyguardUpdateMonitor.getCurrentUser(),
134                 new LockPatternChecker.OnCheckCallback() {
135                     @Override
136                     public void onChecked(boolean matched, int timeoutMs) {
137                         setPasswordEntryInputEnabled(true);
138                         mPendingLockCheck = null;
139                         onPasswordChecked(matched, timeoutMs, true /* isValidPassword */);
140                     }
141                 });
142     }
143 
onPasswordChecked(boolean matched, int timeoutMs, boolean isValidPassword)144     private void onPasswordChecked(boolean matched, int timeoutMs, boolean isValidPassword) {
145         if (matched) {
146             mCallback.reportUnlockAttempt(true, 0);
147             mCallback.dismiss(true);
148         } else {
149             if (isValidPassword) {
150                 mCallback.reportUnlockAttempt(false, timeoutMs);
151                 if (timeoutMs > 0) {
152                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
153                             KeyguardUpdateMonitor.getCurrentUser(), timeoutMs);
154                     handleAttemptLockout(deadline);
155                 }
156             }
157             if (timeoutMs == 0) {
158                 mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true);
159             }
160         }
161         resetPasswordText(true /* animate */);
162     }
163 
resetPasswordText(boolean animate)164     protected abstract void resetPasswordText(boolean animate);
getPasswordText()165     protected abstract String getPasswordText();
setPasswordEntryEnabled(boolean enabled)166     protected abstract void setPasswordEntryEnabled(boolean enabled);
setPasswordEntryInputEnabled(boolean enabled)167     protected abstract void setPasswordEntryInputEnabled(boolean enabled);
168 
169     // Prevent user from using the PIN/Password entry until scheduled deadline.
handleAttemptLockout(long elapsedRealtimeDeadline)170     protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
171         setPasswordEntryEnabled(false);
172         long elapsedRealtime = SystemClock.elapsedRealtime();
173         new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
174 
175             @Override
176             public void onTick(long millisUntilFinished) {
177                 int secondsRemaining = (int) (millisUntilFinished / 1000);
178                 mSecurityMessageDisplay.setMessage(
179                         R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
180             }
181 
182             @Override
183             public void onFinish() {
184                 mSecurityMessageDisplay.setMessage("", false);
185                 resetState();
186             }
187         }.start();
188     }
189 
onUserInput()190     protected void onUserInput() {
191         if (mCallback != null) {
192             mCallback.userActivity();
193         }
194         mSecurityMessageDisplay.setMessage("", false);
195     }
196 
197     @Override
onKeyDown(int keyCode, KeyEvent event)198     public boolean onKeyDown(int keyCode, KeyEvent event) {
199         onUserInput();
200         return false;
201     }
202 
203     @Override
needsInput()204     public boolean needsInput() {
205         return false;
206     }
207 
208     @Override
onPause()209     public void onPause() {
210         if (mPendingLockCheck != null) {
211             mPendingLockCheck.cancel(false);
212             mPendingLockCheck = null;
213         }
214     }
215 
216     @Override
onResume(int reason)217     public void onResume(int reason) {
218         reset();
219     }
220 
221     @Override
getCallback()222     public KeyguardSecurityCallback getCallback() {
223         return mCallback;
224     }
225 
226     @Override
showPromptReason(int reason)227     public void showPromptReason(int reason) {
228         if (reason != PROMPT_REASON_NONE) {
229             int promtReasonStringRes = getPromtReasonStringRes(reason);
230             if (promtReasonStringRes != 0) {
231                 mSecurityMessageDisplay.setMessage(promtReasonStringRes,
232                         true /* important */);
233             }
234         }
235     }
236 
getPromtReasonStringRes(int reason)237     protected abstract int getPromtReasonStringRes(int reason);
238 
239     // Cause a VIRTUAL_KEY vibration
doHapticKeyClick()240     public void doHapticKeyClick() {
241         if (mEnableHaptics) {
242             performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
243                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
244                     | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
245         }
246     }
247 
248     @Override
startDisappearAnimation(Runnable finishRunnable)249     public boolean startDisappearAnimation(Runnable finishRunnable) {
250         return false;
251     }
252 }
253 
254