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