1 /* 2 * Copyright (C) 2018 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.car.settings.security; 18 19 import android.content.Context; 20 import android.os.Bundle; 21 import android.os.UserHandle; 22 import android.support.annotation.VisibleForTesting; 23 import android.text.Editable; 24 import android.text.TextUtils; 25 import android.text.TextWatcher; 26 import android.view.View; 27 import android.view.inputmethod.EditorInfo; 28 import android.view.inputmethod.InputMethodManager; 29 import android.widget.EditText; 30 import android.widget.TextView; 31 32 import com.android.car.settings.R; 33 import com.android.car.settings.common.BaseFragment; 34 35 /** 36 * Fragment for confirming existing lock PIN or password. The containing activity must implement 37 * CheckLockListener. 38 */ 39 public class ConfirmLockPinPasswordFragment extends BaseFragment { 40 41 private static final String FRAGMENT_TAG_CHECK_LOCK_WORKER = "check_lock_worker"; 42 private static final String EXTRA_IS_PIN = "extra_is_pin"; 43 44 private PinPadView mPinPad; 45 private EditText mPasswordField; 46 private TextView mMsgView; 47 48 private CheckLockWorker mCheckLockWorker; 49 private CheckLockListener mCheckLockListener; 50 51 private int mUserId; 52 private boolean mIsPin; 53 private String mEnteredPassword; 54 55 /** 56 * Factory method for creating fragment in PIN mode. 57 */ 58 public static ConfirmLockPinPasswordFragment newPinInstance() { 59 ConfirmLockPinPasswordFragment patternFragment = new ConfirmLockPinPasswordFragment(); 60 Bundle bundle = BaseFragment.getBundle(); 61 bundle.putInt(EXTRA_TITLE_ID, R.string.security_settings_title); 62 bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button); 63 bundle.putInt(EXTRA_LAYOUT, R.layout.confirm_lock_pin_fragment); 64 bundle.putBoolean(EXTRA_IS_PIN, true); 65 patternFragment.setArguments(bundle); 66 return patternFragment; 67 } 68 69 /** 70 * Factory method for creating fragment in password mode. 71 */ 72 public static ConfirmLockPinPasswordFragment newPasswordInstance() { 73 ConfirmLockPinPasswordFragment patternFragment = new ConfirmLockPinPasswordFragment(); 74 Bundle bundle = BaseFragment.getBundle(); 75 bundle.putInt(EXTRA_TITLE_ID, R.string.security_settings_title); 76 bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button); 77 bundle.putInt(EXTRA_LAYOUT, R.layout.confirm_lock_password_fragment); 78 bundle.putBoolean(EXTRA_IS_PIN, false); 79 patternFragment.setArguments(bundle); 80 return patternFragment; 81 } 82 83 @Override 84 public void onAttach(Context context) { 85 super.onAttach(context); 86 if ((getActivity() instanceof CheckLockListener)) { 87 mCheckLockListener = (CheckLockListener) getActivity(); 88 } else { 89 throw new RuntimeException("The activity must implement CheckLockListener"); 90 } 91 } 92 93 @Override 94 public void onCreate(Bundle savedInstanceState) { 95 super.onCreate(savedInstanceState); 96 mUserId = UserHandle.myUserId(); 97 Bundle args = getArguments(); 98 if (args != null) { 99 mIsPin = args.getBoolean(EXTRA_IS_PIN); 100 } 101 } 102 103 @Override 104 public void onViewCreated(View view, Bundle savedInstanceState) { 105 super.onViewCreated(view, savedInstanceState); 106 107 mPasswordField = (EditText) view.findViewById(R.id.password_entry); 108 mMsgView = (TextView) view.findViewById(R.id.message); 109 110 if (mIsPin) { 111 initPinView(view); 112 } else { 113 initPasswordView(); 114 } 115 116 if (savedInstanceState != null) { 117 mCheckLockWorker = (CheckLockWorker) getFragmentManager().findFragmentByTag( 118 FRAGMENT_TAG_CHECK_LOCK_WORKER); 119 } 120 } 121 122 @Override 123 public void onStart() { 124 super.onStart(); 125 if (mCheckLockWorker != null) { 126 mCheckLockWorker.setListener(this::onCheckCompleted); 127 } 128 } 129 130 @Override 131 public void onStop() { 132 super.onStop(); 133 if (mCheckLockWorker != null) { 134 mCheckLockWorker.setListener(null); 135 } 136 } 137 138 private void initCheckLockWorker() { 139 if (mCheckLockWorker == null) { 140 mCheckLockWorker = new CheckLockWorker(); 141 mCheckLockWorker.setListener(this::onCheckCompleted); 142 143 getFragmentManager() 144 .beginTransaction() 145 .add(mCheckLockWorker, FRAGMENT_TAG_CHECK_LOCK_WORKER) 146 .commitNow(); 147 } 148 } 149 150 private void initPinView(View view) { 151 mPinPad = (PinPadView) view.findViewById(R.id.pin_pad); 152 mPinPad.setEnterKeyIcon(R.drawable.ic_done); 153 154 PinPadView.PinPadClickListener pinPadClickListener = new PinPadView.PinPadClickListener() { 155 @Override 156 public void onDigitKeyClick(String digit) { 157 clearError(); 158 mPasswordField.append(digit); 159 } 160 161 @Override 162 public void onBackspaceClick() { 163 clearError(); 164 String pin = mPasswordField.getText().toString(); 165 if (pin.length() > 0) { 166 mPasswordField.setText(pin.substring(0, pin.length() - 1)); 167 } 168 } 169 170 @Override 171 public void onEnterKeyClick() { 172 mEnteredPassword = mPasswordField.getText().toString(); 173 if (!TextUtils.isEmpty(mEnteredPassword)) { 174 initCheckLockWorker(); 175 mPinPad.setEnabled(false); 176 mCheckLockWorker.checkPinPassword(mUserId, mEnteredPassword); 177 } 178 } 179 }; 180 181 mPinPad.setPinPadClickListener(pinPadClickListener); 182 } 183 184 private void initPasswordView() { 185 mPasswordField.setOnEditorActionListener((textView, actionId, keyEvent) -> { 186 // Check if this was the result of hitting the enter or "done" key. 187 if (actionId == EditorInfo.IME_NULL 188 || actionId == EditorInfo.IME_ACTION_DONE 189 || actionId == EditorInfo.IME_ACTION_NEXT) { 190 191 initCheckLockWorker(); 192 if (!mCheckLockWorker.isCheckInProgress()) { 193 mEnteredPassword = mPasswordField.getText().toString(); 194 mCheckLockWorker.checkPinPassword(mUserId, mEnteredPassword); 195 } 196 return true; 197 } 198 return false; 199 }); 200 201 mPasswordField.addTextChangedListener(new TextWatcher() { 202 @Override 203 public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 204 205 @Override 206 public void onTextChanged(CharSequence s, int start, int before, int count) {} 207 208 @Override 209 public void afterTextChanged(Editable s) { 210 clearError(); 211 } 212 }); 213 } 214 215 private void clearError() { 216 if (!TextUtils.isEmpty(mMsgView.getText())) { 217 mMsgView.setText(""); 218 } 219 } 220 221 private void hideKeyboard() { 222 View currentFocus = getActivity().getCurrentFocus(); 223 if (currentFocus == null) { 224 currentFocus = getActivity().getWindow().getDecorView(); 225 } 226 227 if (currentFocus != null) { 228 InputMethodManager inputMethodManager = 229 (InputMethodManager) currentFocus.getContext() 230 .getSystemService(Context.INPUT_METHOD_SERVICE); 231 inputMethodManager 232 .hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); 233 } 234 } 235 236 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 237 void onCheckCompleted(boolean lockMatched) { 238 if (lockMatched) { 239 mCheckLockListener.onLockVerified(mEnteredPassword); 240 } else { 241 mMsgView.setText( 242 mIsPin ? R.string.lockscreen_wrong_pin : R.string.lockscreen_wrong_password); 243 mPinPad.setEnabled(true); 244 } 245 246 if (!mIsPin) { 247 hideKeyboard(); 248 } 249 } 250 } 251