ConfirmLockPattern.java revision afc4ab2ffbb8327ddce9907961295a32cbf49d0f
1/*
2 * Copyright (C) 2008 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.settings;
18
19import com.android.internal.widget.LockPatternUtils;
20import com.android.internal.widget.LockPatternView;
21import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
22
23import android.app.Activity;
24import android.content.Intent;
25import android.os.CountDownTimer;
26import android.os.SystemClock;
27import android.os.Bundle;
28import android.widget.TextView;
29import android.view.Window;
30
31import java.util.List;
32
33/**
34 * Launch this when you want the user to confirm their lock pattern.
35 *
36 * Sets an activity result of {@link Activity#RESULT_OK} when the user
37 * successfully confirmed their pattern.
38 */
39public class ConfirmLockPattern extends Activity {
40
41    /**
42     * Names of {@link CharSequence} fields within the originating {@link Intent}
43     * that are used to configure the keyguard confirmation view's labeling.
44     * The view will use the system-defined resource strings for any labels that
45     * the caller does not supply.
46     */
47    public static final String HEADER_TEXT = "com.android.settings.ConfirmLockPattern.header";
48    public static final String FOOTER_TEXT = "com.android.settings.ConfirmLockPattern.footer";
49    public static final String HEADER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.header_wrong";
50    public static final String FOOTER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.footer_wrong";
51
52    // how long we wait to clear a wrong pattern
53    private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
54
55    private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
56
57    private LockPatternView mLockPatternView;
58    private LockPatternUtils mLockPatternUtils;
59    private int mNumWrongConfirmAttempts;
60    private CountDownTimer mCountdownTimer;
61
62    private TextView mHeaderTextView;
63    private TextView mFooterTextView;
64
65    // caller-supplied text for various prompts
66    private CharSequence mHeaderText;
67    private CharSequence mFooterText;
68    private CharSequence mHeaderWrongText;
69    private CharSequence mFooterWrongText;
70
71
72    private enum Stage {
73        NeedToUnlock,
74        NeedToUnlockWrong,
75        LockedOut
76    }
77
78    @Override
79    protected void onCreate(Bundle savedInstanceState) {
80        super.onCreate(savedInstanceState);
81
82        mLockPatternUtils = new LockPatternUtils(getContentResolver());
83
84        requestWindowFeature(Window.FEATURE_NO_TITLE);
85        setContentView(R.layout.confirm_lock_pattern);
86
87        mHeaderTextView = (TextView) findViewById(R.id.headerText);
88        mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
89        mFooterTextView = (TextView) findViewById(R.id.footerText);
90
91        // make it so unhandled touch events within the unlock screen go to the
92        // lock pattern view.
93        final LinearLayoutWithDefaultTouchRecepient topLayout
94                = (LinearLayoutWithDefaultTouchRecepient) findViewById(
95                R.id.topLayout);
96        topLayout.setDefaultTouchRecepient(mLockPatternView);
97
98        Intent intent = getIntent();
99        if (intent != null) {
100            mHeaderText = intent.getCharSequenceExtra(HEADER_TEXT);
101            mFooterText = intent.getCharSequenceExtra(FOOTER_TEXT);
102            mHeaderWrongText = intent.getCharSequenceExtra(HEADER_WRONG_TEXT);
103            mFooterWrongText = intent.getCharSequenceExtra(FOOTER_WRONG_TEXT);
104        }
105
106        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
107        mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
108        updateStage(Stage.NeedToUnlock);
109
110        if (savedInstanceState != null) {
111            mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
112        } else {
113            // on first launch, if no lock pattern is set, then finish with
114            // success (don't want user to get stuck confirming something that
115            // doesn't exist).
116            if (!mLockPatternUtils.savedPatternExists()) {
117                setResult(RESULT_OK);
118                finish();
119            }
120        }
121    }
122
123    @Override
124    protected void onSaveInstanceState(Bundle outState) {
125        // deliberately not calling super since we are managing this in full
126        outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
127    }
128
129    @Override
130    protected void onPause() {
131        super.onPause();
132
133        if (mCountdownTimer != null) {
134            mCountdownTimer.cancel();
135        }
136    }
137
138    @Override
139    protected void onResume() {
140        super.onResume();
141
142        // if the user is currently locked out, enforce it.
143        long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
144        if (deadline != 0) {
145            handleAttemptLockout(deadline);
146        }
147    }
148
149    private void updateStage(Stage stage) {
150
151        switch (stage) {
152            case NeedToUnlock:
153                if (mHeaderText != null) {
154                    mHeaderTextView.setText(mHeaderText);
155                } else {
156                    mHeaderTextView.setText(R.string.lockpattern_need_to_unlock);
157                }
158                if (mFooterText != null) {
159                    mFooterTextView.setText(mFooterText);
160                } else {
161                    mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer);
162                }
163
164                mLockPatternView.setEnabled(true);
165                mLockPatternView.enableInput();
166                break;
167            case NeedToUnlockWrong:
168                if (mHeaderWrongText != null) {
169                    mHeaderTextView.setText(mHeaderWrongText);
170                } else {
171                    mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
172                }
173                if (mFooterWrongText != null) {
174                    mFooterTextView.setText(mFooterWrongText);
175                } else {
176                    mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer);
177                }
178
179                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
180                mLockPatternView.setEnabled(true);
181                mLockPatternView.enableInput();
182                break;
183            case LockedOut:
184                mLockPatternView.clearPattern();
185                // enabled = false means: disable input, and have the
186                // appearance of being disabled.
187                mLockPatternView.setEnabled(false); // appearance of being disabled
188                break;
189        }
190    }
191
192    private Runnable mClearPatternRunnable = new Runnable() {
193        public void run() {
194            mLockPatternView.clearPattern();
195        }
196    };
197
198    // clear the wrong pattern unless they have started a new one
199    // already
200    private void postClearPatternRunnable() {
201        mLockPatternView.removeCallbacks(mClearPatternRunnable);
202        mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
203    }
204
205    /**
206     * The pattern listener that responds according to a user confirming
207     * an existing lock pattern.
208     */
209    private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener = new LockPatternView.OnPatternListener()  {
210
211        public void onPatternStart() {
212            mLockPatternView.removeCallbacks(mClearPatternRunnable);
213        }
214
215        public void onPatternCleared() {
216            mLockPatternView.removeCallbacks(mClearPatternRunnable);
217        }
218
219        public void onPatternDetected(List<LockPatternView.Cell> pattern) {
220            if (mLockPatternUtils.checkPattern(pattern)) {
221                setResult(RESULT_OK);
222                finish();
223            } else {
224                if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
225                        ++mNumWrongConfirmAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
226                    long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
227                    handleAttemptLockout(deadline);
228                } else {
229                    updateStage(Stage.NeedToUnlockWrong);
230                    postClearPatternRunnable();
231                }
232            }
233        }
234    };
235
236
237    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
238        updateStage(Stage.LockedOut);
239        long elapsedRealtime = SystemClock.elapsedRealtime();
240        mCountdownTimer = new CountDownTimer(
241                elapsedRealtimeDeadline - elapsedRealtime,
242                LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
243
244            @Override
245            public void onTick(long millisUntilFinished) {
246                mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header);
247                final int secondsCountdown = (int) (millisUntilFinished / 1000);
248                mFooterTextView.setText(getString(
249                        R.string.lockpattern_too_many_failed_confirmation_attempts_footer,
250                        secondsCountdown));
251            }
252
253            @Override
254            public void onFinish() {
255                mNumWrongConfirmAttempts = 0;
256                updateStage(Stage.NeedToUnlock);
257            }
258        }.start();
259    }
260}
261