Home | History | Annotate | Download | only in settings
      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
     69     public Intent getIntent() {
     70         Intent modIntent = new Intent(super.getIntent());
     71         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
     72         return modIntent;
     73     }
     74 
     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 
     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 
     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
    101     protected boolean isValidFragment(String fragmentName) {
    102         if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
    103         return false;
    104     }
    105 
    106     /* package */ Class<? extends Fragment> getFragmentClass() {
    107         return ChooseLockPatternFragment.class;
    108     }
    109 
    110     @Override
    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
    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
    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 
    181         protected void setRightButtonEnabled(boolean enabled) {
    182             mFooterRightButton.setEnabled(enabled);
    183         }
    184 
    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
    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              */
    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              */
    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              */
    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
    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
    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
    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
    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
    475         public void onPause() {
    476             super.onPause();
    477             if (mSaveAndFinishWorker != null) {
    478                 mSaveAndFinishWorker.setListener(null);
    479             }
    480         }
    481 
    482         protected Intent getRedactionInterstitialIntent(Context context) {
    483             return RedactionInterstitial.createStartIntent(context, mUserId);
    484         }
    485 
    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 
    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 
    524         public void onClick(View v) {
    525             if (v == mFooterLeftButton) {
    526                 handleLeftButton();
    527             } else if (v == mFooterRightButton) {
    528                 handleRightButton();
    529             }
    530         }
    531 
    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 
    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          */
    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
    646         private void postClearPatternRunnable() {
    647             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    648             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
    649         }
    650 
    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
    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 
    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
    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
    733         protected void finish(Intent resultData) {
    734             if (mLockVirgin) {
    735                 mUtils.setVisiblePatternEnabled(true, mUserId);
    736             }
    737 
    738             super.finish(resultData);
    739         }
    740     }
    741 }
    742