OSDN Git Service

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