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