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_COMPLEXITY_NONE;
20 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
21 
22 import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
23 import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
24 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
25 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS;
26 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE;
27 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS;
28 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER;
29 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS;
30 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE;
31 import static com.android.internal.widget.PasswordValidationError.RECENTLY_USED;
32 import static com.android.internal.widget.PasswordValidationError.TOO_LONG;
33 import static com.android.internal.widget.PasswordValidationError.TOO_SHORT;
34 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
35 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL;
36 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID;
37 
38 import android.app.Activity;
39 import android.app.admin.DevicePolicyManager;
40 import android.app.admin.DevicePolicyManager.PasswordComplexity;
41 import android.app.admin.PasswordMetrics;
42 import android.app.settings.SettingsEnums;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.res.Resources.Theme;
46 import android.graphics.Insets;
47 import android.graphics.Typeface;
48 import android.os.Bundle;
49 import android.os.Handler;
50 import android.os.Message;
51 import android.os.UserHandle;
52 import android.text.Editable;
53 import android.text.InputType;
54 import android.text.Selection;
55 import android.text.Spannable;
56 import android.text.TextUtils;
57 import android.text.TextWatcher;
58 import android.util.Log;
59 import android.util.Pair;
60 import android.view.KeyEvent;
61 import android.view.LayoutInflater;
62 import android.view.View;
63 import android.view.ViewGroup;
64 import android.view.inputmethod.EditorInfo;
65 import android.widget.ImeAwareEditText;
66 import android.widget.TextView;
67 import android.widget.TextView.OnEditorActionListener;
68 
69 import androidx.annotation.StringRes;
70 import androidx.fragment.app.Fragment;
71 import androidx.recyclerview.widget.LinearLayoutManager;
72 import androidx.recyclerview.widget.RecyclerView;
73 
74 import com.android.internal.annotations.VisibleForTesting;
75 import com.android.internal.widget.LockPatternUtils;
76 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
77 import com.android.internal.widget.LockscreenCredential;
78 import com.android.internal.widget.PasswordValidationError;
79 import com.android.internal.widget.TextViewInputDisabler;
80 import com.android.settings.EncryptionInterstitial;
81 import com.android.settings.R;
82 import com.android.settings.SettingsActivity;
83 import com.android.settings.SetupWizardUtils;
84 import com.android.settings.Utils;
85 import com.android.settings.core.InstrumentedFragment;
86 import com.android.settings.notification.RedactionInterstitial;
87 
88 import com.google.android.setupcompat.template.FooterBarMixin;
89 import com.google.android.setupcompat.template.FooterButton;
90 import com.google.android.setupdesign.GlifLayout;
91 
92 import java.util.ArrayList;
93 import java.util.Collections;
94 import java.util.List;
95 
96 public class ChooseLockPassword extends SettingsActivity {
97     private static final String TAG = "ChooseLockPassword";
98 
99     @Override
getIntent()100     public Intent getIntent() {
101         Intent modIntent = new Intent(super.getIntent());
102         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
103         return modIntent;
104     }
105 
106     @Override
onApplyThemeResource(Theme theme, int resid, boolean first)107     protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
108         resid = SetupWizardUtils.getTheme(getIntent());
109         super.onApplyThemeResource(theme, resid, first);
110     }
111 
112     public static class IntentBuilder {
113 
114         private final Intent mIntent;
115 
IntentBuilder(Context context)116         public IntentBuilder(Context context) {
117             mIntent = new Intent(context, ChooseLockPassword.class);
118             mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false);
119             mIntent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, false);
120             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
121         }
122 
setPasswordQuality(int quality)123         public IntentBuilder setPasswordQuality(int quality) {
124             mIntent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
125             return this;
126         }
127 
setUserId(int userId)128         public IntentBuilder setUserId(int userId) {
129             mIntent.putExtra(Intent.EXTRA_USER_ID, userId);
130             return this;
131         }
132 
setChallenge(long challenge)133         public IntentBuilder setChallenge(long challenge) {
134             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
135             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
136             return this;
137         }
138 
setPassword(LockscreenCredential password)139         public IntentBuilder setPassword(LockscreenCredential password) {
140             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
141             return this;
142         }
143 
setForFingerprint(boolean forFingerprint)144         public IntentBuilder setForFingerprint(boolean forFingerprint) {
145             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint);
146             return this;
147         }
148 
setForFace(boolean forFace)149         public IntentBuilder setForFace(boolean forFace) {
150             mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace);
151             return this;
152         }
153 
setRequestedMinComplexity(@asswordComplexity int level)154         public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) {
155             mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level);
156             return this;
157         }
158 
159         /**
160          * Configures the launch such that at the end of the password enrollment, one of its
161          * managed profile (specified by {@code profileId}) will have its lockscreen unified
162          * to the parent user. The profile's current lockscreen credential needs to be specified by
163          * {@code credential}.
164          */
setProfileToUnify(int profileId, LockscreenCredential credential)165         public IntentBuilder setProfileToUnify(int profileId, LockscreenCredential credential) {
166             mIntent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID, profileId);
167             mIntent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL, credential);
168             return this;
169         }
170 
build()171         public Intent build() {
172             return mIntent;
173         }
174     }
175 
176     @Override
isValidFragment(String fragmentName)177     protected boolean isValidFragment(String fragmentName) {
178         if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
179         return false;
180     }
181 
getFragmentClass()182     /* package */ Class<? extends Fragment> getFragmentClass() {
183         return ChooseLockPasswordFragment.class;
184     }
185 
186     @Override
onCreate(Bundle savedInstanceState)187     protected void onCreate(Bundle savedInstanceState) {
188         super.onCreate(savedInstanceState);
189         final boolean forFingerprint = getIntent()
190                 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
191         final boolean forFace = getIntent()
192                 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
193 
194         CharSequence msg = getText(R.string.lockpassword_choose_your_screen_lock_header);
195         if (forFingerprint) {
196             msg = getText(R.string.lockpassword_choose_your_password_header_for_fingerprint);
197         } else if (forFace) {
198             msg = getText(R.string.lockpassword_choose_your_password_header_for_face);
199         }
200 
201         setTitle(msg);
202         findViewById(R.id.content_parent).setFitsSystemWindows(false);
203     }
204 
205     public static class ChooseLockPasswordFragment extends InstrumentedFragment
206             implements OnEditorActionListener, TextWatcher, SaveAndFinishWorker.Listener {
207         private static final String KEY_FIRST_PASSWORD = "first_password";
208         private static final String KEY_UI_STAGE = "ui_stage";
209         private static final String KEY_CURRENT_CREDENTIAL = "current_credential";
210         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
211 
212         private LockscreenCredential mCurrentCredential;
213         private LockscreenCredential mChosenPassword;
214         private boolean mHasChallenge;
215         private long mChallenge;
216         private ImeAwareEditText mPasswordEntry;
217         private TextViewInputDisabler mPasswordEntryInputDisabler;
218 
219         // Minimum password metrics enforced by admins.
220         private PasswordMetrics mMinMetrics;
221         private List<PasswordValidationError> mValidationErrors;
222 
223         @PasswordComplexity private int mMinComplexity = PASSWORD_COMPLEXITY_NONE;
224         protected int mUserId;
225         private byte[] mPasswordHistoryHashFactor;
226         private int mUnificationProfileId = UserHandle.USER_NULL;
227 
228         private LockPatternUtils mLockPatternUtils;
229         private SaveAndFinishWorker mSaveAndFinishWorker;
230         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
231         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
232         protected Stage mUiStage = Stage.Introduction;
233         private PasswordRequirementAdapter mPasswordRequirementAdapter;
234         private GlifLayout mLayout;
235         protected boolean mForFingerprint;
236         protected boolean mForFace;
237 
238         private LockscreenCredential mFirstPassword;
239         private RecyclerView mPasswordRestrictionView;
240         protected boolean mIsAlphaMode;
241         protected FooterButton mSkipOrClearButton;
242         private FooterButton mNextButton;
243         private TextView mMessage;
244 
245         private TextChangedHandler mTextChangedHandler;
246 
247         private static final int CONFIRM_EXISTING_REQUEST = 58;
248         static final int RESULT_FINISHED = RESULT_FIRST_USER;
249 
250         /**
251          * Keep track internally of where the user is in choosing a pattern.
252          */
253         protected enum Stage {
254 
255             Introduction(
256                     R.string.lockpassword_choose_your_screen_lock_header, // password
257                     R.string.lockpassword_choose_your_password_header_for_fingerprint,
258                     R.string.lockpassword_choose_your_password_header_for_face,
259                     R.string.lockpassword_choose_your_screen_lock_header, // pin
260                     R.string.lockpassword_choose_your_pin_header_for_fingerprint,
261                     R.string.lockpassword_choose_your_pin_header_for_face,
262                     R.string.lockpassword_choose_your_password_message, // added security message
263                     R.string.lock_settings_picker_biometrics_added_security_message,
264                     R.string.lockpassword_choose_your_pin_message,
265                     R.string.lock_settings_picker_biometrics_added_security_message,
266                     R.string.next_label),
267 
268             NeedToConfirm(
269                     R.string.lockpassword_confirm_your_password_header,
270                     R.string.lockpassword_confirm_your_password_header,
271                     R.string.lockpassword_confirm_your_password_header,
272                     R.string.lockpassword_confirm_your_pin_header,
273                     R.string.lockpassword_confirm_your_pin_header,
274                     R.string.lockpassword_confirm_your_pin_header,
275                     0,
276                     0,
277                     0,
278                     0,
279                     R.string.lockpassword_confirm_label),
280 
281             ConfirmWrong(
282                     R.string.lockpassword_confirm_passwords_dont_match,
283                     R.string.lockpassword_confirm_passwords_dont_match,
284                     R.string.lockpassword_confirm_passwords_dont_match,
285                     R.string.lockpassword_confirm_pins_dont_match,
286                     R.string.lockpassword_confirm_pins_dont_match,
287                     R.string.lockpassword_confirm_pins_dont_match,
288                     0,
289                     0,
290                     0,
291                     0,
292                     R.string.lockpassword_confirm_label);
293 
Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace, int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace, int messageInAlpha, int messageInAlphaForBiometrics, int messageInNumeric, int messageInNumericForBiometrics, int nextButtonText)294             Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace,
295                     int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace,
296                     int messageInAlpha, int messageInAlphaForBiometrics,
297                     int messageInNumeric, int messageInNumericForBiometrics,
298                     int nextButtonText) {
299                 this.alphaHint = hintInAlpha;
300                 this.alphaHintForFingerprint = hintInAlphaForFingerprint;
301                 this.alphaHintForFace = hintInAlphaForFace;
302 
303                 this.numericHint = hintInNumeric;
304                 this.numericHintForFingerprint = hintInNumericForFingerprint;
305                 this.numericHintForFace = hintInNumericForFace;
306 
307                 this.alphaMessage = messageInAlpha;
308                 this.alphaMessageForBiometrics = messageInAlphaForBiometrics;
309                 this.numericMessage = messageInNumeric;
310                 this.numericMessageForBiometrics = messageInNumericForBiometrics;
311                 this.buttonText = nextButtonText;
312             }
313 
314             public static final int TYPE_NONE = 0;
315             public static final int TYPE_FINGERPRINT = 1;
316             public static final int TYPE_FACE = 2;
317 
318             // Password
319             public final int alphaHint;
320             public final int alphaHintForFingerprint;
321             public final int alphaHintForFace;
322 
323             // PIN
324             public final int numericHint;
325             public final int numericHintForFingerprint;
326             public final int numericHintForFace;
327 
328             public final int alphaMessage;
329             public final int alphaMessageForBiometrics;
330             public final int numericMessage;
331             public final int numericMessageForBiometrics;
332             public final int buttonText;
333 
getHint(boolean isAlpha, int type)334             public @StringRes int getHint(boolean isAlpha, int type) {
335                 if (isAlpha) {
336                     if (type == TYPE_FINGERPRINT) {
337                         return alphaHintForFingerprint;
338                     } else if (type == TYPE_FACE) {
339                         return alphaHintForFace;
340                     } else {
341                         return alphaHint;
342                     }
343                 } else {
344                     if (type == TYPE_FINGERPRINT) {
345                         return numericHintForFingerprint;
346                     } else if (type == TYPE_FACE) {
347                         return numericHintForFace;
348                     } else {
349                         return numericHint;
350                     }
351                 }
352             }
353 
getMessage(boolean isAlpha, int type)354             public @StringRes int getMessage(boolean isAlpha, int type) {
355                 if (isAlpha) {
356                     return type != TYPE_NONE ? alphaMessageForBiometrics : alphaMessage;
357                 } else {
358                     return type != TYPE_NONE ? numericMessageForBiometrics : numericMessage;
359                 }
360             }
361         }
362 
363         // required constructor for fragments
ChooseLockPasswordFragment()364         public ChooseLockPasswordFragment() {
365 
366         }
367 
368         @Override
onCreate(Bundle savedInstanceState)369         public void onCreate(Bundle savedInstanceState) {
370             super.onCreate(savedInstanceState);
371             mLockPatternUtils = new LockPatternUtils(getActivity());
372             Intent intent = getActivity().getIntent();
373             if (!(getActivity() instanceof ChooseLockPassword)) {
374                 throw new SecurityException("Fragment contained in wrong activity");
375             }
376             // Only take this argument into account if it belongs to the current profile.
377             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
378             mForFingerprint = intent.getBooleanExtra(
379                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
380             mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
381             mMinComplexity = intent.getIntExtra(
382                     EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
383 
384             mRequestedQuality = intent.getIntExtra(
385                     LockPatternUtils.PASSWORD_TYPE_KEY, PASSWORD_QUALITY_NUMERIC);
386             mUnificationProfileId = intent.getIntExtra(
387                     EXTRA_KEY_UNIFICATION_PROFILE_ID, UserHandle.USER_NULL);
388 
389             mMinMetrics = mLockPatternUtils.getRequestedPasswordMetrics(mUserId);
390             // If we are to unify a work challenge at the end of the credential enrollment, manually
391             // merge any password policy from that profile here, so we are enrolling a compliant
392             // password. This is because once unified, the profile's password policy will
393             // be enforced on the new credential.
394             if (mUnificationProfileId != UserHandle.USER_NULL) {
395                 mMinMetrics.maxWith(
396                         mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId));
397             }
398 
399             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
400 
401             if (intent.getBooleanExtra(
402                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
403                 SaveAndFinishWorker w = new SaveAndFinishWorker();
404                 final boolean required = getActivity().getIntent().getBooleanExtra(
405                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
406                 LockscreenCredential currentCredential = intent.getParcelableExtra(
407                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
408 
409                 w.setBlocking(true);
410                 w.setListener(this);
411                 w.start(mChooseLockSettingsHelper.utils(), required, false, 0,
412                         currentCredential, currentCredential, mUserId);
413             }
414             mTextChangedHandler = new TextChangedHandler();
415         }
416 
417         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)418         public View onCreateView(LayoutInflater inflater, ViewGroup container,
419                 Bundle savedInstanceState) {
420             return inflater.inflate(R.layout.choose_lock_password, container, false);
421         }
422 
423         @Override
onViewCreated(View view, Bundle savedInstanceState)424         public void onViewCreated(View view, Bundle savedInstanceState) {
425             super.onViewCreated(view, savedInstanceState);
426 
427             mLayout = (GlifLayout) view;
428 
429             // Make the password container consume the optical insets so the edit text is aligned
430             // with the sides of the parent visually.
431             ViewGroup container = view.findViewById(R.id.password_container);
432             container.setOpticalInsets(Insets.NONE);
433 
434             final FooterBarMixin mixin = mLayout.getMixin(FooterBarMixin.class);
435             mixin.setSecondaryButton(
436                     new FooterButton.Builder(getActivity())
437                             .setText(R.string.lockpassword_clear_label)
438                             .setListener(this::onSkipOrClearButtonClick)
439                             .setButtonType(FooterButton.ButtonType.SKIP)
440                             .setTheme(R.style.SudGlifButton_Secondary)
441                             .build()
442             );
443             mixin.setPrimaryButton(
444                     new FooterButton.Builder(getActivity())
445                             .setText(R.string.next_label)
446                             .setListener(this::onNextButtonClick)
447                             .setButtonType(FooterButton.ButtonType.NEXT)
448                             .setTheme(R.style.SudGlifButton_Primary)
449                             .build()
450             );
451             mSkipOrClearButton = mixin.getSecondaryButton();
452             mNextButton = mixin.getPrimaryButton();
453 
454             mMessage = view.findViewById(R.id.sud_layout_description);
455             if (mForFingerprint) {
456                 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header));
457             } else if (mForFace) {
458                 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header));
459             }
460 
461             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
462                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
463                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
464 
465             setupPasswordRequirementsView(view);
466 
467             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
468             mPasswordEntry = view.findViewById(R.id.password_entry);
469             mPasswordEntry.setOnEditorActionListener(this);
470             mPasswordEntry.addTextChangedListener(this);
471             mPasswordEntry.requestFocus();
472             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
473 
474             final Activity activity = getActivity();
475 
476             int currentType = mPasswordEntry.getInputType();
477             mPasswordEntry.setInputType(mIsAlphaMode ? currentType
478                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
479             if (mIsAlphaMode) {
480                 mPasswordEntry.setContentDescription(
481                         getString(R.string.unlock_set_unlock_password_title));
482             } else {
483                 mPasswordEntry.setContentDescription(
484                         getString(R.string.unlock_set_unlock_pin_title));
485             }
486             // Can't set via XML since setInputType resets the fontFamily to null
487             mPasswordEntry.setTypeface(Typeface.create(
488                     getContext().getString(com.android.internal.R.string.config_headlineFontFamily),
489                     Typeface.NORMAL));
490 
491             Intent intent = getActivity().getIntent();
492             final boolean confirmCredentials = intent.getBooleanExtra(
493                     ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
494             mCurrentCredential = intent.getParcelableExtra(
495                     ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
496             mHasChallenge = intent.getBooleanExtra(
497                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
498             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
499             if (savedInstanceState == null) {
500                 updateStage(Stage.Introduction);
501                 if (confirmCredentials) {
502                     mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
503                             getString(R.string.unlock_set_unlock_launch_picker_title), true,
504                             mUserId);
505                 }
506             } else {
507 
508                 // restore from previous state
509                 mFirstPassword = savedInstanceState.getParcelable(KEY_FIRST_PASSWORD);
510                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
511                 if (state != null) {
512                     mUiStage = Stage.valueOf(state);
513                     updateStage(mUiStage);
514                 }
515 
516                 if (mCurrentCredential == null) {
517                     mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_CREDENTIAL);
518                 }
519 
520                 // Re-attach to the exiting worker if there is one.
521                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
522                         FRAGMENT_TAG_SAVE_AND_FINISH);
523             }
524 
525             if (activity instanceof SettingsActivity) {
526                 final SettingsActivity sa = (SettingsActivity) activity;
527                 int title = Stage.Introduction.getHint(mIsAlphaMode, getStageType());
528                 sa.setTitle(title);
529                 mLayout.setHeaderText(title);
530             }
531         }
532 
533         @Override
onDestroy()534         public void onDestroy() {
535             super.onDestroy();
536             if (mCurrentCredential != null) {
537                 mCurrentCredential.zeroize();
538             }
539             // Force a garbage collection immediately to remove remnant of user password shards
540             // from memory.
541             System.gc();
542             System.runFinalization();
543             System.gc();
544         }
545 
getStageType()546         protected int getStageType() {
547             return mForFingerprint ? Stage.TYPE_FINGERPRINT :
548                     mForFace ? Stage.TYPE_FACE :
549                             Stage.TYPE_NONE;
550         }
551 
setupPasswordRequirementsView(View view)552         private void setupPasswordRequirementsView(View view) {
553             mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view);
554             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
555             mPasswordRequirementAdapter = new PasswordRequirementAdapter();
556             mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
557         }
558 
559         @Override
getMetricsCategory()560         public int getMetricsCategory() {
561             return SettingsEnums.CHOOSE_LOCK_PASSWORD;
562         }
563 
564         @Override
onResume()565         public void onResume() {
566             super.onResume();
567             updateStage(mUiStage);
568             if (mSaveAndFinishWorker != null) {
569                 mSaveAndFinishWorker.setListener(this);
570             } else {
571                 mPasswordEntry.requestFocus();
572                 mPasswordEntry.scheduleShowSoftInput();
573             }
574         }
575 
576         @Override
onPause()577         public void onPause() {
578             if (mSaveAndFinishWorker != null) {
579                 mSaveAndFinishWorker.setListener(null);
580             }
581             super.onPause();
582         }
583 
584         @Override
onSaveInstanceState(Bundle outState)585         public void onSaveInstanceState(Bundle outState) {
586             super.onSaveInstanceState(outState);
587             outState.putString(KEY_UI_STAGE, mUiStage.name());
588             outState.putParcelable(KEY_FIRST_PASSWORD, mFirstPassword);
589             outState.putParcelable(KEY_CURRENT_CREDENTIAL, mCurrentCredential);
590         }
591 
592         @Override
onActivityResult(int requestCode, int resultCode, Intent data)593         public void onActivityResult(int requestCode, int resultCode,
594                 Intent data) {
595             super.onActivityResult(requestCode, resultCode, data);
596             switch (requestCode) {
597                 case CONFIRM_EXISTING_REQUEST:
598                     if (resultCode != Activity.RESULT_OK) {
599                         getActivity().setResult(RESULT_FINISHED);
600                         getActivity().finish();
601                     } else {
602                         mCurrentCredential = data.getParcelableExtra(
603                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
604                     }
605                     break;
606             }
607         }
608 
getRedactionInterstitialIntent(Context context)609         protected Intent getRedactionInterstitialIntent(Context context) {
610             return RedactionInterstitial.createStartIntent(context, mUserId);
611         }
612 
updateStage(Stage stage)613         protected void updateStage(Stage stage) {
614             final Stage previousStage = mUiStage;
615             mUiStage = stage;
616             updateUi();
617 
618             // If the stage changed, announce the header for accessibility. This
619             // is a no-op when accessibility is disabled.
620             if (previousStage != stage) {
621                 mLayout.announceForAccessibility(mLayout.getHeaderText());
622             }
623         }
624 
625         /**
626          * Validates PIN/Password and returns the validation result and updates mValidationErrors
627          * and mPasswordReused to reflect validation results.
628          *
629          * @param credential credential the user typed in.
630          * @return whether password satisfies all the requirements.
631          */
632         @VisibleForTesting
validatePassword(LockscreenCredential credential)633         boolean validatePassword(LockscreenCredential credential) {
634             final byte[] password = credential.getCredential();
635             mValidationErrors = PasswordMetrics.validatePassword(
636                     mMinMetrics, mMinComplexity, !mIsAlphaMode, password);
637             if (mValidationErrors.isEmpty() &&  mLockPatternUtils.checkPasswordHistory(
638                         password, getPasswordHistoryHashFactor(), mUserId)) {
639                 mValidationErrors =
640                         Collections.singletonList(new PasswordValidationError(RECENTLY_USED));
641             }
642             return mValidationErrors.isEmpty();
643         }
644 
645         /**
646          * Lazily compute and return the history hash factor of the current user (mUserId), used for
647          * password history check.
648          */
getPasswordHistoryHashFactor()649         private byte[] getPasswordHistoryHashFactor() {
650             if (mPasswordHistoryHashFactor == null) {
651                 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor(
652                         mCurrentCredential != null ? mCurrentCredential
653                                 : LockscreenCredential.createNone(), mUserId);
654             }
655             return mPasswordHistoryHashFactor;
656         }
657 
handleNext()658         public void handleNext() {
659             if (mSaveAndFinishWorker != null) return;
660             // TODO(b/120484642): This is a point of entry for passwords from the UI
661             final Editable passwordText = mPasswordEntry.getText();
662             if (TextUtils.isEmpty(passwordText)) {
663                 return;
664             }
665             mChosenPassword = mIsAlphaMode ? LockscreenCredential.createPassword(passwordText)
666                     : LockscreenCredential.createPin(passwordText);
667             if (mUiStage == Stage.Introduction) {
668                 if (validatePassword(mChosenPassword)) {
669                     mFirstPassword = mChosenPassword;
670                     mPasswordEntry.setText("");
671                     updateStage(Stage.NeedToConfirm);
672                 } else {
673                     mChosenPassword.zeroize();
674                 }
675             } else if (mUiStage == Stage.NeedToConfirm) {
676                 if (mChosenPassword.equals(mFirstPassword)) {
677                     startSaveAndFinish();
678                 } else {
679                     CharSequence tmp = mPasswordEntry.getText();
680                     if (tmp != null) {
681                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
682                     }
683                     updateStage(Stage.ConfirmWrong);
684                     mChosenPassword.zeroize();
685                 }
686             }
687         }
688 
setNextEnabled(boolean enabled)689         protected void setNextEnabled(boolean enabled) {
690             mNextButton.setEnabled(enabled);
691         }
692 
setNextText(int text)693         protected void setNextText(int text) {
694             mNextButton.setText(getActivity(), text);
695         }
696 
onSkipOrClearButtonClick(View view)697         protected void onSkipOrClearButtonClick(View view) {
698             mPasswordEntry.setText("");
699         }
700 
onNextButtonClick(View view)701         protected void onNextButtonClick(View view) {
702             handleNext();
703         }
704 
onEditorAction(TextView v, int actionId, KeyEvent event)705         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
706             // Check if this was the result of hitting the enter or "done" key
707             if (actionId == EditorInfo.IME_NULL
708                     || actionId == EditorInfo.IME_ACTION_DONE
709                     || actionId == EditorInfo.IME_ACTION_NEXT) {
710                 handleNext();
711                 return true;
712             }
713             return false;
714         }
715 
716         /**
717          * @param errorCode error code returned from password validation.
718          * @return an array of messages describing the error, important messages come first.
719          */
convertErrorCodeToMessages()720         String[] convertErrorCodeToMessages() {
721             List<String> messages = new ArrayList<>();
722             for (PasswordValidationError error : mValidationErrors) {
723                 switch (error.errorCode) {
724                     case CONTAINS_INVALID_CHARACTERS:
725                         messages.add(getString(R.string.lockpassword_illegal_character));
726                         break;
727                     case NOT_ENOUGH_UPPER_CASE:
728                         messages.add(getResources().getQuantityString(
729                                 R.plurals.lockpassword_password_requires_uppercase,
730                                 error.requirement, error.requirement));
731                         break;
732                     case NOT_ENOUGH_LOWER_CASE:
733                         messages.add(getResources().getQuantityString(
734                                 R.plurals.lockpassword_password_requires_lowercase,
735                                 error.requirement, error.requirement));
736                         break;
737                     case NOT_ENOUGH_LETTERS:
738                         messages.add(getResources().getQuantityString(
739                                 R.plurals.lockpassword_password_requires_letters,
740                                 error.requirement, error.requirement));
741                         break;
742                     case NOT_ENOUGH_DIGITS:
743                         messages.add(getResources().getQuantityString(
744                                 R.plurals.lockpassword_password_requires_numeric,
745                                 error.requirement, error.requirement));
746                         break;
747                     case NOT_ENOUGH_SYMBOLS:
748                         messages.add(getResources().getQuantityString(
749                                 R.plurals.lockpassword_password_requires_symbols,
750                                 error.requirement, error.requirement));
751                         break;
752                     case NOT_ENOUGH_NON_LETTER:
753                         messages.add(getResources().getQuantityString(
754                                 R.plurals.lockpassword_password_requires_nonletter,
755                                 error.requirement, error.requirement));
756                         break;
757                     case NOT_ENOUGH_NON_DIGITS:
758                         messages.add(getResources().getQuantityString(
759                                 R.plurals.lockpassword_password_requires_nonnumerical,
760                                 error.requirement, error.requirement));
761                         break;
762                     case TOO_SHORT:
763                         messages.add(getResources().getQuantityString(
764                                 mIsAlphaMode
765                                         ? R.plurals.lockpassword_password_too_short
766                                         : R.plurals.lockpassword_pin_too_short,
767                                 error.requirement, error.requirement));
768                         break;
769                     case TOO_LONG:
770                         messages.add(getResources().getQuantityString(
771                                 mIsAlphaMode
772                                         ? R.plurals.lockpassword_password_too_long
773                                         : R.plurals.lockpassword_pin_too_long,
774                                 error.requirement + 1, error.requirement + 1));
775                         break;
776                     case CONTAINS_SEQUENCE:
777                         messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
778                         break;
779                     case RECENTLY_USED:
780                         messages.add(getString(mIsAlphaMode
781                                 ? R.string.lockpassword_password_recently_used
782                                 : R.string.lockpassword_pin_recently_used));
783                         break;
784                     default:
785                         Log.wtf(TAG, "unknown error validating password: " + error);
786                 }
787             }
788 
789             return messages.toArray(new String[0]);
790         }
791 
792         /**
793          * Update the hint based on current Stage and length of password entry
794          */
updateUi()795         protected void updateUi() {
796             final boolean canInput = mSaveAndFinishWorker == null;
797 
798             LockscreenCredential password = mIsAlphaMode
799                     ? LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText())
800                     : LockscreenCredential.createPinOrNone(mPasswordEntry.getText());
801             final int length = password.size();
802             if (mUiStage == Stage.Introduction) {
803                 mPasswordRestrictionView.setVisibility(View.VISIBLE);
804                 final boolean passwordCompliant = validatePassword(password);
805                 String[] messages = convertErrorCodeToMessages();
806                 // Update the fulfillment of requirements.
807                 mPasswordRequirementAdapter.setRequirements(messages);
808                 // Enable/Disable the next button accordingly.
809                 setNextEnabled(passwordCompliant);
810             } else {
811                 // Hide password requirement view when we are just asking user to confirm the pw.
812                 mPasswordRestrictionView.setVisibility(View.GONE);
813                 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, getStageType())));
814                 setNextEnabled(canInput && length >= LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
815                 mSkipOrClearButton.setVisibility(toVisibility(canInput && length > 0));
816             }
817             int message = mUiStage.getMessage(mIsAlphaMode, getStageType());
818             if (message != 0) {
819                 mMessage.setVisibility(View.VISIBLE);
820                 mMessage.setText(message);
821             } else {
822                 mMessage.setVisibility(View.INVISIBLE);
823             }
824 
825             setNextText(mUiStage.buttonText);
826             mPasswordEntryInputDisabler.setInputEnabled(canInput);
827             password.zeroize();
828         }
829 
toVisibility(boolean visibleOrGone)830         protected int toVisibility(boolean visibleOrGone) {
831             return visibleOrGone ? View.VISIBLE : View.GONE;
832         }
833 
setHeaderText(String text)834         private void setHeaderText(String text) {
835             // Only set the text if it is different than the existing one to avoid announcing again.
836             if (!TextUtils.isEmpty(mLayout.getHeaderText())
837                     && mLayout.getHeaderText().toString().equals(text)) {
838                 return;
839             }
840             mLayout.setHeaderText(text);
841         }
842 
afterTextChanged(Editable s)843         public void afterTextChanged(Editable s) {
844             // Changing the text while error displayed resets to NeedToConfirm state
845             if (mUiStage == Stage.ConfirmWrong) {
846                 mUiStage = Stage.NeedToConfirm;
847             }
848             // Schedule the UI update.
849             mTextChangedHandler.notifyAfterTextChanged();
850         }
851 
beforeTextChanged(CharSequence s, int start, int count, int after)852         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
853 
854         }
855 
onTextChanged(CharSequence s, int start, int before, int count)856         public void onTextChanged(CharSequence s, int start, int before, int count) {
857 
858         }
859 
startSaveAndFinish()860         private void startSaveAndFinish() {
861             if (mSaveAndFinishWorker != null) {
862                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
863                 return;
864             }
865 
866             mPasswordEntryInputDisabler.setInputEnabled(false);
867             setNextEnabled(false);
868 
869             mSaveAndFinishWorker = new SaveAndFinishWorker();
870             mSaveAndFinishWorker.setListener(this);
871 
872             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
873                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
874             getFragmentManager().executePendingTransactions();
875 
876             final Intent intent = getActivity().getIntent();
877             final boolean required = intent.getBooleanExtra(
878                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
879             if (mUnificationProfileId != UserHandle.USER_NULL) {
880                 try (LockscreenCredential profileCredential = (LockscreenCredential)
881                         intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) {
882                     mSaveAndFinishWorker.setProfileToUnify(mUnificationProfileId,
883                             profileCredential);
884                 }
885             }
886             mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
887                     mChosenPassword, mCurrentCredential, mUserId);
888         }
889 
890         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)891         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
892             getActivity().setResult(RESULT_FINISHED, resultData);
893 
894             if (mChosenPassword != null) {
895                 mChosenPassword.zeroize();
896             }
897             if (mCurrentCredential != null) {
898                 mCurrentCredential.zeroize();
899             }
900             if (mFirstPassword != null) {
901                 mFirstPassword.zeroize();
902             }
903 
904             mPasswordEntry.setText("");
905 
906             if (!wasSecureBefore) {
907                 Intent intent = getRedactionInterstitialIntent(getActivity());
908                 if (intent != null) {
909                     startActivity(intent);
910                 }
911             }
912             getActivity().finish();
913         }
914 
915         class TextChangedHandler extends Handler {
916             private static final int ON_TEXT_CHANGED = 1;
917             private static final int DELAY_IN_MILLISECOND = 100;
918 
919             /**
920              * With the introduction of delay, we batch processing the text changed event to reduce
921              * unnecessary UI updates.
922              */
notifyAfterTextChanged()923             private void notifyAfterTextChanged() {
924                 removeMessages(ON_TEXT_CHANGED);
925                 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
926             }
927 
928             @Override
handleMessage(Message msg)929             public void handleMessage(Message msg) {
930                 if (getActivity() == null) {
931                     return;
932                 }
933                 if (msg.what == ON_TEXT_CHANGED) {
934                     updateUi();
935                 }
936             }
937         }
938     }
939 
940     public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
941 
942         private LockscreenCredential mChosenPassword;
943         private LockscreenCredential mCurrentCredential;
944 
start(LockPatternUtils utils, boolean required, boolean hasChallenge, long challenge, LockscreenCredential chosenPassword, LockscreenCredential currentCredential, int userId)945         public void start(LockPatternUtils utils, boolean required,
946                 boolean hasChallenge, long challenge,
947                 LockscreenCredential chosenPassword, LockscreenCredential currentCredential,
948                 int userId) {
949             prepare(utils, required, hasChallenge, challenge, userId);
950 
951             mChosenPassword = chosenPassword;
952             mCurrentCredential = currentCredential != null ? currentCredential
953                     : LockscreenCredential.createNone();
954             mUserId = userId;
955 
956             start();
957         }
958 
959         @Override
saveAndVerifyInBackground()960         protected Pair<Boolean, Intent> saveAndVerifyInBackground() {
961             final boolean success = mUtils.setLockCredential(
962                     mChosenPassword, mCurrentCredential, mUserId);
963             if (success) {
964                 unifyProfileCredentialIfRequested();
965             }
966             Intent result = null;
967             if (success && mHasChallenge) {
968                 byte[] token;
969                 try {
970                     token = mUtils.verifyCredential(mChosenPassword, mChallenge, mUserId);
971                 } catch (RequestThrottledException e) {
972                     token = null;
973                 }
974 
975                 if (token == null) {
976                     Log.e(TAG, "critical: no token returned for known good password.");
977                 }
978 
979                 result = new Intent();
980                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
981             }
982             return Pair.create(success, result);
983         }
984     }
985 }
986