1 /*
2  * Copyright (C) 2014 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.keyguard;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.util.AttributeSet;
22 import android.view.KeyEvent;
23 import android.view.MotionEvent;
24 import android.view.View;
25 
26 /**
27  * A Pin based Keyguard input view
28  */
29 public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
30         implements View.OnKeyListener, View.OnTouchListener {
31 
32     protected PasswordTextView mPasswordEntry;
33     private View mOkButton;
34     private View mDeleteButton;
35     private View mButton0;
36     private View mButton1;
37     private View mButton2;
38     private View mButton3;
39     private View mButton4;
40     private View mButton5;
41     private View mButton6;
42     private View mButton7;
43     private View mButton8;
44     private View mButton9;
45 
KeyguardPinBasedInputView(Context context)46     public KeyguardPinBasedInputView(Context context) {
47         this(context, null);
48     }
49 
KeyguardPinBasedInputView(Context context, AttributeSet attrs)50     public KeyguardPinBasedInputView(Context context, AttributeSet attrs) {
51         super(context, attrs);
52     }
53 
54     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)55     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
56         // send focus to the password field
57         return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
58     }
59 
60     @Override
resetState()61     protected void resetState() {
62         setPasswordEntryEnabled(true);
63     }
64 
65     @Override
setPasswordEntryEnabled(boolean enabled)66     protected void setPasswordEntryEnabled(boolean enabled) {
67         mPasswordEntry.setEnabled(enabled);
68         mOkButton.setEnabled(enabled);
69         if (enabled && !mPasswordEntry.hasFocus()) {
70             mPasswordEntry.requestFocus();
71         }
72     }
73 
74     @Override
setPasswordEntryInputEnabled(boolean enabled)75     protected void setPasswordEntryInputEnabled(boolean enabled) {
76         mPasswordEntry.setEnabled(enabled);
77         mOkButton.setEnabled(enabled);
78         if (enabled && !mPasswordEntry.hasFocus()) {
79             mPasswordEntry.requestFocus();
80         }
81     }
82 
83     @Override
onKeyDown(int keyCode, KeyEvent event)84     public boolean onKeyDown(int keyCode, KeyEvent event) {
85         if (KeyEvent.isConfirmKey(keyCode)) {
86             performClick(mOkButton);
87             return true;
88         } else if (keyCode == KeyEvent.KEYCODE_DEL) {
89             performClick(mDeleteButton);
90             return true;
91         }
92         if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
93             int number = keyCode - KeyEvent.KEYCODE_0;
94             performNumberClick(number);
95             return true;
96         }
97         if (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9) {
98             int number = keyCode - KeyEvent.KEYCODE_NUMPAD_0;
99             performNumberClick(number);
100             return true;
101         }
102         return super.onKeyDown(keyCode, event);
103     }
104 
105     @Override
getPromptReasonStringRes(int reason)106     protected int getPromptReasonStringRes(int reason) {
107         switch (reason) {
108             case PROMPT_REASON_RESTART:
109                 return R.string.kg_prompt_reason_restart_pin;
110             case PROMPT_REASON_TIMEOUT:
111                 return R.string.kg_prompt_reason_timeout_pin;
112             case PROMPT_REASON_DEVICE_ADMIN:
113                 return R.string.kg_prompt_reason_device_admin;
114             case PROMPT_REASON_USER_REQUEST:
115                 return R.string.kg_prompt_reason_user_request;
116             case PROMPT_REASON_NONE:
117                 return 0;
118             default:
119                 return R.string.kg_prompt_reason_timeout_pin;
120         }
121     }
122 
performClick(View view)123     private void performClick(View view) {
124         view.performClick();
125     }
126 
performNumberClick(int number)127     private void performNumberClick(int number) {
128         switch (number) {
129             case 0:
130                 performClick(mButton0);
131                 break;
132             case 1:
133                 performClick(mButton1);
134                 break;
135             case 2:
136                 performClick(mButton2);
137                 break;
138             case 3:
139                 performClick(mButton3);
140                 break;
141             case 4:
142                 performClick(mButton4);
143                 break;
144             case 5:
145                 performClick(mButton5);
146                 break;
147             case 6:
148                 performClick(mButton6);
149                 break;
150             case 7:
151                 performClick(mButton7);
152                 break;
153             case 8:
154                 performClick(mButton8);
155                 break;
156             case 9:
157                 performClick(mButton9);
158                 break;
159         }
160     }
161 
162     @Override
resetPasswordText(boolean animate, boolean announce)163     protected void resetPasswordText(boolean animate, boolean announce) {
164         mPasswordEntry.reset(animate, announce);
165     }
166 
167     @Override
getPasswordText()168     protected byte[] getPasswordText() {
169         return charSequenceToByteArray(mPasswordEntry.getText());
170     }
171 
172     @Override
onFinishInflate()173     protected void onFinishInflate() {
174         mPasswordEntry = findViewById(getPasswordTextViewId());
175         mPasswordEntry.setOnKeyListener(this);
176 
177         // Set selected property on so the view can send accessibility events.
178         mPasswordEntry.setSelected(true);
179 
180         mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() {
181             @Override
182             public void onUserActivity() {
183                 onUserInput();
184             }
185         });
186 
187         mOkButton = findViewById(R.id.key_enter);
188         if (mOkButton != null) {
189             mOkButton.setOnTouchListener(this);
190             mOkButton.setOnClickListener(new View.OnClickListener() {
191                 @Override
192                 public void onClick(View v) {
193                     if (mPasswordEntry.isEnabled()) {
194                         verifyPasswordAndUnlock();
195                     }
196                 }
197             });
198             mOkButton.setOnHoverListener(new LiftToActivateListener(getContext()));
199         }
200 
201         mDeleteButton = findViewById(R.id.delete_button);
202         mDeleteButton.setVisibility(View.VISIBLE);
203         mDeleteButton.setOnTouchListener(this);
204         mDeleteButton.setOnClickListener(new OnClickListener() {
205             @Override
206             public void onClick(View v) {
207                 // check for time-based lockouts
208                 if (mPasswordEntry.isEnabled()) {
209                     mPasswordEntry.deleteLastChar();
210                 }
211             }
212         });
213         mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() {
214             @Override
215             public boolean onLongClick(View v) {
216                 // check for time-based lockouts
217                 if (mPasswordEntry.isEnabled()) {
218                     resetPasswordText(true /* animate */, true /* announce */);
219                 }
220                 doHapticKeyClick();
221                 return true;
222             }
223         });
224 
225         mButton0 = findViewById(R.id.key0);
226         mButton1 = findViewById(R.id.key1);
227         mButton2 = findViewById(R.id.key2);
228         mButton3 = findViewById(R.id.key3);
229         mButton4 = findViewById(R.id.key4);
230         mButton5 = findViewById(R.id.key5);
231         mButton6 = findViewById(R.id.key6);
232         mButton7 = findViewById(R.id.key7);
233         mButton8 = findViewById(R.id.key8);
234         mButton9 = findViewById(R.id.key9);
235 
236         mPasswordEntry.requestFocus();
237         super.onFinishInflate();
238     }
239 
240     @Override
onResume(int reason)241     public void onResume(int reason) {
242         super.onResume(reason);
243         mPasswordEntry.requestFocus();
244     }
245 
246     @Override
onTouch(View v, MotionEvent event)247     public boolean onTouch(View v, MotionEvent event) {
248         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
249             doHapticKeyClick();
250         }
251         return false;
252     }
253 
254     @Override
onKey(View v, int keyCode, KeyEvent event)255     public boolean onKey(View v, int keyCode, KeyEvent event) {
256         if (event.getAction() == KeyEvent.ACTION_DOWN) {
257             return onKeyDown(keyCode, event);
258         }
259         return false;
260     }
261 
262     @Override
getTitle()263     public CharSequence getTitle() {
264         return getContext().getString(
265                 com.android.internal.R.string.keyguard_accessibility_pin_unlock);
266     }
267 
268     /*
269      * This method avoids creating a new string when getting a byte array from EditView#getText().
270      */
charSequenceToByteArray(CharSequence chars)271     private static byte[] charSequenceToByteArray(CharSequence chars) {
272         if (chars == null) {
273             return null;
274         }
275         byte[] bytes = new byte[chars.length()];
276         for (int i = 0; i < chars.length(); i++) {
277             bytes[i] = (byte) chars.charAt(i);
278         }
279         return bytes;
280     }
281 }
282