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.text.Editable; 23 import android.text.TextUtils; 24 import android.text.TextWatcher; 25 import android.view.View; 26 import android.view.inputmethod.EditorInfo; 27 import android.view.inputmethod.InputMethodManager; 28 import android.widget.EditText; 29 import android.widget.TextView; 30 31 import androidx.annotation.LayoutRes; 32 import androidx.annotation.StringRes; 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.car.settings.R; 36 import com.android.car.settings.common.BaseFragment; 37 import com.android.internal.widget.LockscreenCredential; 38 import com.android.internal.widget.TextViewInputDisabler; 39 40 /** 41 * Fragment for confirming existing lock PIN or password. The containing activity must implement 42 * CheckLockListener. 43 */ 44 public class ConfirmLockPinPasswordFragment extends BaseFragment { 45 46 private static final String FRAGMENT_TAG_CHECK_LOCK_WORKER = "check_lock_worker"; 47 private static final String EXTRA_IS_PIN = "extra_is_pin"; 48 49 private PinPadView mPinPad; 50 private EditText mPasswordField; 51 private TextView mMsgView; 52 53 private CheckLockWorker mCheckLockWorker; 54 private CheckLockListener mCheckLockListener; 55 56 private int mUserId; 57 private boolean mIsPin; 58 private LockscreenCredential mEnteredPassword; 59 60 private ConfirmLockLockoutHelper mConfirmLockLockoutHelper; 61 62 private TextViewInputDisabler mPasswordEntryInputDisabler; 63 private InputMethodManager mImm; 64 65 /** 66 * Factory method for creating fragment in PIN mode. 67 */ newPinInstance()68 public static ConfirmLockPinPasswordFragment newPinInstance() { 69 ConfirmLockPinPasswordFragment patternFragment = new ConfirmLockPinPasswordFragment(); 70 Bundle bundle = new Bundle(); 71 bundle.putBoolean(EXTRA_IS_PIN, true); 72 patternFragment.setArguments(bundle); 73 return patternFragment; 74 } 75 76 /** 77 * Factory method for creating fragment in password mode. 78 */ newPasswordInstance()79 public static ConfirmLockPinPasswordFragment newPasswordInstance() { 80 ConfirmLockPinPasswordFragment patternFragment = new ConfirmLockPinPasswordFragment(); 81 Bundle bundle = new Bundle(); 82 bundle.putBoolean(EXTRA_IS_PIN, false); 83 patternFragment.setArguments(bundle); 84 return patternFragment; 85 } 86 87 @Override 88 @LayoutRes getLayoutId()89 protected int getLayoutId() { 90 return mIsPin ? R.layout.confirm_lock_pin : R.layout.confirm_lock_password; 91 } 92 93 @Override 94 @StringRes getTitleId()95 protected int getTitleId() { 96 return R.string.security_settings_title; 97 } 98 99 @Override onAttach(Context context)100 public void onAttach(Context context) { 101 super.onAttach(context); 102 if ((getActivity() instanceof CheckLockListener)) { 103 mCheckLockListener = (CheckLockListener) getActivity(); 104 } else { 105 throw new RuntimeException("The activity must implement CheckLockListener"); 106 } 107 108 mUserId = UserHandle.myUserId(); 109 mConfirmLockLockoutHelper = ConfirmLockLockoutHelper.getInstance(requireContext(), mUserId); 110 mImm = requireContext().getSystemService(InputMethodManager.class); 111 } 112 113 @Override onCreate(Bundle savedInstanceState)114 public void onCreate(Bundle savedInstanceState) { 115 super.onCreate(savedInstanceState); 116 Bundle args = getArguments(); 117 if (args != null) { 118 mIsPin = args.getBoolean(EXTRA_IS_PIN); 119 } 120 } 121 122 @Override onViewCreated(View view, Bundle savedInstanceState)123 public void onViewCreated(View view, Bundle savedInstanceState) { 124 super.onViewCreated(view, savedInstanceState); 125 126 mPasswordField = view.findViewById(R.id.password_entry); 127 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordField); 128 mMsgView = view.findViewById(R.id.message); 129 mConfirmLockLockoutHelper.setConfirmLockUIController( 130 new ConfirmLockLockoutHelper.ConfirmLockUIController() { 131 @Override 132 public void setErrorText(String text) { 133 mMsgView.setText(text); 134 } 135 136 @Override 137 public void refreshUI(boolean isLockedOut) { 138 if (mIsPin) { 139 updatePinEntry(isLockedOut); 140 } else { 141 updatePasswordEntry(isLockedOut); 142 } 143 } 144 }); 145 146 if (mIsPin) { 147 initPinView(view); 148 } else { 149 initPasswordView(); 150 } 151 152 if (savedInstanceState != null) { 153 mCheckLockWorker = (CheckLockWorker) getFragmentManager().findFragmentByTag( 154 FRAGMENT_TAG_CHECK_LOCK_WORKER); 155 } 156 } 157 158 @Override onStart()159 public void onStart() { 160 super.onStart(); 161 if (mCheckLockWorker != null) { 162 mCheckLockWorker.setListener(this::onCheckCompleted); 163 } 164 } 165 166 @Override onResume()167 public void onResume() { 168 super.onResume(); 169 170 mConfirmLockLockoutHelper.onResumeUI(); 171 } 172 173 @Override onPause()174 public void onPause() { 175 super.onPause(); 176 177 mConfirmLockLockoutHelper.onPauseUI(); 178 } 179 180 @Override onStop()181 public void onStop() { 182 super.onStop(); 183 if (mCheckLockWorker != null) { 184 mCheckLockWorker.setListener(null); 185 } 186 } 187 188 @Override onDestroy()189 public void onDestroy() { 190 super.onDestroy(); 191 mPasswordField.setText(null); 192 193 PasswordHelper.zeroizeCredentials(mEnteredPassword); 194 } 195 initCheckLockWorker()196 private void initCheckLockWorker() { 197 if (mCheckLockWorker == null) { 198 mCheckLockWorker = new CheckLockWorker(); 199 mCheckLockWorker.setListener(this::onCheckCompleted); 200 201 getFragmentManager() 202 .beginTransaction() 203 .add(mCheckLockWorker, FRAGMENT_TAG_CHECK_LOCK_WORKER) 204 .commitNow(); 205 } 206 } 207 getEnteredPassword()208 private LockscreenCredential getEnteredPassword() { 209 if (mIsPin) { 210 return LockscreenCredential.createPinOrNone(mPasswordField.getText()); 211 } else { 212 return LockscreenCredential.createPasswordOrNone(mPasswordField.getText()); 213 } 214 } 215 initPinView(View view)216 private void initPinView(View view) { 217 mPinPad = view.findViewById(R.id.pin_pad); 218 219 PinPadView.PinPadClickListener pinPadClickListener = new PinPadView.PinPadClickListener() { 220 @Override 221 public void onDigitKeyClick(String digit) { 222 clearError(); 223 mPasswordField.append(digit); 224 } 225 226 @Override 227 public void onBackspaceClick() { 228 clearError(); 229 if (!TextUtils.isEmpty(mPasswordField.getText())) { 230 mPasswordField.getText().delete(mPasswordField.getSelectionEnd() - 1, 231 mPasswordField.getSelectionEnd()); 232 } 233 } 234 235 @Override 236 public void onEnterKeyClick() { 237 mEnteredPassword = getEnteredPassword(); 238 if (!mEnteredPassword.isNone()) { 239 initCheckLockWorker(); 240 mPinPad.setEnabled(false); 241 mCheckLockWorker.checkPinPassword(mUserId, mEnteredPassword); 242 } 243 } 244 }; 245 246 mPinPad.setPinPadClickListener(pinPadClickListener); 247 } 248 initPasswordView()249 private void initPasswordView() { 250 mPasswordField.setOnEditorActionListener((textView, actionId, keyEvent) -> { 251 // Check if this was the result of hitting the enter or "done" key. 252 if (actionId == EditorInfo.IME_NULL 253 || actionId == EditorInfo.IME_ACTION_DONE 254 || actionId == EditorInfo.IME_ACTION_NEXT) { 255 256 initCheckLockWorker(); 257 if (!mCheckLockWorker.isCheckInProgress()) { 258 mEnteredPassword = getEnteredPassword(); 259 mCheckLockWorker.checkPinPassword(mUserId, mEnteredPassword); 260 } 261 return true; 262 } 263 return false; 264 }); 265 266 mPasswordField.addTextChangedListener(new TextWatcher() { 267 @Override 268 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 269 } 270 271 @Override 272 public void onTextChanged(CharSequence s, int start, int before, int count) { 273 } 274 275 @Override 276 public void afterTextChanged(Editable s) { 277 clearError(); 278 } 279 }); 280 } 281 clearError()282 private void clearError() { 283 if (!TextUtils.isEmpty(mMsgView.getText())) { 284 mMsgView.setText(""); 285 } 286 } 287 hideKeyboard()288 private void hideKeyboard() { 289 View currentFocus = getActivity().getCurrentFocus(); 290 if (currentFocus == null) { 291 currentFocus = getActivity().getWindow().getDecorView(); 292 } 293 294 if (currentFocus != null) { 295 InputMethodManager inputMethodManager = 296 (InputMethodManager) currentFocus.getContext() 297 .getSystemService(Context.INPUT_METHOD_SERVICE); 298 inputMethodManager 299 .hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); 300 } 301 } 302 303 @VisibleForTesting onCheckCompleted(boolean lockMatched, int timeoutMs)304 void onCheckCompleted(boolean lockMatched, int timeoutMs) { 305 if (lockMatched) { 306 mCheckLockListener.onLockVerified(mEnteredPassword); 307 } else { 308 if (timeoutMs > 0) { 309 mConfirmLockLockoutHelper.onCheckCompletedWithTimeout(timeoutMs); 310 } else { 311 mMsgView.setText( 312 mIsPin ? R.string.lockscreen_wrong_pin 313 : R.string.lockscreen_wrong_password); 314 if (mIsPin) { 315 mPinPad.setEnabled(true); 316 } 317 } 318 } 319 320 if (!mIsPin) { 321 hideKeyboard(); 322 } 323 } 324 updatePasswordEntry(boolean isLockedOut)325 private void updatePasswordEntry(boolean isLockedOut) { 326 mPasswordField.setEnabled(!isLockedOut); 327 mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut); 328 if (isLockedOut) { 329 if (mImm != null) { 330 mImm.hideSoftInputFromWindow(mPasswordField.getWindowToken(), /* flags= */ 0); 331 } 332 } else { 333 mPasswordField.requestFocus(); 334 } 335 } 336 updatePinEntry(boolean isLockedOut)337 private void updatePinEntry(boolean isLockedOut) { 338 mPinPad.setEnabled(!isLockedOut); 339 if (isLockedOut) { 340 if (mImm != null) { 341 mImm.hideSoftInputFromWindow(mPinPad.getWindowToken(), /* flags= */ 0); 342 } 343 } else { 344 mPinPad.requestFocus(); 345 } 346 } 347 } 348