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.dialer.ui.dialpad;
18 
19 import android.os.Bundle;
20 import android.util.SparseArray;
21 import android.view.KeyEvent;
22 import android.view.LayoutInflater;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.view.ViewGroup;
26 
27 import androidx.annotation.IntDef;
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.fragment.app.Fragment;
31 
32 import com.android.car.dialer.R;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 
37 /**
38  * Fragment which displays a pad of keys.
39  */
40 public class KeypadFragment extends Fragment {
41     private static final SparseArray<Integer> sRIdMap = new SparseArray<>();
42 
43     static {
sRIdMap.put(KeyEvent.KEYCODE_1, R.id.one)44         sRIdMap.put(KeyEvent.KEYCODE_1, R.id.one);
sRIdMap.put(KeyEvent.KEYCODE_2, R.id.two)45         sRIdMap.put(KeyEvent.KEYCODE_2, R.id.two);
sRIdMap.put(KeyEvent.KEYCODE_3, R.id.three)46         sRIdMap.put(KeyEvent.KEYCODE_3, R.id.three);
sRIdMap.put(KeyEvent.KEYCODE_4, R.id.four)47         sRIdMap.put(KeyEvent.KEYCODE_4, R.id.four);
sRIdMap.put(KeyEvent.KEYCODE_5, R.id.five)48         sRIdMap.put(KeyEvent.KEYCODE_5, R.id.five);
sRIdMap.put(KeyEvent.KEYCODE_6, R.id.six)49         sRIdMap.put(KeyEvent.KEYCODE_6, R.id.six);
sRIdMap.put(KeyEvent.KEYCODE_7, R.id.seven)50         sRIdMap.put(KeyEvent.KEYCODE_7, R.id.seven);
sRIdMap.put(KeyEvent.KEYCODE_8, R.id.eight)51         sRIdMap.put(KeyEvent.KEYCODE_8, R.id.eight);
sRIdMap.put(KeyEvent.KEYCODE_9, R.id.nine)52         sRIdMap.put(KeyEvent.KEYCODE_9, R.id.nine);
sRIdMap.put(KeyEvent.KEYCODE_0, R.id.zero)53         sRIdMap.put(KeyEvent.KEYCODE_0, R.id.zero);
sRIdMap.put(KeyEvent.KEYCODE_STAR, R.id.star)54         sRIdMap.put(KeyEvent.KEYCODE_STAR, R.id.star);
sRIdMap.put(KeyEvent.KEYCODE_POUND, R.id.pound)55         sRIdMap.put(KeyEvent.KEYCODE_POUND, R.id.pound);
56     }
57 
58     /** Valid keycodes that can be sent to the callback. **/
59     @Retention(RetentionPolicy.SOURCE)
60     @IntDef({KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3,
61             KeyEvent.KEYCODE_4, KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_6, KeyEvent.KEYCODE_7,
62             KeyEvent.KEYCODE_8, KeyEvent.KEYCODE_9, KeyEvent.KEYCODE_STAR, KeyEvent.KEYCODE_POUND})
63     @interface DialKeyCode {
64     }
65 
66     /** Callback for keypad to interact with its host. */
67     public interface KeypadCallback {
68 
69         /** Called when a key is long pressed. */
onKeypadKeyLongPressed(@ialKeyCode int keycode)70         void onKeypadKeyLongPressed(@DialKeyCode int keycode);
71 
72         /** Called when a key is pressed down. */
onKeypadKeyDown(@ialKeyCode int keycode)73         void onKeypadKeyDown(@DialKeyCode int keycode);
74 
75         /** Called when a key is released. */
onKeypadKeyUp(@ialKeyCode int keycode)76         void onKeypadKeyUp(@DialKeyCode int keycode);
77     }
78 
79 
80     private KeypadCallback mKeypadCallback;
81 
82     @Override
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)83     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
84             @Nullable Bundle savedInstanceState) {
85         if (getParentFragment() instanceof KeypadCallback) {
86             mKeypadCallback = (KeypadCallback) getParentFragment();
87         } else if (getHost() instanceof KeypadCallback) {
88             mKeypadCallback = (KeypadCallback) getHost();
89         }
90 
91         View keypadView = inflater.inflate(R.layout.keypad, container, false);
92         setupKeypadClickListeners(keypadView);
93         return keypadView;
94     }
95 
96     /**
97      * The click listener for all keypad buttons.  Reacts to touch-down and touch-up events, as
98      * well as long-press for certain keys.  Mimics the behavior of the phone dialer app.
99      */
100     private class KeypadClickListener implements View.OnTouchListener,
101             View.OnLongClickListener, View.OnKeyListener, View.OnFocusChangeListener {
102         private final int mKeycode;
103         private boolean mIsKeyDown = false;
104 
KeypadClickListener(@ialKeyCode int keyCode)105         KeypadClickListener(@DialKeyCode int keyCode) {
106             mKeycode = keyCode;
107         }
108 
109         @Override
onLongClick(View v)110         public boolean onLongClick(View v) {
111             mKeypadCallback.onKeypadKeyLongPressed(mKeycode);
112             return true;
113         }
114 
115         @Override
onTouch(View v, MotionEvent event)116         public boolean onTouch(View v, MotionEvent event) {
117             if (mKeypadCallback != null) {
118                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
119                     mKeypadCallback.onKeypadKeyDown(mKeycode);
120                 } else if (event.getAction() == MotionEvent.ACTION_UP) {
121                     mKeypadCallback.onKeypadKeyUp(mKeycode);
122                 }
123             }
124 
125             // Continue propagating the event
126             return false;
127         }
128 
129         @Override
onKey(View v, int keyCode, KeyEvent event)130         public boolean onKey(View v, int keyCode, KeyEvent event) {
131             if (mKeypadCallback != null && KeyEvent.isConfirmKey(keyCode)) {
132                 if (event.getAction() == KeyEvent.ACTION_DOWN && !mIsKeyDown) {
133                     mIsKeyDown = true;
134                     mKeypadCallback.onKeypadKeyDown(mKeycode);
135                 } else if (event.getAction() == KeyEvent.ACTION_UP && mIsKeyDown) {
136                     mIsKeyDown = false;
137                     mKeypadCallback.onKeypadKeyUp(mKeycode);
138                 }
139             }
140 
141             // Continue propagating the event
142             return false;
143         }
144 
145         @Override
onFocusChange(View v, boolean hasFocus)146         public void onFocusChange(View v, boolean hasFocus) {
147             if (!hasFocus && mIsKeyDown) {
148                 mIsKeyDown = false;
149                 mKeypadCallback.onKeypadKeyUp(mKeycode);
150             }
151         }
152     }
153 
setupKeypadClickListeners(View parent)154     private void setupKeypadClickListeners(View parent) {
155         for (int i = 0; i < sRIdMap.size(); i++) {
156             int key = sRIdMap.keyAt(i);
157             KeypadClickListener clickListener = new KeypadClickListener(key);
158             View v = parent.findViewById(sRIdMap.get(key));
159             v.setOnTouchListener(clickListener);
160             v.setOnLongClickListener(clickListener);
161             v.setOnKeyListener(clickListener);
162             v.setOnFocusChangeListener(clickListener);
163         }
164     }
165 }
166