blob: c94bbb2152f871071a161c5e0934d5f607f22b55 [file] [log] [blame]
The Android Open Source Projectde2d9f52008-10-21 07:00:00 -07001/*
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.setOnPatternListener(mConfirmExistingLockPatternListener);
107 updateStage(Stage.NeedToUnlock);
108
109 if (savedInstanceState != null) {
110 mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
111 } else {
112 // on first launch, if no lock pattern is set, then finish with
113 // success (don't want user to get stuck confirming something that
114 // doesn't exist).
115 if (!mLockPatternUtils.savedPatternExists()) {
116 setResult(RESULT_OK);
117 finish();
118 }
119 }
120 }
121
122 @Override
123 protected void onSaveInstanceState(Bundle outState) {
124 // deliberately not calling super since we are managing this in full
125 outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
126 }
127
128 @Override
129 protected void onPause() {
130 super.onPause();
131
132 if (mCountdownTimer != null) {
133 mCountdownTimer.cancel();
134 }
135 }
136
137 @Override
138 protected void onResume() {
139 super.onResume();
140
141 // if the user is currently locked out, enforce it.
142 long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
143 if (deadline != 0) {
144 handleAttemptLockout(deadline);
145 }
146 }
147
148 private void updateStage(Stage stage) {
149
150 switch (stage) {
151 case NeedToUnlock:
152 if (mHeaderText != null) {
153 mHeaderTextView.setText(mHeaderText);
154 } else {
155 mHeaderTextView.setText(R.string.lockpattern_need_to_unlock);
156 }
157 if (mFooterText != null) {
158 mFooterTextView.setText(mFooterText);
159 } else {
160 mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer);
161 }
162
163 mLockPatternView.setEnabled(true);
164 mLockPatternView.enableInput();
165 break;
166 case NeedToUnlockWrong:
167 if (mHeaderWrongText != null) {
168 mHeaderTextView.setText(mHeaderWrongText);
169 } else {
170 mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
171 }
172 if (mFooterWrongText != null) {
173 mFooterTextView.setText(mFooterWrongText);
174 } else {
175 mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer);
176 }
177
178 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
179 mLockPatternView.setEnabled(true);
180 mLockPatternView.enableInput();
181 break;
182 case LockedOut:
183 mLockPatternView.clearPattern();
184 // enabled = false means: disable input, and have the
185 // appearance of being disabled.
186 mLockPatternView.setEnabled(false); // appearance of being disabled
187 break;
188 }
189 }
190
191 private Runnable mClearPatternRunnable = new Runnable() {
192 public void run() {
193 mLockPatternView.clearPattern();
194 }
195 };
196
197 // clear the wrong pattern unless they have started a new one
198 // already
199 private void postClearPatternRunnable() {
200 mLockPatternView.removeCallbacks(mClearPatternRunnable);
201 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
202 }
203
204 /**
205 * The pattern listener that responds according to a user confirming
206 * an existing lock pattern.
207 */
208 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener = new LockPatternView.OnPatternListener() {
209
210 public void onPatternStart() {
211 mLockPatternView.removeCallbacks(mClearPatternRunnable);
212 }
213
214 public void onPatternCleared() {
215 mLockPatternView.removeCallbacks(mClearPatternRunnable);
216 }
217
218 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
219 if (mLockPatternUtils.checkPattern(pattern)) {
220 setResult(RESULT_OK);
221 finish();
222 } else {
223 if (pattern.size() >= LockPatternUtils.MIN_LOCK_PATTERN_SIZE &&
224 ++mNumWrongConfirmAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
225 long deadline = SystemClock.elapsedRealtime() + LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS;
226 mLockPatternUtils.setLockoutAttemptDeadline(deadline);
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}