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