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