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