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