• 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.android.internal.logging.MetricsLogger;
20 import com.google.android.collect.Lists;
21 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
22 import com.android.internal.widget.LockPatternUtils;
23 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
24 import com.android.internal.widget.LockPatternView;
25 import com.android.internal.widget.LockPatternView.Cell;
26 import com.android.settings.notification.RedactionInterstitial;
27 
28 import static com.android.internal.widget.LockPatternView.DisplayMode;
29 
30 import android.app.Activity;
31 import android.app.Fragment;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.os.Bundle;
35 import android.os.UserHandle;
36 import android.util.Log;
37 import android.view.KeyEvent;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.TextView;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.List;
46 
47 /**
48  * If the user has a lock pattern set already, makes them confirm the existing one.
49  *
50  * Then, prompts the user to choose a lock pattern:
51  * - prompts for initial pattern
52  * - asks for confirmation / restart
53  * - saves chosen password when confirmed
54  */
55 public class ChooseLockPattern extends SettingsActivity {
56     /**
57      * Used by the choose lock pattern wizard to indicate the wizard is
58      * finished, and each activity in the wizard should finish.
59      * <p>
60      * Previously, each activity in the wizard would finish itself after
61      * starting the next activity. However, this leads to broken 'Back'
62      * behavior. So, now an activity does not finish itself until it gets this
63      * result.
64      */
65     static final int RESULT_FINISHED = RESULT_FIRST_USER;
66 
67     private static final String TAG = "ChooseLockPattern";
68 
69     @Override
getIntent()70     public Intent getIntent() {
71         Intent modIntent = new Intent(super.getIntent());
72         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
73         return modIntent;
74     }
75 
createIntent(Context context, boolean requirePassword, boolean confirmCredentials)76     public static Intent createIntent(Context context,
77             boolean requirePassword, boolean confirmCredentials) {
78         Intent intent = new Intent(context, ChooseLockPattern.class);
79         intent.putExtra("key_lock_method", "pattern");
80         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
81         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePassword);
82         return intent;
83     }
84 
createIntent(Context context, boolean requirePassword, String pattern)85     public static Intent createIntent(Context context,
86             boolean requirePassword, String pattern) {
87         Intent intent = createIntent(context, requirePassword, false);
88         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
89         return intent;
90     }
91 
92 
createIntent(Context context, boolean requirePassword, long challenge)93     public static Intent createIntent(Context context,
94             boolean requirePassword, long challenge) {
95         Intent intent = createIntent(context, requirePassword, false);
96         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
97         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
98         return intent;
99     }
100 
101     @Override
isValidFragment(String fragmentName)102     protected boolean isValidFragment(String fragmentName) {
103         if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
104         return false;
105     }
106 
getFragmentClass()107     /* package */ Class<? extends Fragment> getFragmentClass() {
108         return ChooseLockPatternFragment.class;
109     }
110 
111     @Override
onCreate(Bundle savedInstanceState)112     public void onCreate(Bundle savedInstanceState) {
113         // requestWindowFeature(Window.FEATURE_NO_TITLE);
114         super.onCreate(savedInstanceState);
115         CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header);
116         setTitle(msg);
117     }
118 
119     @Override
onKeyDown(int keyCode, KeyEvent event)120     public boolean onKeyDown(int keyCode, KeyEvent event) {
121         // *** TODO ***
122         // chooseLockPatternFragment.onKeyDown(keyCode, event);
123         return super.onKeyDown(keyCode, event);
124     }
125 
126     public static class ChooseLockPatternFragment extends InstrumentedFragment
127             implements View.OnClickListener, SaveAndFinishWorker.Listener {
128 
129         public static final int CONFIRM_EXISTING_REQUEST = 55;
130 
131         // how long after a confirmation message is shown before moving on
132         static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
133 
134         // how long we wait to clear a wrong pattern
135         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
136 
137         private static final int ID_EMPTY_MESSAGE = -1;
138 
139         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
140 
141         private String mCurrentPattern;
142         private boolean mHasChallenge;
143         private long mChallenge;
144         protected TextView mHeaderText;
145         protected LockPatternView mLockPatternView;
146         protected TextView mFooterText;
147         private TextView mFooterLeftButton;
148         private TextView mFooterRightButton;
149         protected List<LockPatternView.Cell> mChosenPattern = null;
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 MetricsLogger.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 
360         private static final String KEY_UI_STAGE = "uiStage";
361         private static final String KEY_PATTERN_CHOICE = "chosenPattern";
362         private static final String KEY_CURRENT_PATTERN = "currentPattern";
363 
364         @Override
onCreate(Bundle savedInstanceState)365         public void onCreate(Bundle savedInstanceState) {
366             super.onCreate(savedInstanceState);
367             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
368             if (!(getActivity() instanceof ChooseLockPattern)) {
369                 throw new SecurityException("Fragment contained in wrong activity");
370             }
371         }
372 
373         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)374         public View onCreateView(LayoutInflater inflater, ViewGroup container,
375                 Bundle savedInstanceState) {
376             return inflater.inflate(R.layout.choose_lock_pattern, container, false);
377         }
378 
379         @Override
onViewCreated(View view, Bundle savedInstanceState)380         public void onViewCreated(View view, Bundle savedInstanceState) {
381             super.onViewCreated(view, savedInstanceState);
382             mHeaderText = (TextView) view.findViewById(R.id.headerText);
383             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
384             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
385             mLockPatternView.setTactileFeedbackEnabled(
386                     mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled());
387 
388             mFooterText = (TextView) view.findViewById(R.id.footerText);
389 
390             mFooterLeftButton = (TextView) view.findViewById(R.id.footerLeftButton);
391             mFooterRightButton = (TextView) view.findViewById(R.id.footerRightButton);
392 
393             mFooterLeftButton.setOnClickListener(this);
394             mFooterRightButton.setOnClickListener(this);
395 
396             // make it so unhandled touch events within the unlock screen go to the
397             // lock pattern view.
398             final LinearLayoutWithDefaultTouchRecepient topLayout
399                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
400                     R.id.topLayout);
401             topLayout.setDefaultTouchRecepient(mLockPatternView);
402 
403             final boolean confirmCredentials = getActivity().getIntent()
404                     .getBooleanExtra("confirm_credentials", true);
405             Intent intent = getActivity().getIntent();
406             mCurrentPattern = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
407             mHasChallenge = intent.getBooleanExtra(
408                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
409             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
410 
411             if (savedInstanceState == null) {
412                 if (confirmCredentials) {
413                     // first launch. As a security measure, we're in NeedToConfirm mode until we
414                     // know there isn't an existing password or the user confirms their password.
415                     updateStage(Stage.NeedToConfirm);
416                     boolean launchedConfirmationActivity =
417                         mChooseLockSettingsHelper.launchConfirmationActivity(
418                                 CONFIRM_EXISTING_REQUEST,
419                                 getString(R.string.unlock_set_unlock_launch_picker_title), true);
420                     if (!launchedConfirmationActivity) {
421                         updateStage(Stage.Introduction);
422                     }
423                 } else {
424                     updateStage(Stage.Introduction);
425                 }
426             } else {
427                 // restore from previous state
428                 final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
429                 if (patternString != null) {
430                     mChosenPattern = LockPatternUtils.stringToPattern(patternString);
431                 }
432 
433                 if (mCurrentPattern == null) {
434                     mCurrentPattern = savedInstanceState.getString(KEY_CURRENT_PATTERN);
435                 }
436                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
437 
438                 // Re-attach to the exiting worker if there is one.
439                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
440                         FRAGMENT_TAG_SAVE_AND_FINISH);
441             }
442         }
443 
444         @Override
onResume()445         public void onResume() {
446             super.onResume();
447             updateStage(mUiStage);
448 
449             if (mSaveAndFinishWorker != null) {
450                 setRightButtonEnabled(false);
451                 mSaveAndFinishWorker.setListener(this);
452             }
453         }
454 
455         @Override
onPause()456         public void onPause() {
457             super.onPause();
458             if (mSaveAndFinishWorker != null) {
459                 mSaveAndFinishWorker.setListener(null);
460             }
461         }
462 
getRedactionInterstitialIntent(Context context)463         protected Intent getRedactionInterstitialIntent(Context context) {
464             return RedactionInterstitial.createStartIntent(context);
465         }
466 
handleLeftButton()467         public void handleLeftButton() {
468             if (mUiStage.leftMode == LeftButtonMode.Retry) {
469                 mChosenPattern = null;
470                 mLockPatternView.clearPattern();
471                 updateStage(Stage.Introduction);
472             } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
473                 getActivity().finish();
474             } else {
475                 throw new IllegalStateException("left footer button pressed, but stage of " +
476                         mUiStage + " doesn't make sense");
477             }
478         }
479 
handleRightButton()480         public void handleRightButton() {
481             if (mUiStage.rightMode == RightButtonMode.Continue) {
482                 if (mUiStage != Stage.FirstChoiceValid) {
483                     throw new IllegalStateException("expected ui stage "
484                             + Stage.FirstChoiceValid + " when button is "
485                             + RightButtonMode.Continue);
486                 }
487                 updateStage(Stage.NeedToConfirm);
488             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
489                 if (mUiStage != Stage.ChoiceConfirmed) {
490                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
491                             + " when button is " + RightButtonMode.Confirm);
492                 }
493                 startSaveAndFinish();
494             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
495                 if (mUiStage != Stage.HelpScreen) {
496                     throw new IllegalStateException("Help screen is only mode with ok button, "
497                             + "but stage is " + mUiStage);
498                 }
499                 mLockPatternView.clearPattern();
500                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
501                 updateStage(Stage.Introduction);
502             }
503         }
504 
onClick(View v)505         public void onClick(View v) {
506             if (v == mFooterLeftButton) {
507                 handleLeftButton();
508             } else if (v == mFooterRightButton) {
509                 handleRightButton();
510             }
511         }
512 
onKeyDown(int keyCode, KeyEvent event)513         public boolean onKeyDown(int keyCode, KeyEvent event) {
514             if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
515                 if (mUiStage == Stage.HelpScreen) {
516                     updateStage(Stage.Introduction);
517                     return true;
518                 }
519             }
520             if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
521                 updateStage(Stage.HelpScreen);
522                 return true;
523             }
524             return false;
525         }
526 
onSaveInstanceState(Bundle outState)527         public void onSaveInstanceState(Bundle outState) {
528             super.onSaveInstanceState(outState);
529 
530             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
531             if (mChosenPattern != null) {
532                 outState.putString(KEY_PATTERN_CHOICE,
533                         LockPatternUtils.patternToString(mChosenPattern));
534             }
535 
536             if (mCurrentPattern != null) {
537                 outState.putString(KEY_CURRENT_PATTERN,
538                         mCurrentPattern);
539             }
540         }
541 
542         /**
543          * Updates the messages and buttons appropriate to what stage the user
544          * is at in choosing a view.  This doesn't handle clearing out the pattern;
545          * the pattern is expected to be in the right state.
546          * @param stage
547          */
updateStage(Stage stage)548         protected void updateStage(Stage stage) {
549             final Stage previousStage = mUiStage;
550 
551             mUiStage = stage;
552 
553             // header text, footer text, visibility and
554             // enabled state all known from the stage
555             if (stage == Stage.ChoiceTooShort) {
556                 mHeaderText.setText(
557                         getResources().getString(
558                                 stage.headerMessage,
559                                 LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
560             } else {
561                 mHeaderText.setText(stage.headerMessage);
562             }
563             if (stage.footerMessage == ID_EMPTY_MESSAGE) {
564                 mFooterText.setText("");
565             } else {
566                 mFooterText.setText(stage.footerMessage);
567             }
568 
569             if (stage.leftMode == LeftButtonMode.Gone) {
570                 mFooterLeftButton.setVisibility(View.GONE);
571             } else {
572                 mFooterLeftButton.setVisibility(View.VISIBLE);
573                 mFooterLeftButton.setText(stage.leftMode.text);
574                 mFooterLeftButton.setEnabled(stage.leftMode.enabled);
575             }
576 
577             setRightButtonText(stage.rightMode.text);
578             setRightButtonEnabled(stage.rightMode.enabled);
579 
580             // same for whether the pattern is enabled
581             if (stage.patternEnabled) {
582                 mLockPatternView.enableInput();
583             } else {
584                 mLockPatternView.disableInput();
585             }
586 
587             // the rest of the stuff varies enough that it is easier just to handle
588             // on a case by case basis.
589             mLockPatternView.setDisplayMode(DisplayMode.Correct);
590             boolean announceAlways = false;
591 
592             switch (mUiStage) {
593                 case Introduction:
594                     mLockPatternView.clearPattern();
595                     break;
596                 case HelpScreen:
597                     mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
598                     break;
599                 case ChoiceTooShort:
600                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
601                     postClearPatternRunnable();
602                     announceAlways = true;
603                     break;
604                 case FirstChoiceValid:
605                     break;
606                 case NeedToConfirm:
607                     mLockPatternView.clearPattern();
608                     break;
609                 case ConfirmWrong:
610                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
611                     postClearPatternRunnable();
612                     announceAlways = true;
613                     break;
614                 case ChoiceConfirmed:
615                     break;
616             }
617 
618             // If the stage changed, announce the header for accessibility. This
619             // is a no-op when accessibility is disabled.
620             if (previousStage != stage || announceAlways) {
621                 mHeaderText.announceForAccessibility(mHeaderText.getText());
622             }
623         }
624 
625         // clear the wrong pattern unless they have started a new one
626         // already
postClearPatternRunnable()627         private void postClearPatternRunnable() {
628             mLockPatternView.removeCallbacks(mClearPatternRunnable);
629             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
630         }
631 
startSaveAndFinish()632         private void startSaveAndFinish() {
633             if (mSaveAndFinishWorker != null) {
634                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
635                 return;
636             }
637 
638             setRightButtonEnabled(false);
639 
640             mSaveAndFinishWorker = new SaveAndFinishWorker();
641             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
642                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
643             mSaveAndFinishWorker.setListener(this);
644 
645             final boolean required = getActivity().getIntent().getBooleanExtra(
646                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
647             mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required,
648                     mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern);
649         }
650 
651         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)652         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
653             getActivity().setResult(RESULT_FINISHED, resultData);
654             getActivity().finish();
655 
656             if (!wasSecureBefore) {
657                 Intent intent = getRedactionInterstitialIntent(getActivity());
658                 if (intent != null) {
659                     startActivity(intent);
660                 }
661             }
662         }
663     }
664 
665     private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
666 
667         private List<LockPatternView.Cell> mChosenPattern;
668         private String mCurrentPattern;
669         private boolean mLockVirgin;
670 
start(LockPatternUtils utils, boolean credentialRequired, boolean hasChallenge, long challenge, List<LockPatternView.Cell> chosenPattern, String currentPattern)671         public void start(LockPatternUtils utils, boolean credentialRequired,
672                 boolean hasChallenge, long challenge,
673                 List<LockPatternView.Cell> chosenPattern, String currentPattern) {
674             prepare(utils, credentialRequired, hasChallenge, challenge);
675 
676             mCurrentPattern = currentPattern;
677             mChosenPattern = chosenPattern;
678 
679             mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId());
680 
681             start();
682         }
683 
684         @Override
saveAndVerifyInBackground()685         protected Intent saveAndVerifyInBackground() {
686             Intent result = null;
687             final int userId = UserHandle.myUserId();
688             mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
689 
690             if (mHasChallenge) {
691                 byte[] token;
692                 try {
693                     token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId);
694                 } catch (RequestThrottledException e) {
695                     token = null;
696                 }
697 
698                 if (token == null) {
699                     Log.e(TAG, "critical: no token returned for known good pattern");
700                 }
701 
702                 result = new Intent();
703                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
704             }
705 
706             return result;
707         }
708 
709         @Override
finish(Intent resultData)710         protected void finish(Intent resultData) {
711             if (mLockVirgin) {
712                 mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId());
713             }
714 
715             super.finish(resultData);
716         }
717     }
718 }
719