blob: 973e655246c10ca91296badb164678f2bfe3796c [file] [log] [blame]
The Android Open Source Projectde2d9f52008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2007 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.google.android.collect.Lists;
20
21import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
22import com.android.internal.widget.LockPatternUtils;
23import com.android.internal.widget.LockPatternView;
24import static com.android.internal.widget.LockPatternView.DisplayMode;
25
26import android.app.Activity;
27import android.content.Intent;
28import android.os.Bundle;
29import android.view.KeyEvent;
30import android.view.View;
31import android.view.Window;
32import android.widget.TextView;
33
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37
38/**
39 * If the user has a lock pattern set already, makes them confirm the existing one.
40 *
41 * Then, prompts the user to choose a lock pattern:
42 * - prompts for initial pattern
43 * - asks for confirmation / restart
44 * - saves chosen password when confirmed
45 */
46public class ChooseLockPattern extends Activity implements View.OnClickListener{
47
48 // how long after a confirmation message is shown before moving on
49 static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
50
51 // how long we wait to clear a wrong pattern
52 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
53
54 private static final int ID_EMPTY_MESSAGE = -1;
55
56
57 protected TextView mHeaderText;
58 protected LockPatternView mLockPatternView;
59 protected TextView mFooterText;
60 private TextView mFooterLeftButton;
61 private TextView mFooterRightButton;
62
63 protected List<LockPatternView.Cell> mChosenPattern = null;
64
65 protected LockPatternUtils mLockPatternUtils;
66
67 /**
68 * The patten used during the help screen to show how to draw a pattern.
69 */
70 private final List<LockPatternView.Cell> mAnimatePattern =
71 Collections.unmodifiableList(
72 Lists.newArrayList(
73 LockPatternView.Cell.of(0, 0),
74 LockPatternView.Cell.of(0, 1),
75 LockPatternView.Cell.of(1, 1),
76 LockPatternView.Cell.of(2, 1)
77 ));
78
79
80 /**
81 * The pattern listener that responds according to a user choosing a new
82 * lock pattern.
83 */
84 protected LockPatternView.OnPatternListener mChooseNewLockPatternListener = new LockPatternView.OnPatternListener() {
85
86 public void onPatternStart() {
87 mLockPatternView.removeCallbacks(mClearPatternRunnable);
88 patternInProgress();
89 }
90
91 public void onPatternCleared() {
92 mLockPatternView.removeCallbacks(mClearPatternRunnable);
93 }
94
95 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
96 if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
97 if (mChosenPattern == null) throw new IllegalStateException("null chosen pattern in stage 'need to confirm");
98 if (mChosenPattern.equals(pattern)) {
99 updateStage(Stage.ChoiceConfirmed);
100 } else {
101 updateStage(Stage.ConfirmWrong);
102 }
103 } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
104 if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
105 updateStage(Stage.ChoiceTooShort);
106 } else {
107 mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
108 updateStage(Stage.FirstChoiceValid);
109 }
110 } else {
111 throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
112 + "entering the pattern.");
113 }
114 }
115
116 private void patternInProgress() {
117 mHeaderText.setText(R.string.lockpattern_recording_inprogress);
118 mFooterText.setText("");
119 mFooterLeftButton.setEnabled(false);
120 mFooterRightButton.setEnabled(false);
121 }
122 };
123
124
125 /**
126 * The states of the left footer button.
127 */
128 enum LeftButtonMode {
129 Cancel(R.string.cancel, true),
130 CancelDisabled(R.string.cancel, false),
131 Retry(R.string.lockpattern_retry_button_text, true),
132 RetryDisabled(R.string.lockpattern_retry_button_text, false),
133 Gone(ID_EMPTY_MESSAGE, false);
134
135
136 /**
137 * @param text The displayed text for this mode.
138 * @param enabled Whether the button should be enabled.
139 */
140 LeftButtonMode(int text, boolean enabled) {
141 this.text = text;
142 this.enabled = enabled;
143 }
144
145 final int text;
146 final boolean enabled;
147 }
148
149 /**
150 * The states of the right button.
151 */
152 enum RightButtonMode {
153 Continue(R.string.lockpattern_continue_button_text, true),
154 ContinueDisabled(R.string.lockpattern_continue_button_text, false),
155 Confirm(R.string.lockpattern_confirm_button_text, true),
156 ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
157 Ok(android.R.string.ok, true);
158
159 /**
160 * @param text The displayed text for this mode.
161 * @param enabled Whether the button should be enabled.
162 */
163 RightButtonMode(int text, boolean enabled) {
164 this.text = text;
165 this.enabled = enabled;
166 }
167
168 final int text;
169 final boolean enabled;
170 }
171
172 /**
173 * Keep track internally of where the user is in choosing a pattern.
174 */
175 protected enum Stage {
176
177 Introduction(
178 R.string.lockpattern_recording_intro_header,
179 LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled,
180 R.string.lockpattern_recording_intro_footer, true),
181 HelpScreen(
182 R.string.lockpattern_settings_help_how_to_record,
183 LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
184 ChoiceTooShort(
185 R.string.lockpattern_recording_incorrect_too_short,
186 LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
187 ID_EMPTY_MESSAGE, true),
188 FirstChoiceValid(
189 R.string.lockpattern_pattern_entered_header,
190 LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
191 NeedToConfirm(
192 R.string.lockpattern_need_to_confirm,
193 LeftButtonMode.CancelDisabled, RightButtonMode.ConfirmDisabled,
194 ID_EMPTY_MESSAGE, true),
195 ConfirmWrong(
196 R.string.lockpattern_need_to_unlock_wrong,
197 LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
198 ID_EMPTY_MESSAGE, true),
199 ChoiceConfirmed(
200 R.string.lockpattern_pattern_confirmed_header,
201 LeftButtonMode.Cancel, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
202
203
204 /**
205 * @param headerMessage The message displayed at the top.
206 * @param leftMode The mode of the left button.
207 * @param rightMode The mode of the right button.
208 * @param footerMessage The footer message.
209 * @param patternEnabled Whether the pattern widget is enabled.
210 */
211 Stage(int headerMessage,
212 LeftButtonMode leftMode,
213 RightButtonMode rightMode,
214 int footerMessage, boolean patternEnabled) {
215 this.headerMessage = headerMessage;
216 this.leftMode = leftMode;
217 this.rightMode = rightMode;
218 this.footerMessage = footerMessage;
219 this.patternEnabled = patternEnabled;
220 }
221
222 final int headerMessage;
223 final LeftButtonMode leftMode;
224 final RightButtonMode rightMode;
225 final int footerMessage;
226 final boolean patternEnabled;
227 }
228
229 private Stage mUiStage = Stage.Introduction;
230
231 private Runnable mClearPatternRunnable = new Runnable() {
232 public void run() {
233 mLockPatternView.clearPattern();
234 }
235 };
236
237 private static final String KEY_UI_STAGE = "uiStage";
238 private static final String KEY_PATTERN_CHOICE = "chosenPattern";
239
240 @Override
241 protected void onCreate(Bundle savedInstanceState) {
242 super.onCreate(savedInstanceState);
243
244 mLockPatternUtils = new LockPatternUtils(getContentResolver());
245
246 requestWindowFeature(Window.FEATURE_NO_TITLE);
247
248 setupViews();
249
250 // make it so unhandled touch events within the unlock screen go to the
251 // lock pattern view.
252 final LinearLayoutWithDefaultTouchRecepient topLayout
253 = (LinearLayoutWithDefaultTouchRecepient) findViewById(
254 R.id.topLayout);
255 topLayout.setDefaultTouchRecepient(mLockPatternView);
256
257 if (savedInstanceState == null) {
258 // first launch
259 updateStage(Stage.Introduction);
260 if (mLockPatternUtils.savedPatternExists()) {
261 confirmPattern();
262 }
263 } else {
264 // restore from previous state
265 final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
266 if (patternString != null) {
267 mChosenPattern = LockPatternUtils.stringToPattern(patternString);
268 }
269 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
270 }
271 }
272
273 /**
274 * Keep all "find view" related stuff confined to this function since in
275 * case someone needs to subclass and customize.
276 */
277 protected void setupViews() {
278 setContentView(R.layout.choose_lock_pattern);
279
280 mHeaderText = (TextView) findViewById(R.id.headerText);
281
282 mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
283 mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
284
285 mFooterText = (TextView) findViewById(R.id.footerText);
286
287 mFooterLeftButton = (TextView) findViewById(R.id.footerLeftButton);
288 mFooterRightButton = (TextView) findViewById(R.id.footerRightButton);
289
290 mFooterLeftButton.setOnClickListener(this);
291 mFooterRightButton.setOnClickListener(this);
292 }
293
294 public void onClick(View v) {
295 if (v == mFooterLeftButton) {
296 if (mUiStage.leftMode == LeftButtonMode.Retry) {
297 mChosenPattern = null;
298 mLockPatternView.clearPattern();
299 updateStage(Stage.Introduction);
300 } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
301 finish();
302 } else {
303 throw new IllegalStateException("left footer button pressed, but stage of " +
304 mUiStage + " doesn't make sense");
305 }
306 } else if (v == mFooterRightButton) {
307
308 if (mUiStage.rightMode == RightButtonMode.Continue) {
309 if (mUiStage != Stage.FirstChoiceValid) {
310 throw new IllegalStateException("expected ui stage " + Stage.FirstChoiceValid
311 + " when button is " + RightButtonMode.Continue);
312 }
313 updateStage(Stage.NeedToConfirm);
314 } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
315 if (mUiStage != Stage.ChoiceConfirmed) {
316 throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
317 + " when button is " + RightButtonMode.Confirm);
318 }
319 saveChosenPatternAndFinish();
320 } else if (mUiStage.rightMode == RightButtonMode.Ok) {
321 if (mUiStage != Stage.HelpScreen) {
322 throw new IllegalStateException("Help screen is only mode with ok button, but " +
323 "stage is " + mUiStage);
324 }
325 mLockPatternView.clearPattern();
326 mLockPatternView.setDisplayMode(DisplayMode.Correct);
327 updateStage(Stage.Introduction);
328 }
329 }
330 }
331
332 @Override
333 public boolean onKeyDown(int keyCode, KeyEvent event) {
334 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
335 if (mUiStage == Stage.HelpScreen) {
336 updateStage(Stage.Introduction);
337 return true;
338 }
339 }
340 if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
341 updateStage(Stage.HelpScreen);
342 return true;
343 }
344
345 return super.onKeyDown(keyCode, event);
346 }
347
348 /**
349 * Launch screen to confirm the existing lock pattern.
350 * @see #onActivityResult(int, int, android.content.Intent)
351 */
352 protected void confirmPattern() {
353 final Intent intent = new Intent();
354 intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern");
355 startActivityForResult(intent, 55);
356 }
357
358 /**
359 * @see #confirmPattern
360 */
361 @Override
362 protected void onActivityResult(int requestCode, int resultCode,
363 Intent data) {
364 super.onActivityResult(requestCode, resultCode, data);
365
366 if (requestCode != 55) {
367 return;
368 }
369
370 if (resultCode != Activity.RESULT_OK) {
371 finish();
372 }
373 updateStage(Stage.Introduction);
374 }
375
376 @Override
377 protected void onSaveInstanceState(Bundle outState) {
378 super.onSaveInstanceState(outState);
379
380 outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
381 if (mChosenPattern != null) {
382 outState.putString(KEY_PATTERN_CHOICE, LockPatternUtils.patternToString(mChosenPattern));
383 }
384 }
385
386
387 /**
388 * Updates the messages and buttons appropriate to what stage the user
389 * is at in choosing a view. This doesn't handle clearing out the pattern;
390 * the pattern is expected to be in the right state.
391 * @param stage
392 */
393 protected void updateStage(Stage stage) {
394
395 mUiStage = stage;
396
397 // header text, footer text, visibility and
398 // enabled state all known from the stage
399 if (stage == Stage.ChoiceTooShort) {
400 mHeaderText.setText(
401 getResources().getString(
402 stage.headerMessage,
403 LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
404 } else {
405 mHeaderText.setText(stage.headerMessage);
406 }
407 if (stage.footerMessage == ID_EMPTY_MESSAGE) {
408 mFooterText.setText("");
409 } else {
410 mFooterText.setText(stage.footerMessage);
411 }
412
413 if (stage.leftMode == LeftButtonMode.Gone) {
414 mFooterLeftButton.setVisibility(View.GONE);
415 } else {
416 mFooterLeftButton.setVisibility(View.VISIBLE);
417 mFooterLeftButton.setText(stage.leftMode.text);
418 mFooterLeftButton.setEnabled(stage.leftMode.enabled);
419 }
420
421 mFooterRightButton.setText(stage.rightMode.text);
422 mFooterRightButton.setEnabled(stage.rightMode.enabled);
423
424 // same for whether the patten is enabled
425 if (stage.patternEnabled) {
426 mLockPatternView.enableInput();
427 } else {
428 mLockPatternView.disableInput();
429 }
430
431 // the rest of the stuff varies enough that it is easier just to handle
432 // on a case by case basis.
433 mLockPatternView.setDisplayMode(DisplayMode.Correct);
434
435 switch (mUiStage) {
436 case Introduction:
437 mLockPatternView.clearPattern();
438 break;
439 case HelpScreen:
440 mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
441 break;
442 case ChoiceTooShort:
443 mLockPatternView.setDisplayMode(DisplayMode.Wrong);
444 postClearPatternRunnable();
445 break;
446 case FirstChoiceValid:
447 break;
448 case NeedToConfirm:
449 mLockPatternView.clearPattern();
450 break;
451 case ConfirmWrong:
452 mLockPatternView.setDisplayMode(DisplayMode.Wrong);
453 postClearPatternRunnable();
454 break;
455 case ChoiceConfirmed:
456 break;
457 }
458 }
459
460
461 // clear the wrong pattern unless they have started a new one
462 // already
463 private void postClearPatternRunnable() {
464 mLockPatternView.removeCallbacks(mClearPatternRunnable);
465 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
466 }
467
468 private void saveChosenPatternAndFinish() {
469 boolean patternExistedBefore = mLockPatternUtils.savedPatternExists();
470 mLockPatternUtils.saveLockPattern(mChosenPattern);
471
472 // if setting pattern for first time, enable the lock gesture. otherwise,
473 // keep the user's setting.
474 if (!patternExistedBefore) {
475 mLockPatternUtils.setLockPatternEnabled(true);
476 mLockPatternUtils.setVisiblePatternEnabled(true);
477 }
478 finish();
479 }
480}