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