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