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