KeyguardAbsKeyInputView.java revision 7d5e00ab2b5134d11e40b5a84fa363f8e8b24d68
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 (shouldLockout(deadline)) {
86            handleAttemptLockout(deadline);
87        } else {
88            resetState();
89        }
90    }
91
92    // Allow subclasses to override this behavior
93    protected boolean shouldLockout(long deadline) {
94        return deadline != 0;
95    }
96
97    protected abstract int getPasswordTextViewId();
98    protected abstract void resetState();
99
100    @Override
101    protected void onFinishInflate() {
102        mLockPatternUtils = new LockPatternUtils(mContext);
103
104        mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
105        mPasswordEntry.setOnEditorActionListener(this);
106        mPasswordEntry.addTextChangedListener(this);
107
108        // Set selected property on so the view can send accessibility events.
109        mPasswordEntry.setSelected(true);
110
111        // Poke the wakelock any time the text is selected or modified
112        mPasswordEntry.setOnClickListener(new OnClickListener() {
113            public void onClick(View v) {
114                mCallback.userActivity(0); // TODO: customize timeout for text?
115            }
116        });
117
118        mPasswordEntry.addTextChangedListener(new TextWatcher() {
119            public void onTextChanged(CharSequence s, int start, int before, int count) {
120            }
121
122            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
123            }
124
125            public void afterTextChanged(Editable s) {
126                if (mCallback != null) {
127                    mCallback.userActivity(0);
128                }
129            }
130        });
131        mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
132        mEcaView = findViewById(R.id.keyguard_selector_fade_container);
133        View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
134        if (bouncerFrameView != null) {
135            mBouncerFrame = bouncerFrameView.getBackground();
136        }
137    }
138
139    @Override
140    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
141        // send focus to the password field
142        return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
143    }
144
145    /*
146     * Override this if you have a different string for "wrong password"
147     *
148     * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this
149     */
150    protected int getWrongPasswordStringId() {
151        return R.string.kg_wrong_password;
152    }
153
154    protected void verifyPasswordAndUnlock() {
155        String entry = mPasswordEntry.getText().toString();
156        if (mLockPatternUtils.checkPassword(entry)) {
157            mCallback.reportSuccessfulUnlockAttempt();
158            mCallback.dismiss(true);
159        } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) {
160            // to avoid accidental lockout, only count attempts that are long enough to be a
161            // real password. This may require some tweaking.
162            mCallback.reportFailedUnlockAttempt();
163            if (0 == (mCallback.getFailedAttempts()
164                    % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
165                long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
166                handleAttemptLockout(deadline);
167            }
168            mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true);
169        }
170        mPasswordEntry.setText("");
171    }
172
173    // Prevent user from using the PIN/Password entry until scheduled deadline.
174    protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
175        mPasswordEntry.setEnabled(false);
176        long elapsedRealtime = SystemClock.elapsedRealtime();
177        new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
178
179            @Override
180            public void onTick(long millisUntilFinished) {
181                int secondsRemaining = (int) (millisUntilFinished / 1000);
182                mSecurityMessageDisplay.setMessage(
183                        R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
184            }
185
186            @Override
187            public void onFinish() {
188                mSecurityMessageDisplay.setMessage("", false);
189                resetState();
190            }
191        }.start();
192    }
193
194    @Override
195    public boolean onKeyDown(int keyCode, KeyEvent event) {
196        mCallback.userActivity(0);
197        return false;
198    }
199
200    @Override
201    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
202        // Check if this was the result of hitting the enter key
203        if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE
204                || actionId == EditorInfo.IME_ACTION_NEXT) {
205            verifyPasswordAndUnlock();
206            return true;
207        }
208        return false;
209    }
210
211    @Override
212    public boolean needsInput() {
213        return false;
214    }
215
216    @Override
217    public void onPause() {
218
219    }
220
221    @Override
222    public void onResume(int reason) {
223        reset();
224    }
225
226    @Override
227    public KeyguardSecurityCallback getCallback() {
228        return mCallback;
229    }
230
231    @Override
232    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
233        if (mCallback != null) {
234            mCallback.userActivity(KeyguardViewManager.DIGIT_PRESS_WAKE_MILLIS);
235        }
236    }
237
238    @Override
239    public void onTextChanged(CharSequence s, int start, int before, int count) {
240    }
241
242    @Override
243    public void afterTextChanged(Editable s) {
244    }
245
246    // Cause a VIRTUAL_KEY vibration
247    public void doHapticKeyClick() {
248        if (mEnableHaptics) {
249            performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
250                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
251                    | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
252        }
253    }
254
255    @Override
256    public void showBouncer(int duration) {
257        KeyguardSecurityViewHelper.
258                showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
259    }
260
261    @Override
262    public void hideBouncer(int duration) {
263        KeyguardSecurityViewHelper.
264                hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
265    }
266}
267
268