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