ChooseLockPassword.java revision 3e7e3d6c594018e4bc7fc77acc19e148d8bf1bdd
1/*
2 * Copyright (C) 2010 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
17package com.android.settings;
18
19import com.android.internal.logging.MetricsLogger;
20import com.android.internal.widget.LockPatternUtils;
21import com.android.internal.widget.PasswordEntryKeyboardHelper;
22import com.android.internal.widget.PasswordEntryKeyboardView;
23import com.android.internal.widget.TextViewInputDisabler;
24import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
25import com.android.settings.notification.RedactionInterstitial;
26
27import android.app.Activity;
28import android.app.Fragment;
29import android.app.admin.DevicePolicyManager;
30import android.content.Context;
31import android.content.Intent;
32import android.inputmethodservice.KeyboardView;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Message;
36import android.os.UserHandle;
37import android.text.Editable;
38import android.text.InputType;
39import android.text.Selection;
40import android.text.Spannable;
41import android.text.TextUtils;
42import android.text.TextWatcher;
43import android.util.Log;
44import android.view.KeyEvent;
45import android.view.LayoutInflater;
46import android.view.View;
47import android.view.ViewGroup;
48import android.view.View.OnClickListener;
49import android.view.inputmethod.EditorInfo;
50import android.widget.Button;
51import android.widget.TextView;
52import android.widget.TextView.OnEditorActionListener;
53
54public class ChooseLockPassword extends SettingsActivity {
55    public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
56    public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
57    public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
58    public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
59    public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
60    public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
61    public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
62    public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
63
64    private static final String TAG = "ChooseLockPassword";
65
66    @Override
67    public Intent getIntent() {
68        Intent modIntent = new Intent(super.getIntent());
69        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
70        return modIntent;
71    }
72
73    public static Intent createIntent(Context context, int quality,
74            int minLength, final int maxLength, boolean requirePasswordToDecrypt,
75            boolean confirmCredentials) {
76        Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
77        intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
78        intent.putExtra(PASSWORD_MIN_KEY, minLength);
79        intent.putExtra(PASSWORD_MAX_KEY, maxLength);
80        intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
81        intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
82        return intent;
83    }
84
85    public static Intent createIntent(Context context, int quality,
86            int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password) {
87        Intent intent = createIntent(context, quality, minLength, maxLength,
88                requirePasswordToDecrypt, false);
89        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
90        return intent;
91    }
92
93    public static Intent createIntent(Context context, int quality,
94            int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge) {
95        Intent intent = createIntent(context, quality, minLength, maxLength,
96                requirePasswordToDecrypt, false);
97        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
98        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
99        return intent;
100    }
101
102    @Override
103    protected boolean isValidFragment(String fragmentName) {
104        if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
105        return false;
106    }
107
108    /* package */ Class<? extends Fragment> getFragmentClass() {
109        return ChooseLockPasswordFragment.class;
110    }
111
112    @Override
113    public void onCreate(Bundle savedInstanceState) {
114        // TODO: Fix on phones
115        // Disable IME on our window since we provide our own keyboard
116        //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
117                //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
118        super.onCreate(savedInstanceState);
119        CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
120        setTitle(msg);
121    }
122
123    public static class ChooseLockPasswordFragment extends InstrumentedFragment
124            implements OnClickListener, OnEditorActionListener,  TextWatcher,
125            SaveAndFinishWorker.Listener {
126        private static final String KEY_FIRST_PIN = "first_pin";
127        private static final String KEY_UI_STAGE = "ui_stage";
128        private static final String KEY_CURRENT_PASSWORD = "current_password";
129        private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
130
131        private String mCurrentPassword;
132        private String mChosenPassword;
133        private boolean mHasChallenge;
134        private long mChallenge;
135        private TextView mPasswordEntry;
136        private TextViewInputDisabler mPasswordEntryInputDisabler;
137        private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
138        private int mPasswordMaxLength = 16;
139        private int mPasswordMinLetters = 0;
140        private int mPasswordMinUpperCase = 0;
141        private int mPasswordMinLowerCase = 0;
142        private int mPasswordMinSymbols = 0;
143        private int mPasswordMinNumeric = 0;
144        private int mPasswordMinNonLetter = 0;
145        private LockPatternUtils mLockPatternUtils;
146        private SaveAndFinishWorker mSaveAndFinishWorker;
147        private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
148        private ChooseLockSettingsHelper mChooseLockSettingsHelper;
149        private Stage mUiStage = Stage.Introduction;
150
151        private TextView mHeaderText;
152        private String mFirstPin;
153        private KeyboardView mKeyboardView;
154        private PasswordEntryKeyboardHelper mKeyboardHelper;
155        private boolean mIsAlphaMode;
156        private Button mCancelButton;
157        private Button mNextButton;
158        private static final int CONFIRM_EXISTING_REQUEST = 58;
159        static final int RESULT_FINISHED = RESULT_FIRST_USER;
160        private static final long ERROR_MESSAGE_TIMEOUT = 3000;
161        private static final int MSG_SHOW_ERROR = 1;
162
163        private Handler mHandler = new Handler() {
164            @Override
165            public void handleMessage(Message msg) {
166                if (msg.what == MSG_SHOW_ERROR) {
167                    updateStage((Stage) msg.obj);
168                }
169            }
170        };
171
172        /**
173         * Keep track internally of where the user is in choosing a pattern.
174         */
175        protected enum Stage {
176
177            Introduction(R.string.lockpassword_choose_your_password_header,
178                    R.string.lockpassword_choose_your_pin_header,
179                    R.string.lockpassword_continue_label),
180
181            NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
182                    R.string.lockpassword_confirm_your_pin_header,
183                    R.string.lockpassword_ok_label),
184
185            ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
186                    R.string.lockpassword_confirm_pins_dont_match,
187                    R.string.lockpassword_continue_label);
188
189            Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
190                this.alphaHint = hintInAlpha;
191                this.numericHint = hintInNumeric;
192                this.buttonText = nextButtonText;
193            }
194
195            public final int alphaHint;
196            public final int numericHint;
197            public final int buttonText;
198        }
199
200        // required constructor for fragments
201        public ChooseLockPasswordFragment() {
202
203        }
204
205        @Override
206        public void onCreate(Bundle savedInstanceState) {
207            super.onCreate(savedInstanceState);
208            mLockPatternUtils = new LockPatternUtils(getActivity());
209            Intent intent = getActivity().getIntent();
210            if (!(getActivity() instanceof ChooseLockPassword)) {
211                throw new SecurityException("Fragment contained in wrong activity");
212            }
213            mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
214                    mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality(
215                    UserHandle.myUserId()));
216            mPasswordMinLength = Math.max(Math.max(
217                    LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
218                    intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
219                    mLockPatternUtils.getRequestedMinimumPasswordLength(UserHandle.myUserId()));
220            mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
221            mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
222                    mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
223                    UserHandle.myUserId()));
224            mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
225                    mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
226                    UserHandle.myUserId()));
227            mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
228                    mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
229                    UserHandle.myUserId()));
230            mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
231                    mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
232                    UserHandle.myUserId()));
233            mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
234                    mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
235                    UserHandle.myUserId()));
236            mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
237                    mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
238                    UserHandle.myUserId()));
239
240            mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
241        }
242
243        @Override
244        public View onCreateView(LayoutInflater inflater, ViewGroup container,
245                Bundle savedInstanceState) {
246            return inflater.inflate(R.layout.choose_lock_password, container, false);
247        }
248
249        @Override
250        public void onViewCreated(View view, Bundle savedInstanceState) {
251            super.onViewCreated(view, savedInstanceState);
252
253            mCancelButton = (Button) view.findViewById(R.id.cancel_button);
254            mCancelButton.setOnClickListener(this);
255            mNextButton = (Button) view.findViewById(R.id.next_button);
256            mNextButton.setOnClickListener(this);
257
258            mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
259                    || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
260                    || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
261            mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
262            mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
263            mPasswordEntry.setOnEditorActionListener(this);
264            mPasswordEntry.addTextChangedListener(this);
265            mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
266
267            final Activity activity = getActivity();
268            mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
269                    mKeyboardView, mPasswordEntry);
270            mKeyboardHelper.setKeyboardMode(mIsAlphaMode ?
271                    PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
272                    : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
273
274            mHeaderText = (TextView) view.findViewById(R.id.headerText);
275            mKeyboardView.requestFocus();
276
277            int currentType = mPasswordEntry.getInputType();
278            mPasswordEntry.setInputType(mIsAlphaMode ? currentType
279                    : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
280
281            Intent intent = getActivity().getIntent();
282            final boolean confirmCredentials = intent.getBooleanExtra(
283                    ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
284            mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
285            mHasChallenge = intent.getBooleanExtra(
286                    ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
287            mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
288            if (savedInstanceState == null) {
289                updateStage(Stage.Introduction);
290                if (confirmCredentials) {
291                    mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
292                            getString(R.string.unlock_set_unlock_launch_picker_title), true);
293                }
294            } else {
295                // restore from previous state
296                mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
297                final String state = savedInstanceState.getString(KEY_UI_STAGE);
298                if (state != null) {
299                    mUiStage = Stage.valueOf(state);
300                    updateStage(mUiStage);
301                }
302
303                if (mCurrentPassword == null) {
304                    mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD);
305                }
306
307                // Re-attach to the exiting worker if there is one.
308                mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
309                        FRAGMENT_TAG_SAVE_AND_FINISH);
310            }
311            if (activity instanceof SettingsActivity) {
312                final SettingsActivity sa = (SettingsActivity) activity;
313                int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
314                        : R.string.lockpassword_choose_your_pin_header;
315                CharSequence title = getText(id);
316                sa.setTitle(title);
317            }
318        }
319
320        @Override
321        protected int getMetricsCategory() {
322            return MetricsLogger.CHOOSE_LOCK_PASSWORD;
323        }
324
325        @Override
326        public void onResume() {
327            super.onResume();
328            updateStage(mUiStage);
329            if (mSaveAndFinishWorker != null) {
330                mSaveAndFinishWorker.setListener(this);
331            } else {
332                mKeyboardView.requestFocus();
333            }
334        }
335
336        @Override
337        public void onPause() {
338            mHandler.removeMessages(MSG_SHOW_ERROR);
339            if (mSaveAndFinishWorker != null) {
340                mSaveAndFinishWorker.setListener(null);
341            }
342
343            super.onPause();
344        }
345
346        @Override
347        public void onSaveInstanceState(Bundle outState) {
348            super.onSaveInstanceState(outState);
349            outState.putString(KEY_UI_STAGE, mUiStage.name());
350            outState.putString(KEY_FIRST_PIN, mFirstPin);
351            outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword);
352        }
353
354        @Override
355        public void onActivityResult(int requestCode, int resultCode,
356                Intent data) {
357            super.onActivityResult(requestCode, resultCode, data);
358            switch (requestCode) {
359                case CONFIRM_EXISTING_REQUEST:
360                    if (resultCode != Activity.RESULT_OK) {
361                        getActivity().setResult(RESULT_FINISHED);
362                        getActivity().finish();
363                    } else {
364                        mCurrentPassword = data.getStringExtra(
365                                ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
366                    }
367                    break;
368            }
369        }
370
371        protected Intent getRedactionInterstitialIntent(Context context) {
372            return RedactionInterstitial.createStartIntent(context);
373        }
374
375        protected void updateStage(Stage stage) {
376            final Stage previousStage = mUiStage;
377            mUiStage = stage;
378            updateUi();
379
380            // If the stage changed, announce the header for accessibility. This
381            // is a no-op when accessibility is disabled.
382            if (previousStage != stage) {
383                mHeaderText.announceForAccessibility(mHeaderText.getText());
384            }
385        }
386
387        /**
388         * Validates PIN and returns a message to display if PIN fails test.
389         * @param password the raw password the user typed in
390         * @return error message to show to user or null if password is OK
391         */
392        private String validatePassword(String password) {
393            if (password.length() < mPasswordMinLength) {
394                return getString(mIsAlphaMode ?
395                        R.string.lockpassword_password_too_short
396                        : R.string.lockpassword_pin_too_short, mPasswordMinLength);
397            }
398            if (password.length() > mPasswordMaxLength) {
399                return getString(mIsAlphaMode ?
400                        R.string.lockpassword_password_too_long
401                        : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1);
402            }
403            int letters = 0;
404            int numbers = 0;
405            int lowercase = 0;
406            int symbols = 0;
407            int uppercase = 0;
408            int nonletter = 0;
409            for (int i = 0; i < password.length(); i++) {
410                char c = password.charAt(i);
411                // allow non control Latin-1 characters only
412                if (c < 32 || c > 127) {
413                    return getString(R.string.lockpassword_illegal_character);
414                }
415                if (c >= '0' && c <= '9') {
416                    numbers++;
417                    nonletter++;
418                } else if (c >= 'A' && c <= 'Z') {
419                    letters++;
420                    uppercase++;
421                } else if (c >= 'a' && c <= 'z') {
422                    letters++;
423                    lowercase++;
424                } else {
425                    symbols++;
426                    nonletter++;
427                }
428            }
429            if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
430                    || DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) {
431                if (letters > 0 || symbols > 0) {
432                    // This shouldn't be possible unless user finds some way to bring up
433                    // soft keyboard
434                    return getString(R.string.lockpassword_pin_contains_non_digits);
435                }
436                // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
437                final int sequence = LockPatternUtils.maxLengthSequence(password);
438                if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality
439                        && sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
440                    return getString(R.string.lockpassword_pin_no_sequential_digits);
441                }
442            } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
443                if (letters < mPasswordMinLetters) {
444                    return String.format(getResources().getQuantityString(
445                            R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
446                            mPasswordMinLetters);
447                } else if (numbers < mPasswordMinNumeric) {
448                    return String.format(getResources().getQuantityString(
449                            R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
450                            mPasswordMinNumeric);
451                } else if (lowercase < mPasswordMinLowerCase) {
452                    return String.format(getResources().getQuantityString(
453                            R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
454                            mPasswordMinLowerCase);
455                } else if (uppercase < mPasswordMinUpperCase) {
456                    return String.format(getResources().getQuantityString(
457                            R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
458                            mPasswordMinUpperCase);
459                } else if (symbols < mPasswordMinSymbols) {
460                    return String.format(getResources().getQuantityString(
461                            R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
462                            mPasswordMinSymbols);
463                } else if (nonletter < mPasswordMinNonLetter) {
464                    return String.format(getResources().getQuantityString(
465                            R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
466                            mPasswordMinNonLetter);
467                }
468            } else {
469                final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
470                        == mRequestedQuality;
471                final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
472                        == mRequestedQuality;
473                if ((alphabetic || alphanumeric) && letters == 0) {
474                    return getString(R.string.lockpassword_password_requires_alpha);
475                }
476                if (alphanumeric && numbers == 0) {
477                    return getString(R.string.lockpassword_password_requires_digit);
478                }
479            }
480            if(mLockPatternUtils.checkPasswordHistory(password, UserHandle.myUserId())) {
481                return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
482                        : R.string.lockpassword_pin_recently_used);
483            }
484
485            return null;
486        }
487
488        public void handleNext() {
489            if (mSaveAndFinishWorker != null) return;
490            mChosenPassword = mPasswordEntry.getText().toString();
491            if (TextUtils.isEmpty(mChosenPassword)) {
492                return;
493            }
494            String errorMsg = null;
495            if (mUiStage == Stage.Introduction) {
496                errorMsg = validatePassword(mChosenPassword);
497                if (errorMsg == null) {
498                    mFirstPin = mChosenPassword;
499                    mPasswordEntry.setText("");
500                    updateStage(Stage.NeedToConfirm);
501                }
502            } else if (mUiStage == Stage.NeedToConfirm) {
503                if (mFirstPin.equals(mChosenPassword)) {
504                    startSaveAndFinish();
505                } else {
506                    CharSequence tmp = mPasswordEntry.getText();
507                    if (tmp != null) {
508                        Selection.setSelection((Spannable) tmp, 0, tmp.length());
509                    }
510                    updateStage(Stage.ConfirmWrong);
511                }
512            }
513            if (errorMsg != null) {
514                showError(errorMsg, mUiStage);
515            }
516        }
517
518        protected void setNextEnabled(boolean enabled) {
519            mNextButton.setEnabled(enabled);
520        }
521
522        protected void setNextText(int text) {
523            mNextButton.setText(text);
524        }
525
526        public void onClick(View v) {
527            switch (v.getId()) {
528                case R.id.next_button:
529                    handleNext();
530                    break;
531
532                case R.id.cancel_button:
533                    getActivity().finish();
534                    break;
535            }
536        }
537
538        private void showError(String msg, final Stage next) {
539            mHeaderText.setText(msg);
540            mHeaderText.announceForAccessibility(mHeaderText.getText());
541            Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
542            mHandler.removeMessages(MSG_SHOW_ERROR);
543            mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
544        }
545
546        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
547            // Check if this was the result of hitting the enter or "done" key
548            if (actionId == EditorInfo.IME_NULL
549                    || actionId == EditorInfo.IME_ACTION_DONE
550                    || actionId == EditorInfo.IME_ACTION_NEXT) {
551                handleNext();
552                return true;
553            }
554            return false;
555        }
556
557        /**
558         * Update the hint based on current Stage and length of password entry
559         */
560        private void updateUi() {
561            final boolean canInput = mSaveAndFinishWorker == null;
562            String password = mPasswordEntry.getText().toString();
563            final int length = password.length();
564            if (mUiStage == Stage.Introduction) {
565                if (length < mPasswordMinLength) {
566                    String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
567                            : R.string.lockpassword_pin_too_short, mPasswordMinLength);
568                    mHeaderText.setText(msg);
569                    setNextEnabled(false);
570                } else {
571                    String error = validatePassword(password);
572                    if (error != null) {
573                        mHeaderText.setText(error);
574                        setNextEnabled(false);
575                    } else {
576                        mHeaderText.setText(R.string.lockpassword_press_continue);
577                        setNextEnabled(true);
578                    }
579                }
580            } else {
581                mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
582                setNextEnabled(canInput && length > 0);
583            }
584            setNextText(mUiStage.buttonText);
585            mPasswordEntryInputDisabler.setInputEnabled(canInput);
586        }
587
588        public void afterTextChanged(Editable s) {
589            // Changing the text while error displayed resets to NeedToConfirm state
590            if (mUiStage == Stage.ConfirmWrong) {
591                mUiStage = Stage.NeedToConfirm;
592            }
593            updateUi();
594        }
595
596        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
597
598        }
599
600        public void onTextChanged(CharSequence s, int start, int before, int count) {
601
602        }
603
604        private void startSaveAndFinish() {
605            if (mSaveAndFinishWorker != null) {
606                Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
607                return;
608            }
609
610            mPasswordEntryInputDisabler.setInputEnabled(false);
611            setNextEnabled(false);
612
613            mSaveAndFinishWorker = new SaveAndFinishWorker();
614            getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
615                    FRAGMENT_TAG_SAVE_AND_FINISH).commit();
616            mSaveAndFinishWorker.setListener(this);
617
618            final boolean required = getActivity().getIntent().getBooleanExtra(
619                    EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
620            mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
621                    mChosenPassword, mCurrentPassword, mRequestedQuality);
622        }
623
624        @Override
625        public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
626            getActivity().setResult(RESULT_FINISHED, resultData);
627            getActivity().finish();
628
629            if (!wasSecureBefore) {
630                Intent intent = getRedactionInterstitialIntent(getActivity());
631                if (intent != null) {
632                    startActivity(intent);
633                }
634            }
635        }
636    }
637
638    private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
639
640        private String mChosenPassword;
641        private String mCurrentPassword;
642        private int mRequestedQuality;
643
644        public void start(LockPatternUtils utils, boolean required,
645                boolean hasChallenge, long challenge,
646                String chosenPassword, String currentPassword, int requestedQuality) {
647            prepare(utils, required, hasChallenge, challenge);
648
649            mChosenPassword = chosenPassword;
650            mCurrentPassword = currentPassword;
651            mRequestedQuality = requestedQuality;
652
653            start();
654        }
655
656        @Override
657        protected Intent saveAndVerifyInBackground() {
658            Intent result = null;
659            final int userId = UserHandle.myUserId();
660            mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality,
661                    userId);
662
663            if (mHasChallenge) {
664                byte[] token;
665                try {
666                    token = mUtils.verifyPassword(mChosenPassword, mChallenge, userId);
667                } catch (RequestThrottledException e) {
668                    token = null;
669                }
670
671                if (token == null) {
672                    Log.e(TAG, "critical: no token returned for known good password.");
673                }
674
675                result = new Intent();
676                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
677            }
678
679            return result;
680        }
681    }
682}
683