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