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.content.res.ColorStateList;
21 import android.content.res.TypedArray;
22 import android.util.AttributeSet;
23 import android.view.LayoutInflater;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import android.widget.GridLayout;
27 import android.widget.ImageButton;
28 import android.widget.TextView;
29 
30 import androidx.annotation.DrawableRes;
31 import androidx.annotation.Nullable;
32 import androidx.annotation.VisibleForTesting;
33 
34 import com.android.car.settings.R;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * A custom view for the PIN pad.
41  */
42 public class PinPadView extends GridLayout {
43     // Number of keys in the pin pad, 0-9 plus backspace and enter keys.
44     @VisibleForTesting
45     static final int NUM_KEYS = 12;
46 
47     @VisibleForTesting
48     static final int[] PIN_PAD_DIGIT_KEYS = {R.id.key0, R.id.key1, R.id.key2, R.id.key3,
49             R.id.key4, R.id.key5, R.id.key6, R.id.key7, R.id.key8, R.id.key9};
50 
51     /**
52      * The delay in milliseconds between character deletion when the user continuously holds the
53      * backspace key.
54      */
55     private static final int LONG_CLICK_DELAY_MILLS = 100;
56 
57     private final List<View> mPinKeys = new ArrayList<>(NUM_KEYS);
58     private final Runnable mOnBackspaceLongClick = new Runnable() {
59         public void run() {
60             if (mOnClickListener != null) {
61                 mOnClickListener.onBackspaceClick();
62                 getHandler().postDelayed(this, LONG_CLICK_DELAY_MILLS);
63             }
64         }
65     };
66 
67     private PinPadClickListener mOnClickListener;
68     private ImageButton mEnterKey;
69 
PinPadView(Context context)70     public PinPadView(Context context) {
71         super(context);
72         init(null, 0, 0);
73     }
74 
PinPadView(Context context, AttributeSet attrs)75     public PinPadView(Context context, AttributeSet attrs) {
76         super(context, attrs);
77         init(attrs, 0, 0);
78     }
79 
PinPadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)80     public PinPadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
81         super(context, attrs, defStyleAttr);
82         init(attrs, defStyleAttr, 0);
83     }
84 
PinPadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)85     public PinPadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
86             int defStyleRes) {
87         super(context, attrs, defStyleAttr, defStyleRes);
88         init(attrs, defStyleAttr, defStyleRes);
89     }
90 
91     /**
92      * Set the call back for key click.
93      *
94      * @param pinPadClickListener The call back.
95      */
setPinPadClickListener(PinPadClickListener pinPadClickListener)96     public void setPinPadClickListener(PinPadClickListener pinPadClickListener) {
97         mOnClickListener = pinPadClickListener;
98     }
99 
100     @Override
setEnabled(boolean enabled)101     public void setEnabled(boolean enabled) {
102         super.setEnabled(enabled);
103         for (View key : mPinKeys) {
104             key.setEnabled(enabled);
105         }
106     }
107 
108     /**
109      * Set the resource Id of the enter key icon.
110      *
111      * @param drawableId The resource Id of the drawable.
112      */
setEnterKeyIcon(@rawableRes int drawableId)113     public void setEnterKeyIcon(@DrawableRes int drawableId) {
114         mEnterKey.setImageResource(drawableId);
115     }
116 
117     /**
118      * Override the default tint of the enter key icon.
119      *
120      * @param tint A ColorStateList.
121      */
setEnterKeyImageTint(ColorStateList tint)122     public void setEnterKeyImageTint(ColorStateList tint) {
123         mEnterKey.setImageTintList(tint);
124     }
125 
126     /**
127      * Sets if the enter key for submitting a PIN is enabled or disabled.
128      */
setEnterKeyEnabled(boolean enabled)129     public void setEnterKeyEnabled(boolean enabled) {
130         mEnterKey.setEnabled(enabled);
131     }
132 
init(AttributeSet attrs, int defStyleAttr, int defStyleRes)133     private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
134         LayoutInflater inflater = LayoutInflater.from(getContext());
135         TypedArray typedArray = getContext().obtainStyledAttributes(
136                 attrs, R.styleable.PinPadView, defStyleAttr, defStyleRes);
137         inflater.inflate(
138                 typedArray.getResourceId(R.styleable.PinPadView_layout, R.layout.pin_pad_view),
139                 this, true);
140         typedArray.recycle();
141 
142         for (int keyId : PIN_PAD_DIGIT_KEYS) {
143             TextView key = findViewById(keyId);
144             String digit = key.getTag().toString();
145             key.setOnClickListener(v -> mOnClickListener.onDigitKeyClick(digit));
146             mPinKeys.add(key);
147         }
148 
149         ImageButton backspace = findViewById(R.id.key_backspace);
150         backspace.setOnTouchListener((v, event) -> {
151             switch (event.getAction()) {
152                 case MotionEvent.ACTION_DOWN:
153                     getHandler().post(mOnBackspaceLongClick);
154                     // Must return false so that ripple can show
155                     return false;
156                 case MotionEvent.ACTION_UP:
157                     getHandler().removeCallbacks(mOnBackspaceLongClick);
158                     // Must return false so that ripple can show
159                     return false;
160                 default:
161                     return false;
162             }
163         });
164         mPinKeys.add(backspace);
165 
166         mEnterKey = findViewById(R.id.key_enter);
167         mEnterKey.setOnClickListener(v -> mOnClickListener.onEnterKeyClick());
168 
169         mPinKeys.add(mEnterKey);
170     }
171 
172     /**
173      * The call back interface for onClick event in the view.
174      */
175     public interface PinPadClickListener {
176         /**
177          * One of the digit key has been clicked.
178          *
179          * @param digit A String representing a digit between 0 and 9.
180          */
onDigitKeyClick(String digit)181         void onDigitKeyClick(String digit);
182 
183         /**
184          * The backspace key has been clicked.
185          */
onBackspaceClick()186         void onBackspaceClick();
187 
188         /**
189          * The enter key has been clicked.
190          */
onEnterKeyClick()191         void onEnterKeyClick();
192     }
193 }
194