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 android.app.Activity;
20 import android.app.Fragment;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Bundle;
24 import android.util.Log;
25 import android.view.KeyEvent;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.LinearLayout;
30 import android.widget.ScrollView;
31 import android.widget.TextView;
32 
33 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
34 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
35 import com.android.internal.widget.LockPatternUtils;
36 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
37 import com.android.internal.widget.LockPatternView;
38 import com.android.internal.widget.LockPatternView.Cell;
39 import com.android.internal.widget.LockPatternView.DisplayMode;
40 import com.android.settings.core.InstrumentedPreferenceFragment;
41 import com.android.settings.notification.RedactionInterstitial;
42 import com.android.setupwizardlib.GlifLayout;
43 import com.google.android.collect.Lists;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 
49 /**
50  * If the user has a lock pattern set already, makes them confirm the existing one.
51  *
52  * Then, prompts the user to choose a lock pattern:
53  * - prompts for initial pattern
54  * - asks for confirmation / restart
55  * - saves chosen password when confirmed
56  */
57 public class ChooseLockPattern extends SettingsActivity {
58     /**
59      * Used by the choose lock pattern wizard to indicate the wizard is
60      * finished, and each activity in the wizard should finish.
61      * <p>
62      * Previously, each activity in the wizard would finish itself after
63      * starting the next activity. However, this leads to broken 'Back'
64      * behavior. So, now an activity does not finish itself until it gets this
65      * result.
66      */
67     static final int RESULT_FINISHED = RESULT_FIRST_USER;
68 
69     private static final String TAG = "ChooseLockPattern";
70 
71     @Override
getIntent()72     public Intent getIntent() {
73         Intent modIntent = new Intent(super.getIntent());
74         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
75         return modIntent;
76     }
77 
createIntent(Context context, boolean requirePassword, boolean confirmCredentials, int userId)78     public static Intent createIntent(Context context,
79             boolean requirePassword, boolean confirmCredentials, int userId) {
80         Intent intent = new Intent(context, ChooseLockPattern.class);
81         intent.putExtra("key_lock_method", "pattern");
82         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
83         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePassword);
84         intent.putExtra(Intent.EXTRA_USER_ID, userId);
85         return intent;
86     }
87 
createIntent(Context context, boolean requirePassword, String pattern, int userId)88     public static Intent createIntent(Context context,
89             boolean requirePassword, String pattern, int userId) {
90         Intent intent = createIntent(context, requirePassword, false, userId);
91         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
92         return intent;
93     }
94 
createIntent(Context context, boolean requirePassword, long challenge, int userId)95     public static Intent createIntent(Context context,
96             boolean requirePassword, long challenge, int userId) {
97         Intent intent = createIntent(context, requirePassword, false, userId);
98         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
99         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
100         return intent;
101     }
102 
103     @Override
isValidFragment(String fragmentName)104     protected boolean isValidFragment(String fragmentName) {
105         if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
106         return false;
107     }
108 
getFragmentClass()109     /* package */ Class<? extends Fragment> getFragmentClass() {
110         return ChooseLockPatternFragment.class;
111     }
112 
113     @Override
onCreate(Bundle savedInstanceState)114     protected void onCreate(Bundle savedInstanceState) {
115         // requestWindowFeature(Window.FEATURE_NO_TITLE);
116         super.onCreate(savedInstanceState);
117         CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header);
118         setTitle(msg);
119         LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
120         layout.setFitsSystemWindows(false);
121     }
122 
123     @Override
onKeyDown(int keyCode, KeyEvent event)124     public boolean onKeyDown(int keyCode, KeyEvent event) {
125         // *** TODO ***
126         // chooseLockPatternFragment.onKeyDown(keyCode, event);
127         return super.onKeyDown(keyCode, event);
128     }
129 
130     public static class ChooseLockPatternFragment extends InstrumentedPreferenceFragment
131             implements View.OnClickListener, SaveAndFinishWorker.Listener {
132 
133         public static final int CONFIRM_EXISTING_REQUEST = 55;
134 
135         // how long after a confirmation message is shown before moving on
136         static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
137 
138         // how long we wait to clear a wrong pattern
139         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
140 
141         private static final int ID_EMPTY_MESSAGE = -1;
142 
143         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
144 
145         private String mCurrentPattern;
146         private boolean mHasChallenge;
147         private long mChallenge;
148         protected TextView mHeaderText;
149         protected LockPatternView mLockPatternView;
150         protected TextView mFooterText;
151         private TextView mFooterLeftButton;
152         private TextView mFooterRightButton;
153         protected List<LockPatternView.Cell> mChosenPattern = null;
154         private boolean mHideDrawer = false;
155 
156         // ScrollView that contains title and header, only exist in land mode
157         private ScrollView mTitleHeaderScrollView;
158 
159         /**
160          * The patten used during the help screen to show how to draw a pattern.
161          */
162         private final List<LockPatternView.Cell> mAnimatePattern =
163                 Collections.unmodifiableList(Lists.newArrayList(
164                         LockPatternView.Cell.of(0, 0),
165                         LockPatternView.Cell.of(0, 1),
166                         LockPatternView.Cell.of(1, 1),
167                         LockPatternView.Cell.of(2, 1)
168                 ));
169 
170         @Override
onActivityResult(int requestCode, int resultCode, Intent data)171         public void onActivityResult(int requestCode, int resultCode,
172                 Intent data) {
173             super.onActivityResult(requestCode, resultCode, data);
174             switch (requestCode) {
175                 case CONFIRM_EXISTING_REQUEST:
176                     if (resultCode != Activity.RESULT_OK) {
177                         getActivity().setResult(RESULT_FINISHED);
178                         getActivity().finish();
179                     } else {
180                         mCurrentPattern = data.getStringExtra(
181                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
182                     }
183 
184                     updateStage(Stage.Introduction);
185                     break;
186             }
187         }
188 
setRightButtonEnabled(boolean enabled)189         protected void setRightButtonEnabled(boolean enabled) {
190             mFooterRightButton.setEnabled(enabled);
191         }
192 
setRightButtonText(int text)193         protected void setRightButtonText(int text) {
194             mFooterRightButton.setText(text);
195         }
196 
197         /**
198          * The pattern listener that responds according to a user choosing a new
199          * lock pattern.
200          */
201         protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
202                 new LockPatternView.OnPatternListener() {
203 
204                 public void onPatternStart() {
205                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
206                     patternInProgress();
207                 }
208 
209                 public void onPatternCleared() {
210                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
211                 }
212 
213                 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
214                     if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
215                         if (mChosenPattern == null) throw new IllegalStateException(
216                                 "null chosen pattern in stage 'need to confirm");
217                         if (mChosenPattern.equals(pattern)) {
218                             updateStage(Stage.ChoiceConfirmed);
219                         } else {
220                             updateStage(Stage.ConfirmWrong);
221                         }
222                     } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
223                         if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
224                             updateStage(Stage.ChoiceTooShort);
225                         } else {
226                             mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
227                             updateStage(Stage.FirstChoiceValid);
228                         }
229                     } else {
230                         throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
231                                 + "entering the pattern.");
232                     }
233                 }
234 
235                 public void onPatternCellAdded(List<Cell> pattern) {
236 
237                 }
238 
239                 private void patternInProgress() {
240                     mHeaderText.setText(R.string.lockpattern_recording_inprogress);
241                     mFooterText.setText("");
242                     mFooterLeftButton.setEnabled(false);
243                     mFooterRightButton.setEnabled(false);
244 
245                     if (mTitleHeaderScrollView != null) {
246                         mTitleHeaderScrollView.post(new Runnable() {
247                             @Override
248                             public void run() {
249                                 mTitleHeaderScrollView.fullScroll(ScrollView.FOCUS_DOWN);
250                             }
251                         });
252                     }
253                 }
254          };
255 
256         @Override
getMetricsCategory()257         public int getMetricsCategory() {
258             return MetricsEvent.CHOOSE_LOCK_PATTERN;
259         }
260 
261 
262         /**
263          * The states of the left footer button.
264          */
265         enum LeftButtonMode {
266             Cancel(R.string.cancel, true),
267             CancelDisabled(R.string.cancel, false),
268             Retry(R.string.lockpattern_retry_button_text, true),
269             RetryDisabled(R.string.lockpattern_retry_button_text, false),
270             Gone(ID_EMPTY_MESSAGE, false);
271 
272 
273             /**
274              * @param text The displayed text for this mode.
275              * @param enabled Whether the button should be enabled.
276              */
LeftButtonMode(int text, boolean enabled)277             LeftButtonMode(int text, boolean enabled) {
278                 this.text = text;
279                 this.enabled = enabled;
280             }
281 
282             final int text;
283             final boolean enabled;
284         }
285 
286         /**
287          * The states of the right button.
288          */
289         enum RightButtonMode {
290             Continue(R.string.lockpattern_continue_button_text, true),
291             ContinueDisabled(R.string.lockpattern_continue_button_text, false),
292             Confirm(R.string.lockpattern_confirm_button_text, true),
293             ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
294             Ok(android.R.string.ok, true);
295 
296             /**
297              * @param text The displayed text for this mode.
298              * @param enabled Whether the button should be enabled.
299              */
RightButtonMode(int text, boolean enabled)300             RightButtonMode(int text, boolean enabled) {
301                 this.text = text;
302                 this.enabled = enabled;
303             }
304 
305             final int text;
306             final boolean enabled;
307         }
308 
309         /**
310          * Keep track internally of where the user is in choosing a pattern.
311          */
312         protected enum Stage {
313 
314             Introduction(
315                     R.string.lockpattern_recording_intro_header,
316                     LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled,
317                     ID_EMPTY_MESSAGE, true),
318             HelpScreen(
319                     R.string.lockpattern_settings_help_how_to_record,
320                     LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
321             ChoiceTooShort(
322                     R.string.lockpattern_recording_incorrect_too_short,
323                     LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
324                     ID_EMPTY_MESSAGE, true),
325             FirstChoiceValid(
326                     R.string.lockpattern_pattern_entered_header,
327                     LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
328             NeedToConfirm(
329                     R.string.lockpattern_need_to_confirm,
330                     LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
331                     ID_EMPTY_MESSAGE, true),
332             ConfirmWrong(
333                     R.string.lockpattern_need_to_unlock_wrong,
334                     LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
335                     ID_EMPTY_MESSAGE, true),
336             ChoiceConfirmed(
337                     R.string.lockpattern_pattern_confirmed_header,
338                     LeftButtonMode.Cancel, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
339 
340 
341             /**
342              * @param headerMessage The message displayed at the top.
343              * @param leftMode The mode of the left button.
344              * @param rightMode The mode of the right button.
345              * @param footerMessage The footer message.
346              * @param patternEnabled Whether the pattern widget is enabled.
347              */
Stage(int headerMessage, LeftButtonMode leftMode, RightButtonMode rightMode, int footerMessage, boolean patternEnabled)348             Stage(int headerMessage,
349                     LeftButtonMode leftMode,
350                     RightButtonMode rightMode,
351                     int footerMessage, boolean patternEnabled) {
352                 this.headerMessage = headerMessage;
353                 this.leftMode = leftMode;
354                 this.rightMode = rightMode;
355                 this.footerMessage = footerMessage;
356                 this.patternEnabled = patternEnabled;
357             }
358 
359             final int headerMessage;
360             final LeftButtonMode leftMode;
361             final RightButtonMode rightMode;
362             final int footerMessage;
363             final boolean patternEnabled;
364         }
365 
366         private Stage mUiStage = Stage.Introduction;
367 
368         private Runnable mClearPatternRunnable = new Runnable() {
369             public void run() {
370                 mLockPatternView.clearPattern();
371             }
372         };
373 
374         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
375         private SaveAndFinishWorker mSaveAndFinishWorker;
376         private int mUserId;
377 
378         private static final String KEY_UI_STAGE = "uiStage";
379         private static final String KEY_PATTERN_CHOICE = "chosenPattern";
380         private static final String KEY_CURRENT_PATTERN = "currentPattern";
381 
382         @Override
onCreate(Bundle savedInstanceState)383         public void onCreate(Bundle savedInstanceState) {
384             super.onCreate(savedInstanceState);
385             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
386             if (!(getActivity() instanceof ChooseLockPattern)) {
387                 throw new SecurityException("Fragment contained in wrong activity");
388             }
389             Intent intent = getActivity().getIntent();
390             // Only take this argument into account if it belongs to the current profile.
391             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
392 
393             if (intent.getBooleanExtra(
394                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
395                 SaveAndFinishWorker w = new SaveAndFinishWorker();
396                 final boolean required = getActivity().getIntent().getBooleanExtra(
397                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
398                 String current = intent.getStringExtra(
399                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
400                 w.setBlocking(true);
401                 w.setListener(this);
402                 w.start(mChooseLockSettingsHelper.utils(), required,
403                         false, 0, LockPatternUtils.stringToPattern(current), current, mUserId);
404             }
405             mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
406         }
407 
408         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)409         public View onCreateView(LayoutInflater inflater, ViewGroup container,
410                 Bundle savedInstanceState) {
411             final GlifLayout layout = (GlifLayout) inflater.inflate(
412                     R.layout.choose_lock_pattern, container, false);
413             layout.setHeaderText(getActivity().getTitle());
414             return layout;
415         }
416 
417         @Override
onViewCreated(View view, Bundle savedInstanceState)418         public void onViewCreated(View view, Bundle savedInstanceState) {
419             super.onViewCreated(view, savedInstanceState);
420             mHeaderText = (TextView) view.findViewById(R.id.headerText);
421             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
422             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
423             mLockPatternView.setTactileFeedbackEnabled(
424                     mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled());
425 
426             mFooterText = (TextView) view.findViewById(R.id.footerText);
427 
428             mFooterLeftButton = (TextView) view.findViewById(R.id.footerLeftButton);
429             mFooterRightButton = (TextView) view.findViewById(R.id.footerRightButton);
430 
431             mTitleHeaderScrollView = (ScrollView) view.findViewById(R.id
432                     .scroll_layout_title_header);
433 
434             mFooterLeftButton.setOnClickListener(this);
435             mFooterRightButton.setOnClickListener(this);
436 
437             // make it so unhandled touch events within the unlock screen go to the
438             // lock pattern view.
439             final LinearLayoutWithDefaultTouchRecepient topLayout
440                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
441                     R.id.topLayout);
442             topLayout.setDefaultTouchRecepient(mLockPatternView);
443 
444             final boolean confirmCredentials = getActivity().getIntent()
445                     .getBooleanExtra("confirm_credentials", true);
446             Intent intent = getActivity().getIntent();
447             mCurrentPattern = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
448             mHasChallenge = intent.getBooleanExtra(
449                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
450             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
451 
452             if (savedInstanceState == null) {
453                 if (confirmCredentials) {
454                     // first launch. As a security measure, we're in NeedToConfirm mode until we
455                     // know there isn't an existing password or the user confirms their password.
456                     updateStage(Stage.NeedToConfirm);
457                     boolean launchedConfirmationActivity =
458                         mChooseLockSettingsHelper.launchConfirmationActivity(
459                                 CONFIRM_EXISTING_REQUEST,
460                                 getString(R.string.unlock_set_unlock_launch_picker_title), true,
461                                 mUserId);
462                     if (!launchedConfirmationActivity) {
463                         updateStage(Stage.Introduction);
464                     }
465                 } else {
466                     updateStage(Stage.Introduction);
467                 }
468             } else {
469                 // restore from previous state
470                 final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
471                 if (patternString != null) {
472                     mChosenPattern = LockPatternUtils.stringToPattern(patternString);
473                 }
474 
475                 if (mCurrentPattern == null) {
476                     mCurrentPattern = savedInstanceState.getString(KEY_CURRENT_PATTERN);
477                 }
478                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
479 
480                 // Re-attach to the exiting worker if there is one.
481                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
482                         FRAGMENT_TAG_SAVE_AND_FINISH);
483             }
484         }
485 
486         @Override
onResume()487         public void onResume() {
488             super.onResume();
489             updateStage(mUiStage);
490 
491             if (mSaveAndFinishWorker != null) {
492                 setRightButtonEnabled(false);
493                 mSaveAndFinishWorker.setListener(this);
494             }
495         }
496 
497         @Override
onPause()498         public void onPause() {
499             super.onPause();
500             if (mSaveAndFinishWorker != null) {
501                 mSaveAndFinishWorker.setListener(null);
502             }
503         }
504 
getRedactionInterstitialIntent(Context context)505         protected Intent getRedactionInterstitialIntent(Context context) {
506             return RedactionInterstitial.createStartIntent(context, mUserId);
507         }
508 
handleLeftButton()509         public void handleLeftButton() {
510             if (mUiStage.leftMode == LeftButtonMode.Retry) {
511                 mChosenPattern = null;
512                 mLockPatternView.clearPattern();
513                 updateStage(Stage.Introduction);
514             } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
515                 getActivity().finish();
516             } else {
517                 throw new IllegalStateException("left footer button pressed, but stage of " +
518                         mUiStage + " doesn't make sense");
519             }
520         }
521 
handleRightButton()522         public void handleRightButton() {
523             if (mUiStage.rightMode == RightButtonMode.Continue) {
524                 if (mUiStage != Stage.FirstChoiceValid) {
525                     throw new IllegalStateException("expected ui stage "
526                             + Stage.FirstChoiceValid + " when button is "
527                             + RightButtonMode.Continue);
528                 }
529                 updateStage(Stage.NeedToConfirm);
530             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
531                 if (mUiStage != Stage.ChoiceConfirmed) {
532                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
533                             + " when button is " + RightButtonMode.Confirm);
534                 }
535                 startSaveAndFinish();
536             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
537                 if (mUiStage != Stage.HelpScreen) {
538                     throw new IllegalStateException("Help screen is only mode with ok button, "
539                             + "but stage is " + mUiStage);
540                 }
541                 mLockPatternView.clearPattern();
542                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
543                 updateStage(Stage.Introduction);
544             }
545         }
546 
onClick(View v)547         public void onClick(View v) {
548             if (v == mFooterLeftButton) {
549                 handleLeftButton();
550             } else if (v == mFooterRightButton) {
551                 handleRightButton();
552             }
553         }
554 
onKeyDown(int keyCode, KeyEvent event)555         public boolean onKeyDown(int keyCode, KeyEvent event) {
556             if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
557                 if (mUiStage == Stage.HelpScreen) {
558                     updateStage(Stage.Introduction);
559                     return true;
560                 }
561             }
562             if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
563                 updateStage(Stage.HelpScreen);
564                 return true;
565             }
566             return false;
567         }
568 
onSaveInstanceState(Bundle outState)569         public void onSaveInstanceState(Bundle outState) {
570             super.onSaveInstanceState(outState);
571 
572             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
573             if (mChosenPattern != null) {
574                 outState.putString(KEY_PATTERN_CHOICE,
575                         LockPatternUtils.patternToString(mChosenPattern));
576             }
577 
578             if (mCurrentPattern != null) {
579                 outState.putString(KEY_CURRENT_PATTERN,
580                         mCurrentPattern);
581             }
582         }
583 
584         /**
585          * Updates the messages and buttons appropriate to what stage the user
586          * is at in choosing a view.  This doesn't handle clearing out the pattern;
587          * the pattern is expected to be in the right state.
588          * @param stage
589          */
updateStage(Stage stage)590         protected void updateStage(Stage stage) {
591             final Stage previousStage = mUiStage;
592 
593             mUiStage = stage;
594 
595             // header text, footer text, visibility and
596             // enabled state all known from the stage
597             if (stage == Stage.ChoiceTooShort) {
598                 mHeaderText.setText(
599                         getResources().getString(
600                                 stage.headerMessage,
601                                 LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
602             } else {
603                 mHeaderText.setText(stage.headerMessage);
604             }
605             if (stage.footerMessage == ID_EMPTY_MESSAGE) {
606                 mFooterText.setText("");
607             } else {
608                 mFooterText.setText(stage.footerMessage);
609             }
610 
611             if (stage.leftMode == LeftButtonMode.Gone) {
612                 mFooterLeftButton.setVisibility(View.GONE);
613             } else {
614                 mFooterLeftButton.setVisibility(View.VISIBLE);
615                 mFooterLeftButton.setText(stage.leftMode.text);
616                 mFooterLeftButton.setEnabled(stage.leftMode.enabled);
617             }
618 
619             setRightButtonText(stage.rightMode.text);
620             setRightButtonEnabled(stage.rightMode.enabled);
621 
622             // same for whether the pattern is enabled
623             if (stage.patternEnabled) {
624                 mLockPatternView.enableInput();
625             } else {
626                 mLockPatternView.disableInput();
627             }
628 
629             // the rest of the stuff varies enough that it is easier just to handle
630             // on a case by case basis.
631             mLockPatternView.setDisplayMode(DisplayMode.Correct);
632             boolean announceAlways = false;
633 
634             switch (mUiStage) {
635                 case Introduction:
636                     mLockPatternView.clearPattern();
637                     break;
638                 case HelpScreen:
639                     mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
640                     break;
641                 case ChoiceTooShort:
642                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
643                     postClearPatternRunnable();
644                     announceAlways = true;
645                     break;
646                 case FirstChoiceValid:
647                     break;
648                 case NeedToConfirm:
649                     mLockPatternView.clearPattern();
650                     break;
651                 case ConfirmWrong:
652                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
653                     postClearPatternRunnable();
654                     announceAlways = true;
655                     break;
656                 case ChoiceConfirmed:
657                     break;
658             }
659 
660             // If the stage changed, announce the header for accessibility. This
661             // is a no-op when accessibility is disabled.
662             if (previousStage != stage || announceAlways) {
663                 mHeaderText.announceForAccessibility(mHeaderText.getText());
664             }
665         }
666 
667         // clear the wrong pattern unless they have started a new one
668         // already
postClearPatternRunnable()669         private void postClearPatternRunnable() {
670             mLockPatternView.removeCallbacks(mClearPatternRunnable);
671             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
672         }
673 
startSaveAndFinish()674         private void startSaveAndFinish() {
675             if (mSaveAndFinishWorker != null) {
676                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
677                 return;
678             }
679 
680             setRightButtonEnabled(false);
681 
682             mSaveAndFinishWorker = new SaveAndFinishWorker();
683             mSaveAndFinishWorker.setListener(this);
684 
685             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
686                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
687             getFragmentManager().executePendingTransactions();
688 
689             final boolean required = getActivity().getIntent().getBooleanExtra(
690                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
691             mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required,
692                     mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern, mUserId);
693         }
694 
695         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)696         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
697             getActivity().setResult(RESULT_FINISHED, resultData);
698 
699             if (!wasSecureBefore) {
700                 Intent intent = getRedactionInterstitialIntent(getActivity());
701                 if (intent != null) {
702                     intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer);
703                     startActivity(intent);
704                 }
705             }
706             getActivity().finish();
707         }
708     }
709 
710     public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
711 
712         private List<LockPatternView.Cell> mChosenPattern;
713         private String mCurrentPattern;
714         private boolean mLockVirgin;
715 
start(LockPatternUtils utils, boolean credentialRequired, boolean hasChallenge, long challenge, List<LockPatternView.Cell> chosenPattern, String currentPattern, int userId)716         public void start(LockPatternUtils utils, boolean credentialRequired,
717                 boolean hasChallenge, long challenge,
718                 List<LockPatternView.Cell> chosenPattern, String currentPattern, int userId) {
719             prepare(utils, credentialRequired, hasChallenge, challenge, userId);
720 
721             mCurrentPattern = currentPattern;
722             mChosenPattern = chosenPattern;
723             mUserId = userId;
724 
725             mLockVirgin = !mUtils.isPatternEverChosen(mUserId);
726 
727             start();
728         }
729 
730         @Override
saveAndVerifyInBackground()731         protected Intent saveAndVerifyInBackground() {
732             Intent result = null;
733             final int userId = mUserId;
734             mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
735 
736             if (mHasChallenge) {
737                 byte[] token;
738                 try {
739                     token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId);
740                 } catch (RequestThrottledException e) {
741                     token = null;
742                 }
743 
744                 if (token == null) {
745                     Log.e(TAG, "critical: no token returned for known good pattern");
746                 }
747 
748                 result = new Intent();
749                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
750             }
751 
752             return result;
753         }
754 
755         @Override
finish(Intent resultData)756         protected void finish(Intent resultData) {
757             if (mLockVirgin) {
758                 mUtils.setVisiblePatternEnabled(true, mUserId);
759             }
760 
761             super.finish(resultData);
762         }
763     }
764 }
765