OSDN Git Service

786d5633bb05e10e8068db4ffb15abe6bd23fdc7
[android-x86/packages-apps-Settings.git] / 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                 if (!launchedConfirmationActivity) {
293                     updateStage(Stage.Introduction);
294                 }
295             } else {
296                 updateStage(Stage.Introduction);
297             }
298         } else {
299             // restore from previous state
300             final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
301             if (patternString != null) {
302                 mChosenPattern = LockPatternUtils.stringToPattern(patternString);
303             }
304             updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
305         }
306     }
307
308     /**
309      * Keep all "find view" related stuff confined to this function since in
310      * case someone needs to subclass and customize.
311      */
312     protected void setupViews() {
313         setContentView(R.layout.choose_lock_pattern);
314
315         mHeaderText = (TextView) findViewById(R.id.headerText);
316
317         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
318         mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
319         mLockPatternView.setTactileFeedbackEnabled(
320                 mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled());
321
322         mFooterText = (TextView) findViewById(R.id.footerText);
323
324         mFooterLeftButton = (TextView) findViewById(R.id.footerLeftButton);
325         mFooterRightButton = (TextView) findViewById(R.id.footerRightButton);
326
327         mFooterLeftButton.setOnClickListener(this);
328         mFooterRightButton.setOnClickListener(this);
329     }
330
331     public void onClick(View v) {
332         if (v == mFooterLeftButton) {
333             if (mUiStage.leftMode == LeftButtonMode.Retry) {
334                 mChosenPattern = null;
335                 mLockPatternView.clearPattern();
336                 updateStage(Stage.Introduction);
337             } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
338                 // They are canceling the entire wizard
339                 setResult(RESULT_FINISHED);
340                 finish();
341             } else {
342                 throw new IllegalStateException("left footer button pressed, but stage of " +
343                     mUiStage + " doesn't make sense");
344             }
345         } else if (v == mFooterRightButton) {
346
347             if (mUiStage.rightMode == RightButtonMode.Continue) {
348                 if (mUiStage != Stage.FirstChoiceValid) {
349                     throw new IllegalStateException("expected ui stage " + Stage.FirstChoiceValid
350                             + " when button is " + RightButtonMode.Continue);
351                 }
352                 updateStage(Stage.NeedToConfirm);
353             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
354                 if (mUiStage != Stage.ChoiceConfirmed) {
355                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
356                             + " when button is " + RightButtonMode.Confirm);
357                 }
358                 saveChosenPatternAndFinish();
359             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
360                 if (mUiStage != Stage.HelpScreen) {
361                     throw new IllegalStateException("Help screen is only mode with ok button, but " +
362                             "stage is " + mUiStage);
363                 }
364                 mLockPatternView.clearPattern();
365                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
366                 updateStage(Stage.Introduction);
367             }
368         }
369     }
370
371     @Override
372     public boolean onKeyDown(int keyCode, KeyEvent event) {
373         if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
374             if (mUiStage == Stage.HelpScreen) {
375                 updateStage(Stage.Introduction);
376                 return true;
377             }
378         }
379         if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
380             updateStage(Stage.HelpScreen);
381             return true;
382         }
383
384         return super.onKeyDown(keyCode, event);
385     }
386
387     @Override
388     protected void onSaveInstanceState(Bundle outState) {
389         super.onSaveInstanceState(outState);
390
391         outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
392         if (mChosenPattern != null) {
393             outState.putString(KEY_PATTERN_CHOICE, LockPatternUtils.patternToString(mChosenPattern));
394         }
395     }
396
397
398     /**
399      * Updates the messages and buttons appropriate to what stage the user
400      * is at in choosing a view.  This doesn't handle clearing out the pattern;
401      * the pattern is expected to be in the right state.
402      * @param stage
403      */
404     protected void updateStage(Stage stage) {
405
406         mUiStage = stage;
407
408         // header text, footer text, visibility and
409         // enabled state all known from the stage
410         if (stage == Stage.ChoiceTooShort) {
411             mHeaderText.setText(
412                     getResources().getString(
413                             stage.headerMessage,
414                             LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
415         } else {
416             mHeaderText.setText(stage.headerMessage);
417         }
418         if (stage.footerMessage == ID_EMPTY_MESSAGE) {
419             mFooterText.setText("");
420         } else {
421             mFooterText.setText(stage.footerMessage);
422         }
423
424         if (stage.leftMode == LeftButtonMode.Gone) {
425             mFooterLeftButton.setVisibility(View.GONE);
426         } else {
427             mFooterLeftButton.setVisibility(View.VISIBLE);
428             mFooterLeftButton.setText(stage.leftMode.text);
429             mFooterLeftButton.setEnabled(stage.leftMode.enabled);
430         }
431
432         mFooterRightButton.setText(stage.rightMode.text);
433         mFooterRightButton.setEnabled(stage.rightMode.enabled);
434
435         // same for whether the patten is enabled
436         if (stage.patternEnabled) {
437             mLockPatternView.enableInput();
438         } else {
439             mLockPatternView.disableInput();
440         }
441
442         // the rest of the stuff varies enough that it is easier just to handle
443         // on a case by case basis.
444         mLockPatternView.setDisplayMode(DisplayMode.Correct);
445
446         switch (mUiStage) {
447             case Introduction:
448                 mLockPatternView.clearPattern();
449                 break;
450             case HelpScreen:
451                 mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
452                 break;
453             case ChoiceTooShort:
454                 mLockPatternView.setDisplayMode(DisplayMode.Wrong);
455                 postClearPatternRunnable();
456                 break;
457             case FirstChoiceValid:
458                 break;
459             case NeedToConfirm:
460                 mLockPatternView.clearPattern();
461                 break;
462             case ConfirmWrong:
463                 mLockPatternView.setDisplayMode(DisplayMode.Wrong);
464                 postClearPatternRunnable();
465                 break;
466             case ChoiceConfirmed:
467                 break;
468         }
469     }
470
471
472     // clear the wrong pattern unless they have started a new one
473     // already
474     private void postClearPatternRunnable() {
475         mLockPatternView.removeCallbacks(mClearPatternRunnable);
476         mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
477     }
478
479     private void saveChosenPatternAndFinish() {
480         LockPatternUtils utils = mChooseLockSettingsHelper.utils();
481         final boolean lockVirgin = !utils.isPatternEverChosen();
482
483         utils.saveLockPattern(mChosenPattern);
484         utils.setLockPatternEnabled(true);
485
486         if (lockVirgin) {
487             utils.setVisiblePatternEnabled(true);
488             utils.setTactileFeedbackEnabled(false);
489         }
490
491         setResult(RESULT_FINISHED);
492         finish();
493     }
494 }