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