• 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 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.graphics.drawable.InsetDrawable;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.support.v7.widget.LinearLayoutManager;
29 import android.support.v7.widget.RecyclerView;
30 import android.text.Editable;
31 import android.text.InputType;
32 import android.text.Selection;
33 import android.text.Spannable;
34 import android.text.TextUtils;
35 import android.text.TextWatcher;
36 import android.util.Log;
37 import android.view.inputmethod.EditorInfo;
38 import android.view.KeyEvent;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.View.OnClickListener;
42 import android.view.ViewGroup;
43 import android.widget.Button;
44 import android.widget.EditText;
45 import android.widget.LinearLayout;
46 import android.widget.TextView;
47 import android.widget.TextView.OnEditorActionListener;
48 
49 import com.android.internal.logging.MetricsProto.MetricsEvent;
50 import com.android.internal.widget.LockPatternUtils;
51 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
52 import com.android.internal.widget.TextViewInputDisabler;
53 import com.android.settings.notification.RedactionInterstitial;
54 import com.android.settings.password.PasswordRequirementAdapter;
55 import com.android.setupwizardlib.GlifLayout;
56 
57 import java.util.ArrayList;
58 import java.util.List;
59 
60 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
61 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
62 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
63 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
64 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
65 
66 public class ChooseLockPassword extends SettingsActivity {
67     public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
68     public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
69     public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
70     public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
71     public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
72     public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
73     public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
74     public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
75 
76     private static final String TAG = "ChooseLockPassword";
77 
78     @Override
getIntent()79     public Intent getIntent() {
80         Intent modIntent = new Intent(super.getIntent());
81         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
82         return modIntent;
83     }
84 
createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, boolean confirmCredentials)85     public static Intent createIntent(Context context, int quality,
86             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
87             boolean confirmCredentials) {
88         Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
89         intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
90         intent.putExtra(PASSWORD_MIN_KEY, minLength);
91         intent.putExtra(PASSWORD_MAX_KEY, maxLength);
92         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
93         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
94         return intent;
95     }
96 
createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, boolean confirmCredentials, int userId)97     public static Intent createIntent(Context context, int quality,
98             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
99             boolean confirmCredentials, int userId) {
100         Intent intent = createIntent(context, quality, minLength, maxLength,
101                 requirePasswordToDecrypt, confirmCredentials);
102         intent.putExtra(Intent.EXTRA_USER_ID, userId);
103         return intent;
104     }
105 
createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password)106     public static Intent createIntent(Context context, int quality,
107             int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password) {
108         Intent intent = createIntent(context, quality, minLength, maxLength,
109                 requirePasswordToDecrypt, false);
110         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
111         return intent;
112     }
113 
createIntent(Context context, int quality, int minLength, int maxLength, boolean requirePasswordToDecrypt, String password, int userId)114     public static Intent createIntent(Context context, int quality, int minLength,
115             int maxLength, boolean requirePasswordToDecrypt, String password, int userId) {
116         Intent intent = createIntent(context, quality, minLength, maxLength,
117                 requirePasswordToDecrypt, password);
118         intent.putExtra(Intent.EXTRA_USER_ID, userId);
119         return intent;
120     }
121 
createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge)122     public static Intent createIntent(Context context, int quality,
123             int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge) {
124         Intent intent = createIntent(context, quality, minLength, maxLength,
125                 requirePasswordToDecrypt, false);
126         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
127         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
128         return intent;
129     }
130 
createIntent(Context context, int quality, int minLength, int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId)131     public static Intent createIntent(Context context, int quality, int minLength,
132             int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId) {
133         Intent intent = createIntent(context, quality, minLength, maxLength,
134                 requirePasswordToDecrypt, challenge);
135         intent.putExtra(Intent.EXTRA_USER_ID, userId);
136         return intent;
137     }
138 
139     @Override
isValidFragment(String fragmentName)140     protected boolean isValidFragment(String fragmentName) {
141         if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
142         return false;
143     }
144 
getFragmentClass()145     /* package */ Class<? extends Fragment> getFragmentClass() {
146         return ChooseLockPasswordFragment.class;
147     }
148 
149     @Override
onCreate(Bundle savedInstanceState)150     protected void onCreate(Bundle savedInstanceState) {
151         super.onCreate(savedInstanceState);
152         CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
153         setTitle(msg);
154         LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
155         layout.setFitsSystemWindows(false);
156     }
157 
158     public static class ChooseLockPasswordFragment extends InstrumentedFragment
159             implements OnClickListener, OnEditorActionListener, TextWatcher,
160             SaveAndFinishWorker.Listener {
161         private static final String KEY_FIRST_PIN = "first_pin";
162         private static final String KEY_UI_STAGE = "ui_stage";
163         private static final String KEY_CURRENT_PASSWORD = "current_password";
164         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
165 
166         private String mCurrentPassword;
167         private String mChosenPassword;
168         private boolean mHasChallenge;
169         private long mChallenge;
170         private EditText mPasswordEntry;
171         private TextViewInputDisabler mPasswordEntryInputDisabler;
172         private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
173         private int mPasswordMaxLength = 16;
174         private int mPasswordMinLetters = 0;
175         private int mPasswordMinUpperCase = 0;
176         private int mPasswordMinLowerCase = 0;
177         private int mPasswordMinSymbols = 0;
178         private int mPasswordMinNumeric = 0;
179         private int mPasswordMinNonLetter = 0;
180         private int mPasswordMinLengthToFulfillAllPolicies = 0;
181         private int mUserId;
182         private boolean mHideDrawer = false;
183         /**
184          * Password requirements that we need to verify.
185          */
186         private int[] mPasswordRequirements;
187 
188         private LockPatternUtils mLockPatternUtils;
189         private SaveAndFinishWorker mSaveAndFinishWorker;
190         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
191         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
192         private Stage mUiStage = Stage.Introduction;
193         private PasswordRequirementAdapter mPasswordRequirementAdapter;
194 
195         private TextView mHeaderText;
196         private String mFirstPin;
197         private RecyclerView mPasswordRestrictionView;
198         private boolean mIsAlphaMode;
199         private Button mCancelButton;
200         private Button mNextButton;
201 
202         private TextChangedHandler mTextChangedHandler;
203 
204         private static final int CONFIRM_EXISTING_REQUEST = 58;
205         static final int RESULT_FINISHED = RESULT_FIRST_USER;
206 
207         private static final int MIN_LETTER_IN_PASSWORD = 0;
208         private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1;
209         private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2;
210         private static final int MIN_SYMBOLS_IN_PASSWORD = 3;
211         private static final int MIN_NUMBER_IN_PASSWORD = 4;
212         private static final int MIN_NON_LETTER_IN_PASSWORD = 5;
213 
214         // Error code returned from {@link #validatePassword(String)}.
215         private static final int NO_ERROR = 0;
216         private static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
217         private static final int TOO_SHORT = 1 << 1;
218         private static final int TOO_LONG = 1 << 2;
219         private static final int CONTAIN_NON_DIGITS = 1 << 3;
220         private static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4;
221         private static final int RECENTLY_USED = 1 << 5;
222         private static final int NOT_ENOUGH_LETTER = 1 << 6;
223         private static final int NOT_ENOUGH_UPPER_CASE = 1 << 7;
224         private static final int NOT_ENOUGH_LOWER_CASE = 1 << 8;
225         private static final int NOT_ENOUGH_DIGITS = 1 << 9;
226         private static final int NOT_ENOUGH_SYMBOLS = 1 << 10;
227         private static final int NOT_ENOUGH_NON_LETTER = 1 << 11;
228 
229         /**
230          * Keep track internally of where the user is in choosing a pattern.
231          */
232         protected enum Stage {
233 
234             Introduction(R.string.lockpassword_choose_your_password_header,
235                     R.string.lockpassword_choose_your_pin_header,
236                     R.string.lockpassword_continue_label),
237 
238             NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
239                     R.string.lockpassword_confirm_your_pin_header,
240                     R.string.lockpassword_ok_label),
241 
242             ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
243                     R.string.lockpassword_confirm_pins_dont_match,
244                     R.string.lockpassword_continue_label);
245 
Stage(int hintInAlpha, int hintInNumeric, int nextButtonText)246             Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
247                 this.alphaHint = hintInAlpha;
248                 this.numericHint = hintInNumeric;
249                 this.buttonText = nextButtonText;
250             }
251 
252             public final int alphaHint;
253             public final int numericHint;
254             public final int buttonText;
255         }
256 
257         // required constructor for fragments
ChooseLockPasswordFragment()258         public ChooseLockPasswordFragment() {
259 
260         }
261 
262         @Override
onCreate(Bundle savedInstanceState)263         public void onCreate(Bundle savedInstanceState) {
264             super.onCreate(savedInstanceState);
265             mLockPatternUtils = new LockPatternUtils(getActivity());
266             Intent intent = getActivity().getIntent();
267             if (!(getActivity() instanceof ChooseLockPassword)) {
268                 throw new SecurityException("Fragment contained in wrong activity");
269             }
270             // Only take this argument into account if it belongs to the current profile.
271             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
272             processPasswordRequirements(intent);
273             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
274             mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
275 
276             if (intent.getBooleanExtra(
277                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
278                 SaveAndFinishWorker w = new SaveAndFinishWorker();
279                 final boolean required = getActivity().getIntent().getBooleanExtra(
280                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
281                 String current = intent.getStringExtra(
282                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
283                 w.setBlocking(true);
284                 w.setListener(this);
285                 w.start(mChooseLockSettingsHelper.utils(), required,
286                         false, 0, current, current, mRequestedQuality, mUserId);
287             }
288             mTextChangedHandler = new TextChangedHandler();
289         }
290 
291         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)292         public View onCreateView(LayoutInflater inflater, ViewGroup container,
293                 Bundle savedInstanceState) {
294             return inflater.inflate(R.layout.choose_lock_password, container, false);
295         }
296 
297         @Override
onViewCreated(View view, Bundle savedInstanceState)298         public void onViewCreated(View view, Bundle savedInstanceState) {
299             super.onViewCreated(view, savedInstanceState);
300 
301             mCancelButton = (Button) view.findViewById(R.id.cancel_button);
302             mCancelButton.setOnClickListener(this);
303             mNextButton = (Button) view.findViewById(R.id.next_button);
304             mNextButton.setOnClickListener(this);
305 
306             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
307                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
308                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
309 
310             setupPasswordRequirementsView(view);
311 
312             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
313             mPasswordEntry = (EditText) view.findViewById(R.id.password_entry);
314             mPasswordEntry.setOnEditorActionListener(this);
315             mPasswordEntry.addTextChangedListener(this);
316             mPasswordEntry.requestFocus();
317             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
318 
319             final Activity activity = getActivity();
320             mHeaderText = (TextView) view.findViewById(R.id.headerText);
321 
322             int currentType = mPasswordEntry.getInputType();
323             mPasswordEntry.setInputType(mIsAlphaMode ? currentType
324                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
325 
326             Intent intent = getActivity().getIntent();
327             final boolean confirmCredentials = intent.getBooleanExtra(
328                     ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
329             mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
330             mHasChallenge = intent.getBooleanExtra(
331                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
332             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
333             if (savedInstanceState == null) {
334                 updateStage(Stage.Introduction);
335                 if (confirmCredentials) {
336                     mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
337                             getString(R.string.unlock_set_unlock_launch_picker_title), true,
338                             mUserId);
339                 }
340             } else {
341                 // restore from previous state
342                 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
343                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
344                 if (state != null) {
345                     mUiStage = Stage.valueOf(state);
346                     updateStage(mUiStage);
347                 }
348 
349                 if (mCurrentPassword == null) {
350                     mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD);
351                 }
352 
353                 // Re-attach to the exiting worker if there is one.
354                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
355                         FRAGMENT_TAG_SAVE_AND_FINISH);
356             }
357 
358             // Workaround to show one password requirement below EditText when IME is shown.
359             // By adding an inset to the edit text background, we make the EditText occupy more
360             // vertical space, and the keyboard will then avoid hiding it. We have also set
361             // negative margin in the layout below in order to have them show in the correct
362             // position.
363             final int visibleVerticalSpaceBelowPassword =
364                     getResources().getDimensionPixelOffset(
365                         R.dimen.visible_vertical_space_below_password);
366             InsetDrawable drawable =
367                     new InsetDrawable(
368                     mPasswordEntry.getBackground(), 0, 0, 0, visibleVerticalSpaceBelowPassword);
369             mPasswordEntry.setBackgroundDrawable(drawable);
370             LinearLayout bottomContainer = (LinearLayout) view.findViewById(R.id.bottom_container);
371             LinearLayout.LayoutParams bottomContainerLp =
372                     (LinearLayout.LayoutParams) bottomContainer.getLayoutParams();
373             bottomContainerLp.setMargins(0, -visibleVerticalSpaceBelowPassword, 0, 0);
374 
375             if (activity instanceof SettingsActivity) {
376                 final SettingsActivity sa = (SettingsActivity) activity;
377                 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
378                         : R.string.lockpassword_choose_your_pin_header;
379                 CharSequence title = getText(id);
380                 sa.setTitle(title);
381                 ((GlifLayout) view).setHeaderText(title);
382             }
383         }
384 
setupPasswordRequirementsView(View view)385         private void setupPasswordRequirementsView(View view) {
386             // Construct passwordRequirements and requirementDescriptions.
387             List<Integer> passwordRequirements = new ArrayList<>();
388             List<String> requirementDescriptions = new ArrayList<>();
389             if (mPasswordMinUpperCase > 0) {
390                 passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
391                 requirementDescriptions.add(getResources().getQuantityString(
392                         R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
393                         mPasswordMinUpperCase));
394             }
395             if (mPasswordMinLowerCase > 0) {
396                 passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
397                 requirementDescriptions.add(getResources().getQuantityString(
398                         R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
399                         mPasswordMinLowerCase));
400             }
401             if (mPasswordMinLetters > 0) {
402                 if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) {
403                     passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
404                     requirementDescriptions.add(getResources().getQuantityString(
405                             R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
406                             mPasswordMinLetters));
407                 }
408             }
409             if (mPasswordMinNumeric > 0) {
410                 passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
411                 requirementDescriptions.add(getResources().getQuantityString(
412                         R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
413                         mPasswordMinNumeric));
414             }
415             if (mPasswordMinSymbols > 0) {
416                 passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
417                 requirementDescriptions.add(getResources().getQuantityString(
418                         R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
419                         mPasswordMinSymbols));
420             }
421             if (mPasswordMinNonLetter > 0) {
422                 if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) {
423                     passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
424                     requirementDescriptions.add(getResources().getQuantityString(
425                             R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
426 
427                             mPasswordMinNonLetter));
428                 }
429             }
430             // Convert list to array.
431             mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
432             mPasswordRestrictionView =
433                     (RecyclerView) view.findViewById(R.id.password_requirements_view);
434             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
435             mPasswordRequirementAdapter = new PasswordRequirementAdapter();
436             mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
437         }
438 
439         @Override
getMetricsCategory()440         protected int getMetricsCategory() {
441             return MetricsEvent.CHOOSE_LOCK_PASSWORD;
442         }
443 
444         @Override
onResume()445         public void onResume() {
446             super.onResume();
447             updateStage(mUiStage);
448             if (mSaveAndFinishWorker != null) {
449                 mSaveAndFinishWorker.setListener(this);
450             } else {
451                 mPasswordEntry.requestFocus();
452             }
453         }
454 
455         @Override
onPause()456         public void onPause() {
457             if (mSaveAndFinishWorker != null) {
458                 mSaveAndFinishWorker.setListener(null);
459             }
460             super.onPause();
461         }
462 
463         @Override
onSaveInstanceState(Bundle outState)464         public void onSaveInstanceState(Bundle outState) {
465             super.onSaveInstanceState(outState);
466             outState.putString(KEY_UI_STAGE, mUiStage.name());
467             outState.putString(KEY_FIRST_PIN, mFirstPin);
468             outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword);
469         }
470 
471         @Override
onActivityResult(int requestCode, int resultCode, Intent data)472         public void onActivityResult(int requestCode, int resultCode,
473                 Intent data) {
474             super.onActivityResult(requestCode, resultCode, data);
475             switch (requestCode) {
476                 case CONFIRM_EXISTING_REQUEST:
477                     if (resultCode != Activity.RESULT_OK) {
478                         getActivity().setResult(RESULT_FINISHED);
479                         getActivity().finish();
480                     } else {
481                         mCurrentPassword = data.getStringExtra(
482                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
483                     }
484                     break;
485             }
486         }
487 
getRedactionInterstitialIntent(Context context)488         protected Intent getRedactionInterstitialIntent(Context context) {
489             return RedactionInterstitial.createStartIntent(context, mUserId);
490         }
491 
updateStage(Stage stage)492         protected void updateStage(Stage stage) {
493             final Stage previousStage = mUiStage;
494             mUiStage = stage;
495             updateUi();
496 
497             // If the stage changed, announce the header for accessibility. This
498             // is a no-op when accessibility is disabled.
499             if (previousStage != stage) {
500                 mHeaderText.announceForAccessibility(mHeaderText.getText());
501             }
502         }
503 
504         /**
505          * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
506          *
507          * @param intent the incoming intent
508          */
processPasswordRequirements(Intent intent)509         private void processPasswordRequirements(Intent intent) {
510             final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
511             mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
512                     mRequestedQuality), dpmPasswordQuality);
513             mPasswordMinLength = Math.max(Math.max(
514                     LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
515                     intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
516                     mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
517             mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
518             mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
519                     mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
520                     mUserId));
521             mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
522                     mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
523                     mUserId));
524             mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
525                     mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
526                     mUserId));
527             mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
528                     mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
529                     mUserId));
530             mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
531                     mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
532                     mUserId));
533             mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
534                     mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
535                     mUserId));
536 
537             // Modify the value based on dpm policy.
538             switch (dpmPasswordQuality) {
539                 case PASSWORD_QUALITY_ALPHABETIC:
540                     if (mPasswordMinLetters == 0) {
541                         mPasswordMinLetters = 1;
542                     }
543                     break;
544                 case PASSWORD_QUALITY_ALPHANUMERIC:
545                     if (mPasswordMinLetters == 0) {
546                         mPasswordMinLetters = 1;
547                     }
548                     if (mPasswordMinNumeric == 0) {
549                         mPasswordMinNumeric = 1;
550                     }
551                     break;
552                 case PASSWORD_QUALITY_COMPLEX:
553                     // Reserve all the requirements.
554                     break;
555                 default:
556                     mPasswordMinNumeric = 0;
557                     mPasswordMinLetters = 0;
558                     mPasswordMinUpperCase = 0;
559                     mPasswordMinLowerCase = 0;
560                     mPasswordMinSymbols = 0;
561                     mPasswordMinNonLetter = 0;
562             }
563             mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
564         }
565 
566         /**
567          * Validates PIN and returns the validation result.
568          *
569          * @param password the raw password the user typed in
570          * @return the validation result.
571          */
validatePassword(String password)572         private int validatePassword(String password) {
573             int errorCode = NO_ERROR;
574 
575             if (password.length() < mPasswordMinLength) {
576                 if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
577                     errorCode |= TOO_SHORT;
578                 }
579             } else if (password.length() > mPasswordMaxLength) {
580                 errorCode |= TOO_LONG;
581             } else {
582                 // The length requirements are fulfilled.
583                 if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
584                     // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
585                     final int sequence = LockPatternUtils.maxLengthSequence(password);
586                     if (sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
587                         errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
588                     }
589                 }
590                 // Is the password recently used?
591                 if (mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
592                     errorCode |= RECENTLY_USED;
593                 }
594             }
595 
596             // Count different types of character.
597             int letters = 0;
598             int numbers = 0;
599             int lowercase = 0;
600             int symbols = 0;
601             int uppercase = 0;
602             int nonletter = 0;
603             for (int i = 0; i < password.length(); i++) {
604                 char c = password.charAt(i);
605                 // allow non control Latin-1 characters only
606                 if (c < 32 || c > 127) {
607                     errorCode |= CONTAIN_INVALID_CHARACTERS;
608                     continue;
609                 }
610                 if (c >= '0' && c <= '9') {
611                     numbers++;
612                     nonletter++;
613                 } else if (c >= 'A' && c <= 'Z') {
614                     letters++;
615                     uppercase++;
616                 } else if (c >= 'a' && c <= 'z') {
617                     letters++;
618                     lowercase++;
619                 } else {
620                     symbols++;
621                     nonletter++;
622                 }
623             }
624 
625             // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless
626             // user finds some way to bring up soft keyboard.
627             if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC
628                     || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
629                 if (letters > 0 || symbols > 0) {
630                     errorCode |= CONTAIN_NON_DIGITS;
631                 }
632             }
633 
634             // Check the requirements one by one.
635             for (int i = 0; i < mPasswordRequirements.length; i++) {
636                 int passwordRestriction = mPasswordRequirements[i];
637                 switch (passwordRestriction) {
638                     case MIN_LETTER_IN_PASSWORD:
639                         if (letters < mPasswordMinLetters) {
640                             errorCode |= NOT_ENOUGH_LETTER;
641                         }
642                         break;
643                     case MIN_UPPER_LETTERS_IN_PASSWORD:
644                         if (uppercase < mPasswordMinUpperCase) {
645                             errorCode |= NOT_ENOUGH_UPPER_CASE;
646                         }
647                         break;
648                     case MIN_LOWER_LETTERS_IN_PASSWORD:
649                         if (lowercase < mPasswordMinLowerCase) {
650                             errorCode |= NOT_ENOUGH_LOWER_CASE;
651                         }
652                         break;
653                     case MIN_SYMBOLS_IN_PASSWORD:
654                         if (symbols < mPasswordMinSymbols) {
655                             errorCode |= NOT_ENOUGH_SYMBOLS;
656                         }
657                         break;
658                     case MIN_NUMBER_IN_PASSWORD:
659                         if (numbers < mPasswordMinNumeric) {
660                             errorCode |= NOT_ENOUGH_DIGITS;
661                         }
662                         break;
663                     case MIN_NON_LETTER_IN_PASSWORD:
664                         if (nonletter < mPasswordMinNonLetter) {
665                             errorCode |= NOT_ENOUGH_NON_LETTER;
666                         }
667                         break;
668                 }
669             }
670             return errorCode;
671         }
672 
handleNext()673         public void handleNext() {
674             if (mSaveAndFinishWorker != null) return;
675             mChosenPassword = mPasswordEntry.getText().toString();
676             if (TextUtils.isEmpty(mChosenPassword)) {
677                 return;
678             }
679             if (mUiStage == Stage.Introduction) {
680                 if (validatePassword(mChosenPassword) == NO_ERROR) {
681                     mFirstPin = mChosenPassword;
682                     mPasswordEntry.setText("");
683                     updateStage(Stage.NeedToConfirm);
684                 }
685             } else if (mUiStage == Stage.NeedToConfirm) {
686                 if (mFirstPin.equals(mChosenPassword)) {
687                     startSaveAndFinish();
688                 } else {
689                     CharSequence tmp = mPasswordEntry.getText();
690                     if (tmp != null) {
691                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
692                     }
693                     updateStage(Stage.ConfirmWrong);
694                 }
695             }
696         }
697 
setNextEnabled(boolean enabled)698         protected void setNextEnabled(boolean enabled) {
699             mNextButton.setEnabled(enabled);
700         }
701 
setNextText(int text)702         protected void setNextText(int text) {
703             mNextButton.setText(text);
704         }
705 
onClick(View v)706         public void onClick(View v) {
707             switch (v.getId()) {
708                 case R.id.next_button:
709                     handleNext();
710                     break;
711 
712                 case R.id.cancel_button:
713                     getActivity().finish();
714                     break;
715             }
716         }
717 
onEditorAction(TextView v, int actionId, KeyEvent event)718         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
719             // Check if this was the result of hitting the enter or "done" key
720             if (actionId == EditorInfo.IME_NULL
721                     || actionId == EditorInfo.IME_ACTION_DONE
722                     || actionId == EditorInfo.IME_ACTION_NEXT) {
723                 handleNext();
724                 return true;
725             }
726             return false;
727         }
728 
729         /**
730          * @param errorCode error code returned from {@link #validatePassword(String)}.
731          * @return an array of messages describing the error, important messages come first.
732          */
convertErrorCodeToMessages(int errorCode)733         private String[] convertErrorCodeToMessages(int errorCode) {
734             List<String> messages = new ArrayList<>();
735             if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
736                 messages.add(getString(R.string.lockpassword_illegal_character));
737             }
738             if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
739                 messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
740             }
741             if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
742                 messages.add(getResources().getQuantityString(
743                         R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
744                         mPasswordMinUpperCase));
745             }
746             if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) {
747                 messages.add(getResources().getQuantityString(
748                         R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
749                         mPasswordMinLowerCase));
750             }
751             if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
752                 messages.add(getResources().getQuantityString(
753                         R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
754                         mPasswordMinLetters));
755             }
756             if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
757                 messages.add(getResources().getQuantityString(
758                         R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
759                         mPasswordMinNumeric));
760             }
761             if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) {
762                 messages.add(getResources().getQuantityString(
763                         R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
764                         mPasswordMinSymbols));
765             }
766             if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) {
767                 messages.add(getResources().getQuantityString(
768                         R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
769                         mPasswordMinNonLetter));
770             }
771             if ((errorCode & TOO_SHORT) > 0) {
772                 messages.add(getString(mIsAlphaMode ?
773                         R.string.lockpassword_password_too_short
774                         : R.string.lockpassword_pin_too_short, mPasswordMinLength));
775             }
776             if ((errorCode & TOO_LONG) > 0) {
777                 messages.add(getString(mIsAlphaMode ?
778                         R.string.lockpassword_password_too_long
779                         : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1));
780             }
781             if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
782                 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
783             }
784             if ((errorCode & RECENTLY_USED) > 0) {
785                 messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
786                         : R.string.lockpassword_pin_recently_used));
787             }
788             return messages.toArray(new String[0]);
789         }
790 
getMinLengthToFulfillAllPolicies()791         private int getMinLengthToFulfillAllPolicies() {
792             final int minLengthForLetters = Math.max(mPasswordMinLetters,
793                     mPasswordMinUpperCase + mPasswordMinLowerCase);
794             final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter,
795                     mPasswordMinSymbols + mPasswordMinNumeric);
796             return minLengthForLetters + minLengthForNonLetters;
797         }
798 
799         /**
800          * Update the hint based on current Stage and length of password entry
801          */
updateUi()802         private void updateUi() {
803             final boolean canInput = mSaveAndFinishWorker == null;
804             String password = mPasswordEntry.getText().toString();
805             final int length = password.length();
806             if (mUiStage == Stage.Introduction) {
807                 mPasswordRestrictionView.setVisibility(View.VISIBLE);
808                 final int errorCode = validatePassword(password);
809                 String[] messages = convertErrorCodeToMessages(errorCode);
810                 // Update the fulfillment of requirements.
811                 mPasswordRequirementAdapter.setRequirements(messages);
812                 // Enable/Disable the next button accordingly.
813                 setNextEnabled(errorCode == NO_ERROR);
814             } else {
815                 // Hide password requirement view when we are just asking user to confirm the pw.
816                 mPasswordRestrictionView.setVisibility(View.GONE);
817                 setHeaderText(getString(
818                         mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint));
819                 setNextEnabled(canInput && length > 0);
820             }
821             setNextText(mUiStage.buttonText);
822             mPasswordEntryInputDisabler.setInputEnabled(canInput);
823         }
824 
setHeaderText(String text)825         private void setHeaderText(String text) {
826             // Only set the text if it is different than the existing one to avoid announcing again.
827             if (!TextUtils.isEmpty(mHeaderText.getText())
828                     && mHeaderText.getText().toString().equals(text)) {
829                 return;
830             }
831             mHeaderText.setText(text);
832         }
833 
afterTextChanged(Editable s)834         public void afterTextChanged(Editable s) {
835             // Changing the text while error displayed resets to NeedToConfirm state
836             if (mUiStage == Stage.ConfirmWrong) {
837                 mUiStage = Stage.NeedToConfirm;
838             }
839             // Schedule the UI update.
840             mTextChangedHandler.notifyAfterTextChanged();
841         }
842 
beforeTextChanged(CharSequence s, int start, int count, int after)843         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
844 
845         }
846 
onTextChanged(CharSequence s, int start, int before, int count)847         public void onTextChanged(CharSequence s, int start, int before, int count) {
848 
849         }
850 
startSaveAndFinish()851         private void startSaveAndFinish() {
852             if (mSaveAndFinishWorker != null) {
853                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
854                 return;
855             }
856 
857             mPasswordEntryInputDisabler.setInputEnabled(false);
858             setNextEnabled(false);
859 
860             mSaveAndFinishWorker = new SaveAndFinishWorker();
861             mSaveAndFinishWorker.setListener(this);
862 
863             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
864                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
865             getFragmentManager().executePendingTransactions();
866 
867             final boolean required = getActivity().getIntent().getBooleanExtra(
868                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
869             mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
870                     mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId);
871         }
872 
873         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)874         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
875             getActivity().setResult(RESULT_FINISHED, resultData);
876 
877             if (!wasSecureBefore) {
878                 Intent intent = getRedactionInterstitialIntent(getActivity());
879                 if (intent != null) {
880                     intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer);
881                     startActivity(intent);
882                 }
883             }
884             getActivity().finish();
885         }
886 
887         class TextChangedHandler extends Handler {
888             private static final int ON_TEXT_CHANGED = 1;
889             private static final int DELAY_IN_MILLISECOND = 100;
890 
891             /**
892              * With the introduction of delay, we batch processing the text changed event to reduce
893              * unnecessary UI updates.
894              */
notifyAfterTextChanged()895             private void notifyAfterTextChanged() {
896                 removeMessages(ON_TEXT_CHANGED);
897                 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
898             }
899 
900             @Override
handleMessage(Message msg)901             public void handleMessage(Message msg) {
902                 if (msg.what == ON_TEXT_CHANGED) {
903                     updateUi();
904                 }
905             }
906         }
907     }
908 
909     private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
910 
911         private String mChosenPassword;
912         private String mCurrentPassword;
913         private int mRequestedQuality;
914 
start(LockPatternUtils utils, boolean required, boolean hasChallenge, long challenge, String chosenPassword, String currentPassword, int requestedQuality, int userId)915         public void start(LockPatternUtils utils, boolean required,
916                 boolean hasChallenge, long challenge,
917                 String chosenPassword, String currentPassword, int requestedQuality, int userId) {
918             prepare(utils, required, hasChallenge, challenge, userId);
919 
920             mChosenPassword = chosenPassword;
921             mCurrentPassword = currentPassword;
922             mRequestedQuality = requestedQuality;
923             mUserId = userId;
924 
925             start();
926         }
927 
928         @Override
saveAndVerifyInBackground()929         protected Intent saveAndVerifyInBackground() {
930             Intent result = null;
931             mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality,
932                     mUserId);
933 
934             if (mHasChallenge) {
935                 byte[] token;
936                 try {
937                     token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId);
938                 } catch (RequestThrottledException e) {
939                     token = null;
940                 }
941 
942                 if (token == null) {
943                     Log.e(TAG, "critical: no token returned for known good password.");
944                 }
945 
946                 result = new Intent();
947                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
948             }
949 
950             return result;
951         }
952     }
953 }
954