ConfirmLockPassword.java revision 3ea423ae0ff56d249b6844b3a68c67ee5eba243d
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.text.TextUtils;
20import com.android.internal.widget.LockPatternUtils;
21import com.android.internal.widget.PasswordEntryKeyboardHelper;
22import com.android.internal.widget.PasswordEntryKeyboardView;
23
24import android.app.Activity;
25import android.app.Fragment;
26import android.app.admin.DevicePolicyManager;
27import android.content.Intent;
28import android.os.Bundle;
29import android.os.CountDownTimer;
30import android.os.Handler;
31import android.os.SystemClock;
32import android.os.storage.StorageManager;
33import android.text.Editable;
34import android.text.InputType;
35import android.text.TextWatcher;
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
46public class ConfirmLockPassword extends SettingsActivity {
47
48    public static final String PACKAGE = "com.android.settings";
49    public static final String HEADER_TEXT = PACKAGE + ".ConfirmLockPattern.header";
50
51    public static class InternalActivity extends ConfirmLockPassword {
52    }
53
54    @Override
55    public Intent getIntent() {
56        Intent modIntent = new Intent(super.getIntent());
57        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
58        return modIntent;
59    }
60
61    @Override
62    protected boolean isValidFragment(String fragmentName) {
63        if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true;
64        return false;
65    }
66
67    @Override
68    public void onCreate(Bundle savedInstanceState) {
69        // Disable IME on our window since we provide our own keyboard
70        //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
71                //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
72        super.onCreate(savedInstanceState);
73        CharSequence msg = getText(R.string.lockpassword_confirm_your_password_header);
74        setTitle(msg);
75    }
76
77    public static class ConfirmLockPasswordFragment extends Fragment implements OnClickListener,
78            OnEditorActionListener, TextWatcher {
79        private static final String KEY_NUM_WRONG_CONFIRM_ATTEMPTS
80                = "confirm_lock_password_fragment.key_num_wrong_confirm_attempts";
81        private static final long ERROR_MESSAGE_TIMEOUT = 3000;
82        private TextView mPasswordEntry;
83        private LockPatternUtils mLockPatternUtils;
84        private TextView mHeaderText;
85        private Handler mHandler = new Handler();
86        private PasswordEntryKeyboardHelper mKeyboardHelper;
87        private PasswordEntryKeyboardView mKeyboardView;
88        private Button mContinueButton;
89        private int mNumWrongConfirmAttempts;
90        private CountDownTimer mCountdownTimer;
91        private boolean mIsAlpha;
92
93        // required constructor for fragments
94        public ConfirmLockPasswordFragment() {
95
96        }
97
98        @Override
99        public void onCreate(Bundle savedInstanceState) {
100            super.onCreate(savedInstanceState);
101            mLockPatternUtils = new LockPatternUtils(getActivity());
102            if (savedInstanceState != null) {
103                mNumWrongConfirmAttempts = savedInstanceState.getInt(
104                        KEY_NUM_WRONG_CONFIRM_ATTEMPTS, 0);
105            }
106        }
107
108        @Override
109        public View onCreateView(LayoutInflater inflater, ViewGroup container,
110                Bundle savedInstanceState) {
111            final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality();
112            View view = inflater.inflate(R.layout.confirm_lock_password, null);
113            // Disable IME on our window since we provide our own keyboard
114
115            view.findViewById(R.id.cancel_button).setOnClickListener(this);
116            mContinueButton = (Button) view.findViewById(R.id.next_button);
117            mContinueButton.setOnClickListener(this);
118            mContinueButton.setEnabled(false); // disable until the user enters at least one char
119
120            mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
121            mPasswordEntry.setOnEditorActionListener(this);
122            mPasswordEntry.addTextChangedListener(this);
123
124            mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
125            mHeaderText = (TextView) view.findViewById(R.id.headerText);
126            mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
127                    || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
128                    || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality;
129
130            Intent intent = getActivity().getIntent();
131            if (intent != null) {
132                CharSequence headerMessage = intent.getCharSequenceExtra(HEADER_TEXT);
133                if (TextUtils.isEmpty(headerMessage)) {
134                    headerMessage = getString(getDefaultHeader());
135                }
136                mHeaderText.setText(headerMessage);
137            }
138
139            final Activity activity = getActivity();
140            mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
141                    mKeyboardView, mPasswordEntry);
142            mKeyboardHelper.setKeyboardMode(mIsAlpha ?
143                    PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
144                    : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
145            mKeyboardView.requestFocus();
146
147            int currentType = mPasswordEntry.getInputType();
148            mPasswordEntry.setInputType(mIsAlpha ? currentType
149                    : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
150
151            if (activity instanceof SettingsActivity) {
152                final SettingsActivity sa = (SettingsActivity) activity;
153                int id = getDefaultHeader();
154                CharSequence title = getText(id);
155                sa.setTitle(title);
156            }
157
158            return view;
159        }
160
161        private int getDefaultHeader() {
162            return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
163                    : R.string.lockpassword_confirm_your_pin_header;
164        }
165
166        @Override
167        public void onPause() {
168            super.onPause();
169            mKeyboardView.requestFocus();
170            if (mCountdownTimer != null) {
171                mCountdownTimer.cancel();
172                mCountdownTimer = null;
173            }
174        }
175
176        @Override
177        public void onResume() {
178            // TODO Auto-generated method stub
179            super.onResume();
180            mKeyboardView.requestFocus();
181            long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
182            if (deadline != 0) {
183                handleAttemptLockout(deadline);
184            }
185        }
186
187        @Override
188        public void onSaveInstanceState(Bundle outState) {
189            super.onSaveInstanceState(outState);
190            outState.putInt(KEY_NUM_WRONG_CONFIRM_ATTEMPTS, mNumWrongConfirmAttempts);
191        }
192
193        private void handleNext() {
194            final String pin = mPasswordEntry.getText().toString();
195            if (mLockPatternUtils.checkPassword(pin)) {
196
197                Intent intent = new Intent();
198                if (getActivity() instanceof ConfirmLockPassword.InternalActivity) {
199                    intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
200                                    mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
201                                             : StorageManager.CRYPT_TYPE_PIN);
202                    intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
203                }
204
205                getActivity().setResult(RESULT_OK, intent);
206                getActivity().finish();
207            } else {
208                if (++mNumWrongConfirmAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
209                    long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
210                    handleAttemptLockout(deadline);
211                } else {
212                    showError(R.string.lockpattern_need_to_unlock_wrong);
213                }
214            }
215        }
216
217        private void handleAttemptLockout(long elapsedRealtimeDeadline) {
218            long elapsedRealtime = SystemClock.elapsedRealtime();
219            showError(R.string.lockpattern_too_many_failed_confirmation_attempts_header, 0);
220            mPasswordEntry.setEnabled(false);
221            mCountdownTimer = new CountDownTimer(
222                    elapsedRealtimeDeadline - elapsedRealtime,
223                    LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
224
225                @Override
226                public void onTick(long millisUntilFinished) {
227                    final int secondsCountdown = (int) (millisUntilFinished / 1000);
228                    mHeaderText.setText(getString(
229                            R.string.lockpattern_too_many_failed_confirmation_attempts_footer,
230                            secondsCountdown));
231                }
232
233                @Override
234                public void onFinish() {
235                    mPasswordEntry.setEnabled(true);
236                    mHeaderText.setText(getDefaultHeader());
237                    mNumWrongConfirmAttempts = 0;
238                }
239            }.start();
240        }
241
242        public void onClick(View v) {
243            switch (v.getId()) {
244                case R.id.next_button:
245                    handleNext();
246                    break;
247
248                case R.id.cancel_button:
249                    getActivity().setResult(RESULT_CANCELED);
250                    getActivity().finish();
251                    break;
252            }
253        }
254
255        private void showError(int msg) {
256            showError(msg, ERROR_MESSAGE_TIMEOUT);
257        }
258
259        private final Runnable mResetErrorRunnable = new Runnable() {
260            public void run() {
261                mHeaderText.setText(getDefaultHeader());
262            }
263        };
264
265        private void showError(int msg, long timeout) {
266            mHeaderText.setText(msg);
267            mHeaderText.announceForAccessibility(mHeaderText.getText());
268            mPasswordEntry.setText(null);
269            mHandler.removeCallbacks(mResetErrorRunnable);
270            if (timeout != 0) {
271                mHandler.postDelayed(mResetErrorRunnable, timeout);
272            }
273        }
274
275        // {@link OnEditorActionListener} methods.
276        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
277            // Check if this was the result of hitting the enter or "done" key
278            if (actionId == EditorInfo.IME_NULL
279                    || actionId == EditorInfo.IME_ACTION_DONE
280                    || actionId == EditorInfo.IME_ACTION_NEXT) {
281                handleNext();
282                return true;
283            }
284            return false;
285        }
286
287        // {@link TextWatcher} methods.
288        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
289        }
290
291        public void onTextChanged(CharSequence s, int start, int before, int count) {
292        }
293
294        public void afterTextChanged(Editable s) {
295            mContinueButton.setEnabled(mPasswordEntry.getText().length() > 0);
296        }
297    }
298}
299