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