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