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