• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.settings;
18 
19 import com.android.internal.widget.LockPatternUtils;
20 import com.android.internal.widget.PasswordEntryKeyboardHelper;
21 import com.android.internal.widget.PasswordEntryKeyboardView;
22 
23 import android.app.Activity;
24 import android.app.Fragment;
25 import android.app.admin.DevicePolicyManager;
26 import android.content.Intent;
27 import android.inputmethodservice.KeyboardView;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.preference.PreferenceActivity;
32 import android.text.Editable;
33 import android.text.InputType;
34 import android.text.Selection;
35 import android.text.Spannable;
36 import android.text.TextUtils;
37 import android.text.TextWatcher;
38 import android.view.KeyEvent;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.View.OnClickListener;
43 import android.view.accessibility.AccessibilityEvent;
44 import android.view.inputmethod.EditorInfo;
45 import android.widget.Button;
46 import android.widget.TextView;
47 import android.widget.TextView.OnEditorActionListener;
48 
49 public class ChooseLockPassword extends PreferenceActivity {
50     public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
51     public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
52     public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
53     public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
54     public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
55     public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
56     public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
57     public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
58 
59     @Override
getIntent()60     public Intent getIntent() {
61         Intent modIntent = new Intent(super.getIntent());
62         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPasswordFragment.class.getName());
63         modIntent.putExtra(EXTRA_NO_HEADERS, true);
64         return modIntent;
65     }
66 
67     @Override
onCreate(Bundle savedInstanceState)68     public void onCreate(Bundle savedInstanceState) {
69         // TODO: Fix on phones
70         // Disable IME on our window since we provide our own keyboard
71         //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
72                 //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
73         super.onCreate(savedInstanceState);
74         CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
75         showBreadCrumbs(msg, msg);
76     }
77 
78     public static class ChooseLockPasswordFragment extends Fragment
79             implements OnClickListener, OnEditorActionListener,  TextWatcher {
80         private static final String KEY_FIRST_PIN = "first_pin";
81         private static final String KEY_UI_STAGE = "ui_stage";
82         private TextView mPasswordEntry;
83         private int mPasswordMinLength = 4;
84         private int mPasswordMaxLength = 16;
85         private int mPasswordMinLetters = 0;
86         private int mPasswordMinUpperCase = 0;
87         private int mPasswordMinLowerCase = 0;
88         private int mPasswordMinSymbols = 0;
89         private int mPasswordMinNumeric = 0;
90         private int mPasswordMinNonLetter = 0;
91         private LockPatternUtils mLockPatternUtils;
92         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
93         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
94         private Stage mUiStage = Stage.Introduction;
95         private TextView mHeaderText;
96         private String mFirstPin;
97         private KeyboardView mKeyboardView;
98         private PasswordEntryKeyboardHelper mKeyboardHelper;
99         private boolean mIsAlphaMode;
100         private Button mCancelButton;
101         private Button mNextButton;
102         private static final int CONFIRM_EXISTING_REQUEST = 58;
103         static final int RESULT_FINISHED = RESULT_FIRST_USER;
104         private static final long ERROR_MESSAGE_TIMEOUT = 3000;
105         private static final int MSG_SHOW_ERROR = 1;
106 
107         private Handler mHandler = new Handler() {
108             @Override
109             public void handleMessage(Message msg) {
110                 if (msg.what == MSG_SHOW_ERROR) {
111                     updateStage((Stage) msg.obj);
112                 }
113             }
114         };
115 
116         /**
117          * Keep track internally of where the user is in choosing a pattern.
118          */
119         protected enum Stage {
120 
121             Introduction(R.string.lockpassword_choose_your_password_header,
122                     R.string.lockpassword_choose_your_pin_header,
123                     R.string.lockpassword_continue_label),
124 
125             NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
126                     R.string.lockpassword_confirm_your_pin_header,
127                     R.string.lockpassword_ok_label),
128 
129             ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
130                     R.string.lockpassword_confirm_pins_dont_match,
131                     R.string.lockpassword_continue_label);
132 
133             /**
134              * @param headerMessage The message displayed at the top.
135              */
Stage(int hintInAlpha, int hintInNumeric, int nextButtonText)136             Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
137                 this.alphaHint = hintInAlpha;
138                 this.numericHint = hintInNumeric;
139                 this.buttonText = nextButtonText;
140             }
141 
142             public final int alphaHint;
143             public final int numericHint;
144             public final int buttonText;
145         }
146 
147         // required constructor for fragments
ChooseLockPasswordFragment()148         public ChooseLockPasswordFragment() {
149 
150         }
151 
152         @Override
onCreate(Bundle savedInstanceState)153         public void onCreate(Bundle savedInstanceState) {
154             super.onCreate(savedInstanceState);
155             mLockPatternUtils = new LockPatternUtils(getActivity());
156             Intent intent = getActivity().getIntent();
157             mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
158                     mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality());
159             mPasswordMinLength = Math.max(
160                     intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength), mLockPatternUtils
161                             .getRequestedMinimumPasswordLength());
162             mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
163             mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
164                     mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters());
165             mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
166                     mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase());
167             mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
168                     mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase());
169             mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
170                     mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric());
171             mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
172                     mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols());
173             mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
174                     mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter());
175 
176             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
177         }
178 
179         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)180         public View onCreateView(LayoutInflater inflater, ViewGroup container,
181                 Bundle savedInstanceState) {
182 
183             View view = inflater.inflate(R.layout.choose_lock_password, null);
184 
185             mCancelButton = (Button) view.findViewById(R.id.cancel_button);
186             mCancelButton.setOnClickListener(this);
187             mNextButton = (Button) view.findViewById(R.id.next_button);
188             mNextButton.setOnClickListener(this);
189 
190             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
191                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
192                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
193             mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
194             mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
195             mPasswordEntry.setOnEditorActionListener(this);
196             mPasswordEntry.addTextChangedListener(this);
197 
198             final Activity activity = getActivity();
199             mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
200                     mKeyboardView, mPasswordEntry);
201             mKeyboardHelper.setKeyboardMode(mIsAlphaMode ?
202                     PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
203                     : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
204 
205             mHeaderText = (TextView) view.findViewById(R.id.headerText);
206             mKeyboardView.requestFocus();
207 
208             int currentType = mPasswordEntry.getInputType();
209             mPasswordEntry.setInputType(mIsAlphaMode ? currentType
210                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
211 
212             Intent intent = getActivity().getIntent();
213             final boolean confirmCredentials = intent.getBooleanExtra("confirm_credentials", true);
214             if (savedInstanceState == null) {
215                 updateStage(Stage.Introduction);
216                 if (confirmCredentials) {
217                     mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
218                             null, null);
219                 }
220             } else {
221                 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
222                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
223                 if (state != null) {
224                     mUiStage = Stage.valueOf(state);
225                     updateStage(mUiStage);
226                 }
227             }
228             // Update the breadcrumb (title) if this is embedded in a PreferenceActivity
229             if (activity instanceof PreferenceActivity) {
230                 final PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
231                 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
232                         : R.string.lockpassword_choose_your_pin_header;
233                 CharSequence title = getText(id);
234                 preferenceActivity.showBreadCrumbs(title, title);
235             }
236 
237             return view;
238         }
239 
240         @Override
onResume()241         public void onResume() {
242             super.onResume();
243             updateStage(mUiStage);
244             mKeyboardView.requestFocus();
245         }
246 
247         @Override
onPause()248         public void onPause() {
249             mHandler.removeMessages(MSG_SHOW_ERROR);
250 
251             super.onPause();
252         }
253 
254         @Override
onSaveInstanceState(Bundle outState)255         public void onSaveInstanceState(Bundle outState) {
256             super.onSaveInstanceState(outState);
257             outState.putString(KEY_UI_STAGE, mUiStage.name());
258             outState.putString(KEY_FIRST_PIN, mFirstPin);
259         }
260 
261         @Override
onActivityResult(int requestCode, int resultCode, Intent data)262         public void onActivityResult(int requestCode, int resultCode,
263                 Intent data) {
264             super.onActivityResult(requestCode, resultCode, data);
265             switch (requestCode) {
266                 case CONFIRM_EXISTING_REQUEST:
267                     if (resultCode != Activity.RESULT_OK) {
268                         getActivity().setResult(RESULT_FINISHED);
269                         getActivity().finish();
270                     }
271                     break;
272             }
273         }
274 
updateStage(Stage stage)275         protected void updateStage(Stage stage) {
276             final Stage previousStage = mUiStage;
277             mUiStage = stage;
278             updateUi();
279 
280             // If the stage changed, announce the header for accessibility. This
281             // is a no-op when accessibility is disabled.
282             if (previousStage != stage) {
283                 mHeaderText.announceForAccessibility(mHeaderText.getText());
284             }
285         }
286 
287         /**
288          * Validates PIN and returns a message to display if PIN fails test.
289          * @param password the raw password the user typed in
290          * @return error message to show to user or null if password is OK
291          */
validatePassword(String password)292         private String validatePassword(String password) {
293             if (password.length() < mPasswordMinLength) {
294                 return getString(mIsAlphaMode ?
295                         R.string.lockpassword_password_too_short
296                         : R.string.lockpassword_pin_too_short, mPasswordMinLength);
297             }
298             if (password.length() > mPasswordMaxLength) {
299                 return getString(mIsAlphaMode ?
300                         R.string.lockpassword_password_too_long
301                         : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1);
302             }
303             int letters = 0;
304             int numbers = 0;
305             int lowercase = 0;
306             int symbols = 0;
307             int uppercase = 0;
308             int nonletter = 0;
309             for (int i = 0; i < password.length(); i++) {
310                 char c = password.charAt(i);
311                 // allow non control Latin-1 characters only
312                 if (c < 32 || c > 127) {
313                     return getString(R.string.lockpassword_illegal_character);
314                 }
315                 if (c >= '0' && c <= '9') {
316                     numbers++;
317                     nonletter++;
318                 } else if (c >= 'A' && c <= 'Z') {
319                     letters++;
320                     uppercase++;
321                 } else if (c >= 'a' && c <= 'z') {
322                     letters++;
323                     lowercase++;
324                 } else {
325                     symbols++;
326                     nonletter++;
327                 }
328             }
329             if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
330                     && (letters > 0 || symbols > 0)) {
331                 // This shouldn't be possible unless user finds some way to bring up
332                 // soft keyboard
333                 return getString(R.string.lockpassword_pin_contains_non_digits);
334             } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
335                 if (letters < mPasswordMinLetters) {
336                     return String.format(getResources().getQuantityString(
337                             R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
338                             mPasswordMinLetters);
339                 } else if (numbers < mPasswordMinNumeric) {
340                     return String.format(getResources().getQuantityString(
341                             R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
342                             mPasswordMinNumeric);
343                 } else if (lowercase < mPasswordMinLowerCase) {
344                     return String.format(getResources().getQuantityString(
345                             R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
346                             mPasswordMinLowerCase);
347                 } else if (uppercase < mPasswordMinUpperCase) {
348                     return String.format(getResources().getQuantityString(
349                             R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
350                             mPasswordMinUpperCase);
351                 } else if (symbols < mPasswordMinSymbols) {
352                     return String.format(getResources().getQuantityString(
353                             R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
354                             mPasswordMinSymbols);
355                 } else if (nonletter < mPasswordMinNonLetter) {
356                     return String.format(getResources().getQuantityString(
357                             R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
358                             mPasswordMinNonLetter);
359                 }
360             } else {
361                 final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
362                         == mRequestedQuality;
363                 final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
364                         == mRequestedQuality;
365                 if ((alphabetic || alphanumeric) && letters == 0) {
366                     return getString(R.string.lockpassword_password_requires_alpha);
367                 }
368                 if (alphanumeric && numbers == 0) {
369                     return getString(R.string.lockpassword_password_requires_digit);
370                 }
371             }
372             if(mLockPatternUtils.checkPasswordHistory(password)) {
373                 return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
374                         : R.string.lockpassword_pin_recently_used);
375             }
376             return null;
377         }
378 
handleNext()379         private void handleNext() {
380             final String pin = mPasswordEntry.getText().toString();
381             if (TextUtils.isEmpty(pin)) {
382                 return;
383             }
384             String errorMsg = null;
385             if (mUiStage == Stage.Introduction) {
386                 errorMsg = validatePassword(pin);
387                 if (errorMsg == null) {
388                     mFirstPin = pin;
389                     mPasswordEntry.setText("");
390                     updateStage(Stage.NeedToConfirm);
391                 }
392             } else if (mUiStage == Stage.NeedToConfirm) {
393                 if (mFirstPin.equals(pin)) {
394                     final boolean isFallback = getActivity().getIntent().getBooleanExtra(
395                             LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
396                     mLockPatternUtils.clearLock(isFallback);
397                     mLockPatternUtils.saveLockPassword(pin, mRequestedQuality, isFallback);
398                     getActivity().setResult(RESULT_FINISHED);
399                     getActivity().finish();
400                 } else {
401                     CharSequence tmp = mPasswordEntry.getText();
402                     if (tmp != null) {
403                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
404                     }
405                     updateStage(Stage.ConfirmWrong);
406                 }
407             }
408             if (errorMsg != null) {
409                 showError(errorMsg, mUiStage);
410             }
411         }
412 
onClick(View v)413         public void onClick(View v) {
414             switch (v.getId()) {
415                 case R.id.next_button:
416                     handleNext();
417                     break;
418 
419                 case R.id.cancel_button:
420                     getActivity().finish();
421                     break;
422             }
423         }
424 
showError(String msg, final Stage next)425         private void showError(String msg, final Stage next) {
426             mHeaderText.setText(msg);
427             mHeaderText.announceForAccessibility(mHeaderText.getText());
428             Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
429             mHandler.removeMessages(MSG_SHOW_ERROR);
430             mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
431         }
432 
onEditorAction(TextView v, int actionId, KeyEvent event)433         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
434             // Check if this was the result of hitting the enter or "done" key
435             if (actionId == EditorInfo.IME_NULL
436                     || actionId == EditorInfo.IME_ACTION_DONE
437                     || actionId == EditorInfo.IME_ACTION_NEXT) {
438                 handleNext();
439                 return true;
440             }
441             return false;
442         }
443 
444         /**
445          * Update the hint based on current Stage and length of password entry
446          */
updateUi()447         private void updateUi() {
448             String password = mPasswordEntry.getText().toString();
449             final int length = password.length();
450             if (mUiStage == Stage.Introduction && length > 0) {
451                 if (length < mPasswordMinLength) {
452                     String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
453                             : R.string.lockpassword_pin_too_short, mPasswordMinLength);
454                     mHeaderText.setText(msg);
455                     mNextButton.setEnabled(false);
456                 } else {
457                     String error = validatePassword(password);
458                     if (error != null) {
459                         mHeaderText.setText(error);
460                         mNextButton.setEnabled(false);
461                     } else {
462                         mHeaderText.setText(R.string.lockpassword_press_continue);
463                         mNextButton.setEnabled(true);
464                     }
465                 }
466             } else {
467                 mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
468                 mNextButton.setEnabled(length > 0);
469             }
470             mNextButton.setText(mUiStage.buttonText);
471         }
472 
afterTextChanged(Editable s)473         public void afterTextChanged(Editable s) {
474             // Changing the text while error displayed resets to NeedToConfirm state
475             if (mUiStage == Stage.ConfirmWrong) {
476                 mUiStage = Stage.NeedToConfirm;
477             }
478             updateUi();
479         }
480 
beforeTextChanged(CharSequence s, int start, int count, int after)481         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
482 
483         }
484 
onTextChanged(CharSequence s, int start, int before, int count)485         public void onTextChanged(CharSequence s, int start, int before, int count) {
486 
487         }
488     }
489 }
490