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