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