Home | History | Annotate | Download | only in settings
      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
     81     public Intent getIntent() {
     82         Intent modIntent = new Intent(super.getIntent());
     83         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
     84         return modIntent;
     85     }
     86 
     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 
     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 
    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 
    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 
    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 
    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
    142     protected boolean isValidFragment(String fragmentName) {
    143         if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
    144         return false;
    145     }
    146 
    147     /* package */ Class<? extends Fragment> getFragmentClass() {
    148         return ChooseLockPasswordFragment.class;
    149     }
    150 
    151     @Override
    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 
    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
    260         public ChooseLockPasswordFragment() {
    261 
    262         }
    263 
    264         @Override
    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
    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
    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 
    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
    442         public int getMetricsCategory() {
    443             return MetricsEvent.CHOOSE_LOCK_PASSWORD;
    444         }
    445 
    446         @Override
    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
    458         public void onPause() {
    459             if (mSaveAndFinishWorker != null) {
    460                 mSaveAndFinishWorker.setListener(null);
    461             }
    462             super.onPause();
    463         }
    464 
    465         @Override
    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
    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 
    490         protected Intent getRedactionInterstitialIntent(Context context) {
    491             return RedactionInterstitial.createStartIntent(context, mUserId);
    492         }
    493 
    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          */
    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          */
    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 
    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 
    682         protected void setNextEnabled(boolean enabled) {
    683             mNextButton.setEnabled(enabled);
    684         }
    685 
    686         protected void setNextText(int text) {
    687             mNextButton.setText(text);
    688         }
    689 
    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 
    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          */
    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 
    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          */
    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 
    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 
    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 
    827         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    828 
    829         }
    830 
    831         public void onTextChanged(CharSequence s, int start, int before, int count) {
    832 
    833         }
    834 
    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
    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              */
    879             private void notifyAfterTextChanged() {
    880                 removeMessages(ON_TEXT_CHANGED);
    881                 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
    882             }
    883 
    884             @Override
    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 
    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
    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