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