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