ChooseLockPassword.java revision 015c186a2466ad1bee01952ef067ba8d7e21cab7
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.widget.LockPatternUtils;
20import com.android.internal.widget.PasswordEntryKeyboardHelper;
21import com.android.internal.widget.PasswordEntryKeyboardView;
22
23import android.app.Activity;
24import android.app.admin.DevicePolicyManager;
25import android.content.Intent;
26import android.inputmethodservice.KeyboardView;
27import android.os.Bundle;
28import android.os.Handler;
29import android.text.Editable;
30import android.text.Selection;
31import android.text.Spannable;
32import android.text.TextUtils;
33import android.text.TextWatcher;
34import android.view.KeyEvent;
35import android.view.View;
36import android.view.WindowManager;
37import android.view.View.OnClickListener;
38import android.view.inputmethod.EditorInfo;
39import android.widget.Button;
40import android.widget.TextView;
41import android.widget.TextView.OnEditorActionListener;
42
43
44public class ChooseLockPassword extends Activity implements OnClickListener, OnEditorActionListener,
45        TextWatcher {
46    private static final String KEY_FIRST_PIN = "first_pin";
47    private static final String KEY_UI_STAGE = "ui_stage";
48    private TextView mPasswordEntry;
49    private int mPasswordMinLength = 4;
50    private int mPasswordMaxLength = 16;
51    private LockPatternUtils mLockPatternUtils;
52    private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
53    private ChooseLockSettingsHelper mChooseLockSettingsHelper;
54    private com.android.settings.ChooseLockPassword.Stage mUiStage = Stage.Introduction;
55    private TextView mHeaderText;
56    private String mFirstPin;
57    private KeyboardView mKeyboardView;
58    private PasswordEntryKeyboardHelper mKeyboardHelper;
59    private boolean mIsAlphaMode;
60    private Button mCancelButton;
61    private Button mNextButton;
62    public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
63    public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
64    private static Handler mHandler = new Handler();
65    private static final int CONFIRM_EXISTING_REQUEST = 58;
66    static final int RESULT_FINISHED = RESULT_FIRST_USER;
67    private static final long ERROR_MESSAGE_TIMEOUT = 3000;
68
69    /**
70     * Keep track internally of where the user is in choosing a pattern.
71     */
72    protected enum Stage {
73
74        Introduction(R.string.lockpassword_choose_your_password_header,
75                R.string.lockpassword_choose_your_pin_header,
76                R.string.lockpassword_continue_label),
77
78        NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
79                R.string.lockpassword_confirm_your_pin_header,
80                R.string.lockpassword_ok_label),
81
82        ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
83                R.string.lockpassword_confirm_pins_dont_match,
84                R.string.lockpassword_continue_label);
85
86        /**
87         * @param headerMessage The message displayed at the top.
88         */
89        Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
90            this.alphaHint = hintInAlpha;
91            this.numericHint = hintInNumeric;
92            this.buttonText = nextButtonText;
93        }
94
95        public final int alphaHint;
96        public final int numericHint;
97        public final int buttonText;
98    }
99
100    @Override
101    protected void onCreate(Bundle savedInstanceState) {
102        super.onCreate(savedInstanceState);
103        mLockPatternUtils = new LockPatternUtils(this);
104        mRequestedQuality = getIntent().getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality);
105        mPasswordMinLength = getIntent().getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength);
106        mPasswordMaxLength = getIntent().getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
107
108        final boolean confirmCredentials = getIntent().getBooleanExtra("confirm_credentials", true);
109        int minMode = mLockPatternUtils.getRequestedPasswordQuality();
110        if (mRequestedQuality < minMode) {
111            mRequestedQuality = minMode;
112        }
113        int minLength = mLockPatternUtils.getRequestedMinimumPasswordLength();
114        if (mPasswordMinLength < minLength) {
115            mPasswordMinLength = minLength;
116        }
117        initViews();
118        mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this);
119        if (savedInstanceState == null) {
120            updateStage(Stage.Introduction);
121            if (confirmCredentials) {
122                mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
123                        null, null);
124            }
125        }
126    }
127
128    private void initViews() {
129        setContentView(R.layout.choose_lock_password);
130        // Disable IME on our window since we provide our own keyboard
131        getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
132                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
133
134        mCancelButton = (Button) findViewById(R.id.cancel_button);
135        mCancelButton.setOnClickListener(this);
136        mNextButton = (Button) findViewById(R.id.next_button);
137        mNextButton.setOnClickListener(this);
138
139        mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
140        mPasswordEntry = (TextView) findViewById(R.id.password_entry);
141        mPasswordEntry.setOnEditorActionListener(this);
142        mPasswordEntry.addTextChangedListener(this);
143
144        mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
145            || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality;
146        mKeyboardHelper = new PasswordEntryKeyboardHelper(this, mKeyboardView, mPasswordEntry);
147        mKeyboardHelper.setKeyboardMode(mIsAlphaMode ?
148                PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
149                : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
150
151        mHeaderText = (TextView) findViewById(R.id.headerText);
152        mKeyboardView.requestFocus();
153    }
154
155    @Override
156    protected void onResume() {
157        super.onResume();
158        updateStage(mUiStage);
159        mKeyboardView.requestFocus();
160    }
161
162    @Override
163    protected void onSaveInstanceState(Bundle outState) {
164        super.onSaveInstanceState(outState);
165        outState.putString(KEY_UI_STAGE, mUiStage.name());
166        outState.putString(KEY_FIRST_PIN, mFirstPin);
167    }
168
169    @Override
170    protected void onRestoreInstanceState(Bundle savedInstanceState) {
171        super.onRestoreInstanceState(savedInstanceState);
172        String state = savedInstanceState.getString(KEY_UI_STAGE);
173        mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
174        if (state != null) {
175            mUiStage = Stage.valueOf(state);
176            updateStage(mUiStage);
177        }
178    }
179
180    @Override
181    protected void onActivityResult(int requestCode, int resultCode,
182            Intent data) {
183        super.onActivityResult(requestCode, resultCode, data);
184        switch (requestCode) {
185            case CONFIRM_EXISTING_REQUEST:
186                if (resultCode != Activity.RESULT_OK) {
187                    setResult(RESULT_FINISHED);
188                    finish();
189                }
190                break;
191        }
192    }
193
194    protected void updateStage(Stage stage) {
195        mUiStage = stage;
196        updateUi();
197    }
198
199    /**
200     * Validates PIN and returns a message to display if PIN fails test.
201     * @param password the raw password the user typed in
202     * @return error message to show to user or null if password is OK
203     */
204    private String validatePassword(String password) {
205        if (password.length() < mPasswordMinLength) {
206            return getString(mIsAlphaMode ?
207                    R.string.lockpassword_password_too_short
208                    : R.string.lockpassword_pin_too_short, mPasswordMinLength);
209        }
210        if (password.length() > mPasswordMaxLength) {
211            return getString(mIsAlphaMode ?
212                    R.string.lockpassword_password_too_long
213                    : R.string.lockpassword_pin_too_long, mPasswordMaxLength);
214        }
215        boolean hasAlpha = false;
216        boolean hasDigit = false;
217        boolean hasSymbol = false;
218        for (int i = 0; i < password.length(); i++) {
219            char c = password.charAt(i);
220            // allow non white space Latin-1 characters only
221            if (c <= 32 || c > 127) {
222                return getString(R.string.lockpassword_illegal_character);
223            }
224            if (c >= '0' && c <= '9') {
225                hasDigit = true;
226            } else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
227                hasAlpha = true;
228            } else {
229                hasSymbol = true;
230            }
231        }
232        if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
233                && (hasAlpha | hasSymbol)) {
234            // This shouldn't be possible unless user finds some way to bring up soft keyboard
235            return getString(R.string.lockpassword_pin_contains_non_digits);
236        } else {
237            final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
238                    == mRequestedQuality;
239            final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
240                    == mRequestedQuality;
241            final boolean symbolic = false; // not yet
242            if ((alphabetic || alphanumeric) && !hasAlpha) {
243                return getString(R.string.lockpassword_password_requires_alpha);
244            }
245            if (alphanumeric && !hasDigit) {
246                return getString(R.string.lockpassword_password_requires_digit);
247            }
248            if (symbolic && !hasSymbol) {
249                return getString(R.string.lockpassword_password_requires_symbol);
250            }
251        }
252        if(mLockPatternUtils.checkPasswordHistory(password)) {
253            return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
254                    : R.string.lockpassword_pin_recently_used);
255        }
256        return null;
257    }
258
259    private void handleNext() {
260        final String pin = mPasswordEntry.getText().toString();
261        if (TextUtils.isEmpty(pin)) {
262            return;
263        }
264        String errorMsg = null;
265        if (mUiStage == Stage.Introduction) {
266            errorMsg = validatePassword(pin);
267            if (errorMsg == null) {
268                mFirstPin = pin;
269                updateStage(Stage.NeedToConfirm);
270                mPasswordEntry.setText("");
271            }
272        } else if (mUiStage == Stage.NeedToConfirm) {
273            if (mFirstPin.equals(pin)) {
274                mLockPatternUtils.clearLock();
275                mLockPatternUtils.saveLockPassword(pin, mRequestedQuality);
276                finish();
277            } else {
278                updateStage(Stage.ConfirmWrong);
279                CharSequence tmp = mPasswordEntry.getText();
280                if (tmp != null) {
281                    Selection.setSelection((Spannable) tmp, 0, tmp.length());
282                }
283            }
284        }
285        if (errorMsg != null) {
286            showError(errorMsg, mUiStage);
287        }
288    }
289
290    public void onClick(View v) {
291        switch (v.getId()) {
292            case R.id.next_button:
293                handleNext();
294                break;
295
296            case R.id.cancel_button:
297                finish();
298                break;
299        }
300    }
301
302    private void showError(String msg, final Stage next) {
303        mHeaderText.setText(msg);
304        mHandler.postDelayed(new Runnable() {
305            public void run() {
306                updateStage(next);
307            }
308        }, ERROR_MESSAGE_TIMEOUT);
309    }
310
311    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
312        // Check if this was the result of hitting the enter key
313        if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) {
314            handleNext();
315            return true;
316        }
317        return false;
318    }
319
320    /**
321     * Update the hint based on current Stage and length of password entry
322     */
323    private void updateUi() {
324        String password = mPasswordEntry.getText().toString();
325        final int length = password.length();
326        if (mUiStage == Stage.Introduction && length > 0) {
327            if (length < mPasswordMinLength) {
328                String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
329                        : R.string.lockpassword_pin_too_short, mPasswordMinLength);
330                mHeaderText.setText(msg);
331                mNextButton.setEnabled(false);
332            } else {
333                String error = validatePassword(password);
334                if (error != null) {
335                    mHeaderText.setText(error);
336                    mNextButton.setEnabled(false);
337                } else {
338                    mHeaderText.setText(R.string.lockpassword_press_continue);
339                    mNextButton.setEnabled(true);
340                }
341            }
342        } else {
343            mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
344            mNextButton.setEnabled(length > 0);
345        }
346        mNextButton.setText(mUiStage.buttonText);
347    }
348
349    public void afterTextChanged(Editable s) {
350        // Changing the text while error displayed resets to NeedToConfirm state
351        if (mUiStage == Stage.ConfirmWrong) {
352            mUiStage = Stage.NeedToConfirm;
353        }
354        updateUi();
355    }
356
357    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
358
359    }
360
361    public void onTextChanged(CharSequence s, int start, int before, int count) {
362
363    }
364}
365