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