KeyguardAbsKeyInputView.java revision 5ecd81154fa039961f65bb4e36d18ac555b0d1d6
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.graphics.Rect;
21import android.graphics.drawable.Drawable;
22import android.os.CountDownTimer;
23import android.os.SystemClock;
24import android.text.Editable;
25import android.text.TextWatcher;
26import android.util.AttributeSet;
27import android.view.HapticFeedbackConstants;
28import android.view.KeyEvent;
29import android.view.View;
30import android.view.inputmethod.EditorInfo;
31import android.widget.LinearLayout;
32import android.widget.TextView;
33import android.widget.TextView.OnEditorActionListener;
34
35import com.android.internal.widget.LockPatternUtils;
36
37/**
38 * Base class for PIN and password unlock screens.
39 */
40public abstract class KeyguardAbsKeyInputView extends LinearLayout
41        implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
42    protected KeyguardSecurityCallback mCallback;
43    protected TextView mPasswordEntry;
44    protected LockPatternUtils mLockPatternUtils;
45    protected SecurityMessageDisplay mSecurityMessageDisplay;
46    protected View mEcaView;
47    private Drawable mBouncerFrame;
48    protected boolean mEnableHaptics;
49
50    // To avoid accidental lockout due to events while the device in in the pocket, ignore
51    // any passwords with length less than or equal to this length.
52    protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
53
54    public KeyguardAbsKeyInputView(Context context) {
55        this(context, null);
56    }
57
58    public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
59        super(context, attrs);
60    }
61
62    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
63        mCallback = callback;
64    }
65
66    public void setLockPatternUtils(LockPatternUtils utils) {
67        mLockPatternUtils = utils;
68        mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
69    }
70
71    @Override
72    public void onWindowFocusChanged(boolean hasWindowFocus) {
73        if (hasWindowFocus) {
74            reset();
75        }
76    }
77
78    public void reset() {
79        // start fresh
80        mPasswordEntry.setText("");
81        mPasswordEntry.requestFocus();
82
83        // if the user is currently locked out, enforce it.
84        long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
85        if (deadline != 0) {
86            handleAttemptLockout(deadline);
87        } else {
88            resetState();
89        }
90    }
91
92    protected abstract int getPasswordTextViewId();
93    protected abstract void resetState();
94
95    @Override
96    protected void onFinishInflate() {
97        mLockPatternUtils = new LockPatternUtils(mContext);
98
99        mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
100        mPasswordEntry.setOnEditorActionListener(this);
101        mPasswordEntry.addTextChangedListener(this);
102
103        // Set selected property on so the view can send accessibility events.
104        mPasswordEntry.setSelected(true);
105
106        // Poke the wakelock any time the text is selected or modified
107        mPasswordEntry.setOnClickListener(new OnClickListener() {
108            public void onClick(View v) {
109                mCallback.userActivity(0); // TODO: customize timeout for text?
110            }
111        });
112
113        mPasswordEntry.addTextChangedListener(new TextWatcher() {
114            public void onTextChanged(CharSequence s, int start, int before, int count) {
115            }
116
117            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
118            }
119
120            public void afterTextChanged(Editable s) {
121                if (mCallback != null) {
122                    mCallback.userActivity(0);
123                }
124            }
125        });
126        mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
127        mEcaView = findViewById(R.id.keyguard_selector_fade_container);
128        View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
129        if (bouncerFrameView != null) {
130            mBouncerFrame = bouncerFrameView.getBackground();
131        }
132    }
133
134    @Override
135    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
136        // send focus to the password field
137        return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
138    }
139
140    /*
141     * Override this if you have a different string for "wrong password"
142     *
143     * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this
144     */
145    protected int getWrongPasswordStringId() {
146        return R.string.kg_wrong_password;
147    }
148
149    protected void verifyPasswordAndUnlock() {
150        String entry = mPasswordEntry.getText().toString();
151        if (mLockPatternUtils.checkPassword(entry)) {
152            mCallback.reportSuccessfulUnlockAttempt();
153            mCallback.dismiss(true);
154        } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) {
155            // to avoid accidental lockout, only count attempts that are long enough to be a
156            // real password. This may require some tweaking.
157            mCallback.reportFailedUnlockAttempt();
158            if (0 == (mCallback.getFailedAttempts()
159                    % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
160                long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
161                handleAttemptLockout(deadline);
162            }
163            mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true);
164        }
165        mPasswordEntry.setText("");
166    }
167
168    // Prevent user from using the PIN/Password entry until scheduled deadline.
169    protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
170        mPasswordEntry.setEnabled(false);
171        long elapsedRealtime = SystemClock.elapsedRealtime();
172        new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
173
174            @Override
175            public void onTick(long millisUntilFinished) {
176                int secondsRemaining = (int) (millisUntilFinished / 1000);
177                mSecurityMessageDisplay.setMessage(
178                        R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
179            }
180
181            @Override
182            public void onFinish() {
183                mSecurityMessageDisplay.setMessage("", false);
184                resetState();
185            }
186        }.start();
187    }
188
189    @Override
190    public boolean onKeyDown(int keyCode, KeyEvent event) {
191        mCallback.userActivity(0);
192        return false;
193    }
194
195    @Override
196    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
197        // Check if this was the result of hitting the enter key
198        if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE
199                || actionId == EditorInfo.IME_ACTION_NEXT) {
200            verifyPasswordAndUnlock();
201            return true;
202        }
203        return false;
204    }
205
206    @Override
207    public boolean needsInput() {
208        return false;
209    }
210
211    @Override
212    public void onPause() {
213
214    }
215
216    @Override
217    public void onResume(int reason) {
218        reset();
219    }
220
221    @Override
222    public KeyguardSecurityCallback getCallback() {
223        return mCallback;
224    }
225
226    @Override
227    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
228        if (mCallback != null) {
229            mCallback.userActivity(KeyguardViewManager.DIGIT_PRESS_WAKE_MILLIS);
230        }
231    }
232
233    @Override
234    public void onTextChanged(CharSequence s, int start, int before, int count) {
235    }
236
237    @Override
238    public void afterTextChanged(Editable s) {
239    }
240
241    // Cause a VIRTUAL_KEY vibration
242    public void doHapticKeyClick() {
243        if (mEnableHaptics) {
244            performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
245                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
246                    | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
247        }
248    }
249
250    @Override
251    public void showBouncer(int duration) {
252        KeyguardSecurityViewHelper.
253                showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
254    }
255
256    @Override
257    public void hideBouncer(int duration) {
258        KeyguardSecurityViewHelper.
259                hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
260    }
261}
262
263