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