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