ChooseLockPassword.java revision 9ba765b875ebeb66d8a870a29a00fe53e9da306d
1bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang/*
2bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Copyright (C) 2010 The Android Open Source Project
3bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *
4bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Licensed under the Apache License, Version 2.0 (the "License");
5bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * you may not use this file except in compliance with the License.
6bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * You may obtain a copy of the License at
7bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *
8bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *      http://www.apache.org/licenses/LICENSE-2.0
9bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *
10bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Unless required by applicable law or agreed to in writing, software
11bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * distributed under the License is distributed on an "AS IS" BASIS,
12bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * See the License for the specific language governing permissions and
14bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * limitations under the License.
15bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */
16bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
179a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangpackage com.android.settings;
18bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
19a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.app.Activity;
20a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.app.Fragment;
214143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.app.admin.DevicePolicyManager;
224143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.content.Context;
234143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.content.Intent;
244143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.graphics.drawable.InsetDrawable;
254143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.os.Bundle;
264143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.os.Handler;
27c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.os.Message;
28c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.support.v7.widget.LinearLayoutManager;
29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.support.v7.widget.RecyclerView;
30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.Editable;
31c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.text.InputType;
32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.Selection;
33c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.text.Spannable;
34c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.text.TextUtils;
35a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.text.TextWatcher;
36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log;
3747d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.view.inputmethod.EditorInfo;
38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.KeyEvent;
39c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.view.LayoutInflater;
40c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.view.View;
41bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View.OnClickListener;
42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.ViewGroup;
43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.Button;
44bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.EditText;
45bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.LinearLayout;
46bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.TextView;
47bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.TextView.OnEditorActionListener;
48bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
4947d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.internal.logging.MetricsProto.MetricsEvent;
5047d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.internal.widget.LockPatternUtils;
51bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport com.android.internal.widget.LockPatternUtils.RequestThrottledException;
5247d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.internal.widget.TextViewInputDisabler;
530c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawaimport com.android.settings.notification.RedactionInterstitial;
54bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport com.android.settings.password.PasswordRequirementAdapter;
553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport com.android.setupwizardlib.GlifLayout;
563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
57c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport java.util.ArrayList;
58c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport java.util.List;
5947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
60bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
65bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangpublic class ChooseLockPassword extends SettingsActivity {
67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
753ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
76a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa    private static final String TAG = "ChooseLockPassword";
77a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa
78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    @Override
79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public Intent getIntent() {
80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Intent modIntent = new Intent(super.getIntent());
81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        return modIntent;
83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
85bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public static Intent createIntent(Context context, int quality,
863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            int minLength, final int maxLength, boolean requirePasswordToDecrypt,
873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            boolean confirmCredentials) {
88a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
89bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        intent.putExtra(PASSWORD_MIN_KEY, minLength);
913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        intent.putExtra(PASSWORD_MAX_KEY, maxLength);
923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
93bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
940c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        return intent;
950c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    }
960c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
970c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    public static Intent createIntent(Context context, int quality,
980c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            int minLength, final int maxLength, boolean requirePasswordToDecrypt,
99bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            boolean confirmCredentials, int userId) {
10047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        Intent intent = createIntent(context, quality, minLength, maxLength,
10147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                requirePasswordToDecrypt, confirmCredentials);
10247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        intent.putExtra(Intent.EXTRA_USER_ID, userId);
103bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        return intent;
104bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
105c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
106c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    public static Intent createIntent(Context context, int quality,
107f9df037f350fad73659307ba05f230d2db69051aMason Tang            int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password) {
1080c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        Intent intent = createIntent(context, quality, minLength, maxLength,
1090c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                requirePasswordToDecrypt, false);
1100c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
1110c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        return intent;
1120c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    }
1130c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
1140c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    public static Intent createIntent(Context context, int quality, int minLength,
11547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            int maxLength, boolean requirePasswordToDecrypt, String password, int userId) {
116a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        Intent intent = createIntent(context, quality, minLength, maxLength,
11747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                requirePasswordToDecrypt, password);
11847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        intent.putExtra(Intent.EXTRA_USER_ID, userId);
119c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        return intent;
120c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    }
121c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
122c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    public static Intent createIntent(Context context, int quality,
123c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge) {
124c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        Intent intent = createIntent(context, quality, minLength, maxLength,
125c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                requirePasswordToDecrypt, false);
126c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
127c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
128c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        return intent;
129c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    }
130c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
131c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    public static Intent createIntent(Context context, int quality, int minLength,
132c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId) {
133c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        Intent intent = createIntent(context, quality, minLength, maxLength,
134c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                requirePasswordToDecrypt, challenge);
135c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        intent.putExtra(Intent.EXTRA_USER_ID, userId);
136c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        return intent;
137c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    }
138c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
139c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    @Override
140c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    protected boolean isValidFragment(String fragmentName) {
141c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
14247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        return false;
14347d40324272ae39af0872bf5cbf27e1800478021Mason Tang    }
14447d40324272ae39af0872bf5cbf27e1800478021Mason Tang
145a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa    /* package */ Class<? extends Fragment> getFragmentClass() {
146c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        return ChooseLockPasswordFragment.class;
147c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    }
148bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
149bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    @Override
15047d40324272ae39af0872bf5cbf27e1800478021Mason Tang    protected void onCreate(Bundle savedInstanceState) {
15147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        super.onCreate(savedInstanceState);
152c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
153bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        setTitle(msg);
154bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
15547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        layout.setFitsSystemWindows(false);
156d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung    }
157d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung
158d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung    public static class ChooseLockPasswordFragment extends InstrumentedFragment
159d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung            implements OnClickListener, OnEditorActionListener, TextWatcher,
16047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            SaveAndFinishWorker.Listener {
16147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final String KEY_FIRST_PIN = "first_pin";
162c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private static final String KEY_UI_STAGE = "ui_stage";
163c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private static final String KEY_CURRENT_PASSWORD = "current_password";
164bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
165bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
16647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private String mCurrentPassword;
16747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private String mChosenPassword;
16847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private boolean mHasChallenge;
16947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private long mChallenge;
17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private EditText mPasswordEntry;
171bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private TextViewInputDisabler mPasswordEntryInputDisabler;
172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
17347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private int mPasswordMaxLength = 16;
17447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private int mPasswordMinLetters = 0;
17547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private int mPasswordMinUpperCase = 0;
17647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private int mPasswordMinLowerCase = 0;
17747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private int mPasswordMinSymbols = 0;
17847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private int mPasswordMinNumeric = 0;
179bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private int mPasswordMinNonLetter = 0;
180c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private int mPasswordMinLengthToFulfillAllPolicies = 0;
1813ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private int mUserId;
1823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private boolean mHideDrawer = false;
183bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan        /**
1844143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa         * Password requirements that we need to verify.
1853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         */
1863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private int[] mPasswordRequirements;
1873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private LockPatternUtils mLockPatternUtils;
1893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private SaveAndFinishWorker mSaveAndFinishWorker;
1903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
1913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private ChooseLockSettingsHelper mChooseLockSettingsHelper;
1923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private Stage mUiStage = Stage.Introduction;
1933ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private PasswordRequirementAdapter mPasswordRequirementAdapter;
1943ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1953ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private TextView mHeaderText;
196a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private String mFirstPin;
19747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private RecyclerView mPasswordRestrictionView;
198a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        private boolean mIsAlphaMode;
199a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        private Button mCancelButton;
200a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private Button mNextButton;
201a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa
202a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private TextChangedHandler mTextChangedHandler;
203a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa
204ffaeace621183dfe8770471a30b2f1138aac5f86Daisuke Miyakawa        private static final int CONFIRM_EXISTING_REQUEST = 58;
205309c34fcce4912a9c6f1c0a39c090cebf61296beMichael Chan        static final int RESULT_FINISHED = RESULT_FIRST_USER;
206309c34fcce4912a9c6f1c0a39c090cebf61296beMichael Chan
207a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private static final int MIN_LETTER_IN_PASSWORD = 0;
208a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1;
209a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2;
210a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        private static final int MIN_SYMBOLS_IN_PASSWORD = 3;
211a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        private static final int MIN_NUMBER_IN_PASSWORD = 4;
212bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private static final int MIN_NON_LETTER_IN_PASSWORD = 5;
213a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa
214a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        // Error code returned from {@link #validatePassword(String)}.
215bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private static final int NO_ERROR = 0;
2164143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa        private static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
217bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan        private static final int TOO_SHORT = 1 << 1;
218bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan        private static final int TOO_LONG = 1 << 2;
2194143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa        private static final int CONTAIN_NON_DIGITS = 1 << 3;
22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4;
22147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final int RECENTLY_USED = 1 << 5;
222bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private static final int NOT_ENOUGH_LETTER = 1 << 6;
223bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        private static final int NOT_ENOUGH_UPPER_CASE = 1 << 7;
22447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final int NOT_ENOUGH_LOWER_CASE = 1 << 8;
22547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final int NOT_ENOUGH_DIGITS = 1 << 9;
2263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private static final int NOT_ENOUGH_SYMBOLS = 1 << 10;
22747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final int NOT_ENOUGH_NON_LETTER = 1 << 11;
228bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Keep track internally of where the user is in choosing a pattern.
23147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
23247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected enum Stage {
2333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Introduction(R.string.lockpassword_choose_your_password_header,
235bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    R.string.lockpassword_choose_your_pin_header,
23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    R.string.lockpassword_continue_label),
23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
238c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
239c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    R.string.lockpassword_confirm_your_pin_header,
240c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    R.string.lockpassword_ok_label),
241c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
242c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
243c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    R.string.lockpassword_confirm_pins_dont_match,
244c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    R.string.lockpassword_continue_label);
245c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
246c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
247c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                this.alphaHint = hintInAlpha;
24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                this.numericHint = hintInNumeric;
249bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                this.buttonText = nextButtonText;
25047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
25147d40324272ae39af0872bf5cbf27e1800478021Mason Tang
25247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            public final int alphaHint;
253bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            public final int numericHint;
254bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            public final int buttonText;
25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
256c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
257c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        // required constructor for fragments
258c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public ChooseLockPasswordFragment() {
259c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
260c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
261c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
26247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
26347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onCreate(Bundle savedInstanceState) {
26447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            super.onCreate(savedInstanceState);
26547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mLockPatternUtils = new LockPatternUtils(getActivity());
26647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Intent intent = getActivity().getIntent();
26747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (!(getActivity() instanceof ChooseLockPassword)) {
26847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                throw new SecurityException("Fragment contained in wrong activity");
26947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
270c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            // Only take this argument into account if it belongs to the current profile.
271c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
272c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            processPasswordRequirements(intent);
273c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
274c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
275c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
276c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (intent.getBooleanExtra(
277c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
278c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                SaveAndFinishWorker w = new SaveAndFinishWorker();
279c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                final boolean required = getActivity().getIntent().getBooleanExtra(
280c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
281c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                String current = intent.getStringExtra(
282c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
283c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                w.setBlocking(true);
284c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                w.setListener(this);
28547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                w.start(mChooseLockSettingsHelper.utils(), required,
286c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        false, 0, current, current, mRequestedQuality, mUserId);
287c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
288c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mTextChangedHandler = new TextChangedHandler();
289c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
290c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
291c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        @Override
292c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public View onCreateView(LayoutInflater inflater, ViewGroup container,
293c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                Bundle savedInstanceState) {
294c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            return inflater.inflate(R.layout.choose_lock_password, container, false);
295bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
296c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
297c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        @Override
298c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public void onViewCreated(View view, Bundle savedInstanceState) {
299c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            super.onViewCreated(view, savedInstanceState);
300c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
30147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCancelButton = (Button) view.findViewById(R.id.cancel_button);
30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCancelButton.setOnClickListener(this);
303c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mNextButton = (Button) view.findViewById(R.id.next_button);
304bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            mNextButton.setOnClickListener(this);
305e0cb5ba884c52e9d36875fb4a9ebdf40a81cb642Michael Chan
306c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
30721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik                    || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
30821a183875fbbfa54f5a2a87779888a5fb7d1af44Erik                    || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
30947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            setupPasswordRequirementsView(view);
31121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik
312ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
31321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik            mPasswordEntry = (EditText) view.findViewById(R.id.password_entry);
31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mPasswordEntry.setOnEditorActionListener(this);
315bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            mPasswordEntry.addTextChangedListener(this);
316bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            mPasswordEntry.requestFocus();
31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
3180c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
31947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            final Activity activity = getActivity();
320ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            mHeaderText = (TextView) view.findViewById(R.id.headerText);
3210c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
322ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            int currentType = mPasswordEntry.getInputType();
3230c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            mPasswordEntry.setInputType(mIsAlphaMode ? currentType
3240c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
3250c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
3260c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            Intent intent = getActivity().getIntent();
32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            final boolean confirmCredentials = intent.getBooleanExtra(
3280c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
32947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
3300c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            mHasChallenge = intent.getBooleanExtra(
3310c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
3320c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
3330c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            if (savedInstanceState == null) {
3340c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                updateStage(Stage.Introduction);
335bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                if (confirmCredentials) {
336bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
3370c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                            getString(R.string.unlock_set_unlock_launch_picker_title), true,
3380c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                            mUserId);
3390c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                }
3400c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            } else {
3410c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                // restore from previous state
3420c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
343bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                final String state = savedInstanceState.getString(KEY_UI_STAGE);
3440c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                if (state != null) {
345bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    mUiStage = Stage.valueOf(state);
346bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    updateStage(mUiStage);
347ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan                }
34847d40324272ae39af0872bf5cbf27e1800478021Mason Tang
34947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (mCurrentPassword == null) {
35047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD);
35147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
35247d40324272ae39af0872bf5cbf27e1800478021Mason Tang
35347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Re-attach to the exiting worker if there is one.
354ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan                mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
355ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan                        FRAGMENT_TAG_SAVE_AND_FINISH);
356ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            }
357ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan
358ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            // Workaround to show one password requirement below EditText when IME is shown.
359ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            // By adding an inset to the edit text background, we make the EditText occupy more
360ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            // vertical space, and the keyboard will then avoid hiding it. We have also set
361ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            // negative margin in the layout below in order to have them show in the correct
362ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            // position.
363ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            final int visibleVerticalSpaceBelowPassword =
364ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan                    getResources().getDimensionPixelOffset(
365bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                        R.dimen.visible_vertical_space_below_password);
366bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            InsetDrawable drawable =
36747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    new InsetDrawable(
36847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    mPasswordEntry.getBackground(), 0, 0, 0, visibleVerticalSpaceBelowPassword);
36947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mPasswordEntry.setBackgroundDrawable(drawable);
37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            LinearLayout bottomContainer = (LinearLayout) view.findViewById(R.id.bottom_container);
37147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            LinearLayout.LayoutParams bottomContainerLp =
37247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    (LinearLayout.LayoutParams) bottomContainer.getLayoutParams();
373c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            bottomContainerLp.setMargins(0, -visibleVerticalSpaceBelowPassword, 0, 0);
374c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
375c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (activity instanceof SettingsActivity) {
376c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                final SettingsActivity sa = (SettingsActivity) activity;
377c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
378c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        : R.string.lockpassword_choose_your_pin_header;
379c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                CharSequence title = getText(id);
380c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                sa.setTitle(title);
381c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                ((GlifLayout) view).setHeaderText(title);
382c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
383c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
384c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
385c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private void setupPasswordRequirementsView(View view) {
386c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            // Construct passwordRequirements and requirementDescriptions.
387c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            List<Integer> passwordRequirements = new ArrayList<>();
388c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            List<String> requirementDescriptions = new ArrayList<>();
389c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (mPasswordMinUpperCase > 0) {
390c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
391c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                requirementDescriptions.add(getResources().getQuantityString(
392c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
393c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        mPasswordMinUpperCase));
394c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
395c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (mPasswordMinLowerCase > 0) {
396c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
397c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                requirementDescriptions.add(getResources().getQuantityString(
398c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
399c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        mPasswordMinLowerCase));
400c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
401c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (mPasswordMinLetters > 0) {
402c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) {
403c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
404c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    requirementDescriptions.add(getResources().getQuantityString(
405c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                            R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
406c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                            mPasswordMinLetters));
407c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                }
408c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
409c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (mPasswordMinNumeric > 0) {
410c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
411c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                requirementDescriptions.add(getResources().getQuantityString(
412c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
413c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        mPasswordMinNumeric));
414c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
415c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (mPasswordMinSymbols > 0) {
416c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
417c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                requirementDescriptions.add(getResources().getQuantityString(
418c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                        R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
4193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        mPasswordMinSymbols));
420bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
4213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (mPasswordMinNonLetter > 0) {
4223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) {
4233ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
4243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    requirementDescriptions.add(getResources().getQuantityString(
4253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                            R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
4263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
4273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                            mPasswordMinNonLetter));
4283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                }
4293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
4303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // Convert list to array.
4313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
4323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mPasswordRestrictionView =
4333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    (RecyclerView) view.findViewById(R.id.password_requirements_view);
4343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
4353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mPasswordRequirementAdapter = new PasswordRequirementAdapter();
4363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
4373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
4383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
4393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        @Override
4403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        protected int getMetricsCategory() {
441bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            return MetricsEvent.CHOOSE_LOCK_PASSWORD;
442bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
443bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
444        @Override
445        public void onResume() {
446            super.onResume();
447            updateStage(mUiStage);
448            if (mSaveAndFinishWorker != null) {
449                mSaveAndFinishWorker.setListener(this);
450            } else {
451                mPasswordEntry.requestFocus();
452            }
453        }
454
455        @Override
456        public void onPause() {
457            if (mSaveAndFinishWorker != null) {
458                mSaveAndFinishWorker.setListener(null);
459            }
460            super.onPause();
461        }
462
463        @Override
464        public void onSaveInstanceState(Bundle outState) {
465            super.onSaveInstanceState(outState);
466            outState.putString(KEY_UI_STAGE, mUiStage.name());
467            outState.putString(KEY_FIRST_PIN, mFirstPin);
468            outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword);
469        }
470
471        @Override
472        public void onActivityResult(int requestCode, int resultCode,
473                Intent data) {
474            super.onActivityResult(requestCode, resultCode, data);
475            switch (requestCode) {
476                case CONFIRM_EXISTING_REQUEST:
477                    if (resultCode != Activity.RESULT_OK) {
478                        getActivity().setResult(RESULT_FINISHED);
479                        getActivity().finish();
480                    } else {
481                        mCurrentPassword = data.getStringExtra(
482                                ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
483                    }
484                    break;
485            }
486        }
487
488        protected Intent getRedactionInterstitialIntent(Context context) {
489            return RedactionInterstitial.createStartIntent(context, mUserId);
490        }
491
492        protected void updateStage(Stage stage) {
493            final Stage previousStage = mUiStage;
494            mUiStage = stage;
495            updateUi();
496
497            // If the stage changed, announce the header for accessibility. This
498            // is a no-op when accessibility is disabled.
499            if (previousStage != stage) {
500                mHeaderText.announceForAccessibility(mHeaderText.getText());
501            }
502        }
503
504        /**
505         * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
506         *
507         * @param intent the incoming intent
508         */
509        private void processPasswordRequirements(Intent intent) {
510            final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
511            mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
512                    mRequestedQuality), dpmPasswordQuality);
513            mPasswordMinLength = Math.max(Math.max(
514                    LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
515                    intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
516                    mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
517            mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
518            mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
519                    mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
520                    mUserId));
521            mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
522                    mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
523                    mUserId));
524            mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
525                    mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
526                    mUserId));
527            mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
528                    mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
529                    mUserId));
530            mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
531                    mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
532                    mUserId));
533            mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
534                    mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
535                    mUserId));
536
537            // Modify the value based on dpm policy.
538            switch (dpmPasswordQuality) {
539                case PASSWORD_QUALITY_ALPHABETIC:
540                    if (mPasswordMinLetters == 0) {
541                        mPasswordMinLetters = 1;
542                    }
543                    break;
544                case PASSWORD_QUALITY_ALPHANUMERIC:
545                    if (mPasswordMinLetters == 0) {
546                        mPasswordMinLetters = 1;
547                    }
548                    if (mPasswordMinNumeric == 0) {
549                        mPasswordMinNumeric = 1;
550                    }
551                    break;
552                case PASSWORD_QUALITY_COMPLEX:
553                    // Reserve all the requirements.
554                    break;
555                default:
556                    mPasswordMinNumeric = 0;
557                    mPasswordMinLetters = 0;
558                    mPasswordMinUpperCase = 0;
559                    mPasswordMinLowerCase = 0;
560                    mPasswordMinSymbols = 0;
561                    mPasswordMinNonLetter = 0;
562            }
563            mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
564        }
565
566        /**
567         * Validates PIN and returns the validation result.
568         *
569         * @param password the raw password the user typed in
570         * @return the validation result.
571         */
572        private int validatePassword(String password) {
573            int errorCode = NO_ERROR;
574
575            if (password.length() < mPasswordMinLength) {
576                if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
577                    errorCode |= TOO_SHORT;
578                }
579            } else if (password.length() > mPasswordMaxLength) {
580                errorCode |= TOO_LONG;
581            } else {
582                // The length requirements are fulfilled.
583                if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
584                    // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
585                    final int sequence = LockPatternUtils.maxLengthSequence(password);
586                    if (sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
587                        errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
588                    }
589                }
590                // Is the password recently used?
591                if (mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
592                    errorCode |= RECENTLY_USED;
593                }
594            }
595
596            // Count different types of character.
597            int letters = 0;
598            int numbers = 0;
599            int lowercase = 0;
600            int symbols = 0;
601            int uppercase = 0;
602            int nonletter = 0;
603            for (int i = 0; i < password.length(); i++) {
604                char c = password.charAt(i);
605                // allow non control Latin-1 characters only
606                if (c < 32 || c > 127) {
607                    errorCode |= CONTAIN_INVALID_CHARACTERS;
608                    continue;
609                }
610                if (c >= '0' && c <= '9') {
611                    numbers++;
612                    nonletter++;
613                } else if (c >= 'A' && c <= 'Z') {
614                    letters++;
615                    uppercase++;
616                } else if (c >= 'a' && c <= 'z') {
617                    letters++;
618                    lowercase++;
619                } else {
620                    symbols++;
621                    nonletter++;
622                }
623            }
624
625            // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless
626            // user finds some way to bring up soft keyboard.
627            if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC
628                    || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
629                if (letters > 0 || symbols > 0) {
630                    errorCode |= CONTAIN_NON_DIGITS;
631                }
632            }
633
634            // Check the requirements one by one.
635            for (int i = 0; i < mPasswordRequirements.length; i++) {
636                int passwordRestriction = mPasswordRequirements[i];
637                switch (passwordRestriction) {
638                    case MIN_LETTER_IN_PASSWORD:
639                        if (letters < mPasswordMinLetters) {
640                            errorCode |= NOT_ENOUGH_LETTER;
641                        }
642                        break;
643                    case MIN_UPPER_LETTERS_IN_PASSWORD:
644                        if (uppercase < mPasswordMinUpperCase) {
645                            errorCode |= NOT_ENOUGH_UPPER_CASE;
646                        }
647                        break;
648                    case MIN_LOWER_LETTERS_IN_PASSWORD:
649                        if (lowercase < mPasswordMinLowerCase) {
650                            errorCode |= NOT_ENOUGH_LOWER_CASE;
651                        }
652                        break;
653                    case MIN_SYMBOLS_IN_PASSWORD:
654                        if (symbols < mPasswordMinSymbols) {
655                            errorCode |= NOT_ENOUGH_SYMBOLS;
656                        }
657                        break;
658                    case MIN_NUMBER_IN_PASSWORD:
659                        if (numbers < mPasswordMinNumeric) {
660                            errorCode |= NOT_ENOUGH_DIGITS;
661                        }
662                        break;
663                    case MIN_NON_LETTER_IN_PASSWORD:
664                        if (nonletter < mPasswordMinNonLetter) {
665                            errorCode |= NOT_ENOUGH_NON_LETTER;
666                        }
667                        break;
668                }
669            }
670            return errorCode;
671        }
672
673        public void handleNext() {
674            if (mSaveAndFinishWorker != null) return;
675            mChosenPassword = mPasswordEntry.getText().toString();
676            if (TextUtils.isEmpty(mChosenPassword)) {
677                return;
678            }
679            if (mUiStage == Stage.Introduction) {
680                if (validatePassword(mChosenPassword) == NO_ERROR) {
681                    mFirstPin = mChosenPassword;
682                    mPasswordEntry.setText("");
683                    updateStage(Stage.NeedToConfirm);
684                }
685            } else if (mUiStage == Stage.NeedToConfirm) {
686                if (mFirstPin.equals(mChosenPassword)) {
687                    startSaveAndFinish();
688                } else {
689                    CharSequence tmp = mPasswordEntry.getText();
690                    if (tmp != null) {
691                        Selection.setSelection((Spannable) tmp, 0, tmp.length());
692                    }
693                    updateStage(Stage.ConfirmWrong);
694                }
695            }
696        }
697
698        protected void setNextEnabled(boolean enabled) {
699            mNextButton.setEnabled(enabled);
700        }
701
702        protected void setNextText(int text) {
703            mNextButton.setText(text);
704        }
705
706        public void onClick(View v) {
707            switch (v.getId()) {
708                case R.id.next_button:
709                    handleNext();
710                    break;
711
712                case R.id.cancel_button:
713                    getActivity().finish();
714                    break;
715            }
716        }
717
718        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
719            // Check if this was the result of hitting the enter or "done" key
720            if (actionId == EditorInfo.IME_NULL
721                    || actionId == EditorInfo.IME_ACTION_DONE
722                    || actionId == EditorInfo.IME_ACTION_NEXT) {
723                handleNext();
724                return true;
725            }
726            return false;
727        }
728
729        /**
730         * @param errorCode error code returned from {@link #validatePassword(String)}.
731         * @return an array of messages describing the error, important messages come first.
732         */
733        private String[] convertErrorCodeToMessages(int errorCode) {
734            List<String> messages = new ArrayList<>();
735            if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
736                messages.add(getString(R.string.lockpassword_illegal_character));
737            }
738            if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
739                messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
740            }
741            if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
742                messages.add(getResources().getQuantityString(
743                        R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
744                        mPasswordMinUpperCase));
745            }
746            if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) {
747                messages.add(getResources().getQuantityString(
748                        R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
749                        mPasswordMinLowerCase));
750            }
751            if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
752                messages.add(getResources().getQuantityString(
753                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
754                        mPasswordMinLetters));
755            }
756            if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
757                messages.add(getResources().getQuantityString(
758                        R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
759                        mPasswordMinNumeric));
760            }
761            if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) {
762                messages.add(getResources().getQuantityString(
763                        R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
764                        mPasswordMinSymbols));
765            }
766            if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) {
767                messages.add(getResources().getQuantityString(
768                        R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
769                        mPasswordMinNonLetter));
770            }
771            if ((errorCode & TOO_SHORT) > 0) {
772                messages.add(getString(mIsAlphaMode ?
773                        R.string.lockpassword_password_too_short
774                        : R.string.lockpassword_pin_too_short, mPasswordMinLength));
775            }
776            if ((errorCode & TOO_LONG) > 0) {
777                messages.add(getString(mIsAlphaMode ?
778                        R.string.lockpassword_password_too_long
779                        : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1));
780            }
781            if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
782                messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
783            }
784            if ((errorCode & RECENTLY_USED) > 0) {
785                messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
786                        : R.string.lockpassword_pin_recently_used));
787            }
788            return messages.toArray(new String[0]);
789        }
790
791        private int getMinLengthToFulfillAllPolicies() {
792            final int minLengthForLetters = Math.max(mPasswordMinLetters,
793                    mPasswordMinUpperCase + mPasswordMinLowerCase);
794            final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter,
795                    mPasswordMinSymbols + mPasswordMinNumeric);
796            return minLengthForLetters + minLengthForNonLetters;
797        }
798
799        /**
800         * Update the hint based on current Stage and length of password entry
801         */
802        private void updateUi() {
803            final boolean canInput = mSaveAndFinishWorker == null;
804            String password = mPasswordEntry.getText().toString();
805            final int length = password.length();
806            if (mUiStage == Stage.Introduction) {
807                mPasswordRestrictionView.setVisibility(View.VISIBLE);
808                final int errorCode = validatePassword(password);
809                String[] messages = convertErrorCodeToMessages(errorCode);
810                // Update the fulfillment of requirements.
811                mPasswordRequirementAdapter.setRequirements(messages);
812                // Enable/Disable the next button accordingly.
813                setNextEnabled(errorCode == NO_ERROR);
814            } else {
815                // Hide password requirement view when we are just asking user to confirm the pw.
816                mPasswordRestrictionView.setVisibility(View.GONE);
817                setHeaderText(getString(
818                        mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint));
819                setNextEnabled(canInput && length > 0);
820            }
821            setNextText(mUiStage.buttonText);
822            mPasswordEntryInputDisabler.setInputEnabled(canInput);
823        }
824
825        private void setHeaderText(String text) {
826            // Only set the text if it is different than the existing one to avoid announcing again.
827            if (!TextUtils.isEmpty(mHeaderText.getText())
828                    && mHeaderText.getText().toString().equals(text)) {
829                return;
830            }
831            mHeaderText.setText(text);
832        }
833
834        public void afterTextChanged(Editable s) {
835            // Changing the text while error displayed resets to NeedToConfirm state
836            if (mUiStage == Stage.ConfirmWrong) {
837                mUiStage = Stage.NeedToConfirm;
838            }
839            // Schedule the UI update.
840            mTextChangedHandler.notifyAfterTextChanged();
841        }
842
843        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
844
845        }
846
847        public void onTextChanged(CharSequence s, int start, int before, int count) {
848
849        }
850
851        private void startSaveAndFinish() {
852            if (mSaveAndFinishWorker != null) {
853                Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
854                return;
855            }
856
857            mPasswordEntryInputDisabler.setInputEnabled(false);
858            setNextEnabled(false);
859
860            mSaveAndFinishWorker = new SaveAndFinishWorker();
861            mSaveAndFinishWorker.setListener(this);
862
863            getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
864                    FRAGMENT_TAG_SAVE_AND_FINISH).commit();
865            getFragmentManager().executePendingTransactions();
866
867            final boolean required = getActivity().getIntent().getBooleanExtra(
868                    EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
869            mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
870                    mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId);
871        }
872
873        @Override
874        public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
875            getActivity().setResult(RESULT_FINISHED, resultData);
876
877            if (!wasSecureBefore) {
878                Intent intent = getRedactionInterstitialIntent(getActivity());
879                if (intent != null) {
880                    intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer);
881                    startActivity(intent);
882                }
883            }
884            getActivity().finish();
885        }
886
887        class TextChangedHandler extends Handler {
888            private static final int ON_TEXT_CHANGED = 1;
889            private static final int DELAY_IN_MILLISECOND = 100;
890
891            /**
892             * With the introduction of delay, we batch processing the text changed event to reduce
893             * unnecessary UI updates.
894             */
895            private void notifyAfterTextChanged() {
896                removeMessages(ON_TEXT_CHANGED);
897                sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
898            }
899
900            @Override
901            public void handleMessage(Message msg) {
902                if (msg.what == ON_TEXT_CHANGED) {
903                    updateUi();
904                }
905            }
906        }
907    }
908
909    private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
910
911        private String mChosenPassword;
912        private String mCurrentPassword;
913        private int mRequestedQuality;
914
915        public void start(LockPatternUtils utils, boolean required,
916                boolean hasChallenge, long challenge,
917                String chosenPassword, String currentPassword, int requestedQuality, int userId) {
918            prepare(utils, required, hasChallenge, challenge, userId);
919
920            mChosenPassword = chosenPassword;
921            mCurrentPassword = currentPassword;
922            mRequestedQuality = requestedQuality;
923            mUserId = userId;
924
925            start();
926        }
927
928        @Override
929        protected Intent saveAndVerifyInBackground() {
930            Intent result = null;
931            mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality,
932                    mUserId);
933
934            if (mHasChallenge) {
935                byte[] token;
936                try {
937                    token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId);
938                } catch (RequestThrottledException e) {
939                    token = null;
940                }
941
942                if (token == null) {
943                    Log.e(TAG, "critical: no token returned for known good password.");
944                }
945
946                result = new Intent();
947                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
948            }
949
950            return result;
951        }
952    }
953}
954