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 package com.android.car.dialer.ui; 17 18 import android.media.AudioManager; 19 import android.media.ToneGenerator; 20 import android.os.Bundle; 21 import android.support.annotation.NonNull; 22 import android.support.annotation.Nullable; 23 import android.support.v4.app.Fragment; 24 import android.util.SparseArray; 25 import android.util.SparseIntArray; 26 import android.view.KeyEvent; 27 import android.view.LayoutInflater; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewGroup; 31 32 import com.android.car.dialer.R; 33 import com.android.car.dialer.telecom.UiCallManager; 34 35 /** 36 * Dialpad Fragment which displays a dialpad. 37 */ 38 public class DialpadFragment extends Fragment { 39 private static final SparseIntArray sToneMap = new SparseIntArray(); 40 private static final SparseArray<String> sDialValueMap = new SparseArray<>(); 41 private static final SparseArray<Integer> sRIdMap = new SparseArray<>(); 42 43 private static final int TONE_LENGTH_INFINITE = -1; 44 private static final int TONE_RELATIVE_VOLUME = 80; 45 46 static { sToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1)47 sToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1); sToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2)48 sToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2); sToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3)49 sToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3); sToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4)50 sToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4); sToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5)51 sToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5); sToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6)52 sToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6); sToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7)53 sToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7); sToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8)54 sToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8); sToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9)55 sToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9); sToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0)56 sToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0); sToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S)57 sToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S); sToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P)58 sToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P); 59 sDialValueMap.put(KeyEvent.KEYCODE_1, "1")60 sDialValueMap.put(KeyEvent.KEYCODE_1, "1"); sDialValueMap.put(KeyEvent.KEYCODE_2, "2")61 sDialValueMap.put(KeyEvent.KEYCODE_2, "2"); sDialValueMap.put(KeyEvent.KEYCODE_3, "3")62 sDialValueMap.put(KeyEvent.KEYCODE_3, "3"); sDialValueMap.put(KeyEvent.KEYCODE_4, "4")63 sDialValueMap.put(KeyEvent.KEYCODE_4, "4"); sDialValueMap.put(KeyEvent.KEYCODE_5, "5")64 sDialValueMap.put(KeyEvent.KEYCODE_5, "5"); sDialValueMap.put(KeyEvent.KEYCODE_6, "6")65 sDialValueMap.put(KeyEvent.KEYCODE_6, "6"); sDialValueMap.put(KeyEvent.KEYCODE_7, "7")66 sDialValueMap.put(KeyEvent.KEYCODE_7, "7"); sDialValueMap.put(KeyEvent.KEYCODE_8, "8")67 sDialValueMap.put(KeyEvent.KEYCODE_8, "8"); sDialValueMap.put(KeyEvent.KEYCODE_9, "9")68 sDialValueMap.put(KeyEvent.KEYCODE_9, "9"); sDialValueMap.put(KeyEvent.KEYCODE_0, "0")69 sDialValueMap.put(KeyEvent.KEYCODE_0, "0"); sDialValueMap.put(KeyEvent.KEYCODE_STAR, "*")70 sDialValueMap.put(KeyEvent.KEYCODE_STAR, "*"); sDialValueMap.put(KeyEvent.KEYCODE_POUND, "#")71 sDialValueMap.put(KeyEvent.KEYCODE_POUND, "#"); 72 sRIdMap.put(KeyEvent.KEYCODE_1, R.id.one)73 sRIdMap.put(KeyEvent.KEYCODE_1, R.id.one); sRIdMap.put(KeyEvent.KEYCODE_2, R.id.two)74 sRIdMap.put(KeyEvent.KEYCODE_2, R.id.two); sRIdMap.put(KeyEvent.KEYCODE_3, R.id.three)75 sRIdMap.put(KeyEvent.KEYCODE_3, R.id.three); sRIdMap.put(KeyEvent.KEYCODE_4, R.id.four)76 sRIdMap.put(KeyEvent.KEYCODE_4, R.id.four); sRIdMap.put(KeyEvent.KEYCODE_5, R.id.five)77 sRIdMap.put(KeyEvent.KEYCODE_5, R.id.five); sRIdMap.put(KeyEvent.KEYCODE_6, R.id.six)78 sRIdMap.put(KeyEvent.KEYCODE_6, R.id.six); sRIdMap.put(KeyEvent.KEYCODE_7, R.id.seven)79 sRIdMap.put(KeyEvent.KEYCODE_7, R.id.seven); sRIdMap.put(KeyEvent.KEYCODE_8, R.id.eight)80 sRIdMap.put(KeyEvent.KEYCODE_8, R.id.eight); sRIdMap.put(KeyEvent.KEYCODE_9, R.id.nine)81 sRIdMap.put(KeyEvent.KEYCODE_9, R.id.nine); sRIdMap.put(KeyEvent.KEYCODE_0, R.id.zero)82 sRIdMap.put(KeyEvent.KEYCODE_0, R.id.zero); sRIdMap.put(KeyEvent.KEYCODE_STAR, R.id.star)83 sRIdMap.put(KeyEvent.KEYCODE_STAR, R.id.star); sRIdMap.put(KeyEvent.KEYCODE_POUND, R.id.pound)84 sRIdMap.put(KeyEvent.KEYCODE_POUND, R.id.pound); 85 } 86 newInstance()87 public static DialpadFragment newInstance() { 88 return new DialpadFragment(); 89 } 90 91 /** 92 * Callback for dialpad to interact with its host. 93 */ 94 public interface DialpadCallback { 95 /** 96 * Called when voice mail should be dialed. 97 */ onDialVoiceMail()98 void onDialVoiceMail(); 99 100 /** 101 * Called when a digit should be append. 102 */ onAppendDigit(String digit)103 void onAppendDigit(String digit); 104 } 105 106 private ToneGenerator mToneGenerator; 107 private DialpadCallback mDialpadCallback; 108 109 @Override onCreate(@ullable Bundle savedInstanceState)110 public void onCreate(@Nullable Bundle savedInstanceState) { 111 super.onCreate(savedInstanceState); 112 mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME); 113 } 114 115 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)116 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 117 @Nullable Bundle savedInstanceState) { 118 if (getParentFragment() != null && getParentFragment() instanceof DialpadCallback) { 119 mDialpadCallback = (DialpadCallback) getParentFragment(); 120 } else if (getHost() instanceof DialpadCallback) { 121 mDialpadCallback = (DialpadCallback) getHost(); 122 } 123 124 View dialpadView = inflater.inflate(R.layout.dialpad, container, false); 125 setupKeypadClickListeners(dialpadView); 126 return dialpadView; 127 } 128 129 @Override onPause()130 public void onPause() { 131 super.onPause(); 132 stopTone(); 133 } 134 135 /** 136 * The click listener for all dialpad buttons. Reacts to touch-down and touch-up events, as 137 * well as long-press for certain keys. Mimics the behavior of the phone dialer app. 138 */ 139 private class DialpadClickListener implements View.OnTouchListener, 140 View.OnLongClickListener { 141 private final int mTone; 142 private final String mValue; 143 DialpadClickListener(int keyCode)144 DialpadClickListener(int keyCode) { 145 mTone = sToneMap.get(keyCode); 146 mValue = sDialValueMap.get(keyCode); 147 } 148 149 @Override onLongClick(View v)150 public boolean onLongClick(View v) { 151 switch (mValue) { 152 case "0": 153 if (mDialpadCallback != null) { 154 mDialpadCallback.onAppendDigit("+"); 155 } 156 stopTone(); 157 return true; 158 case "1": 159 // TODO: this currently does not work (at least over bluetooth HFP), because 160 // the framework is unable to get the voicemail number. Revisit later... 161 if (mDialpadCallback != null) { 162 mDialpadCallback.onDialVoiceMail(); 163 } 164 return true; 165 default: 166 return false; 167 } 168 } 169 170 @Override onTouch(View v, MotionEvent event)171 public boolean onTouch(View v, MotionEvent event) { 172 UiCallManager uiCallmanager = UiCallManager.get(); 173 boolean hasActiveCall = uiCallmanager.getPrimaryCall() != null; 174 if (event.getAction() == MotionEvent.ACTION_DOWN) { 175 if (mDialpadCallback != null) { 176 mDialpadCallback.onAppendDigit(mValue); 177 } 178 if (hasActiveCall) { 179 uiCallmanager.playDtmfTone(uiCallmanager.getPrimaryCall(), mValue.charAt(0)); 180 } else { 181 playTone(mTone); 182 } 183 } else if (event.getAction() == MotionEvent.ACTION_UP) { 184 if (hasActiveCall) { 185 uiCallmanager.stopDtmfTone(uiCallmanager.getPrimaryCall()); 186 } else { 187 stopTone(); 188 } 189 } 190 191 // Continue propagating the touch event 192 return false; 193 } 194 } 195 playTone(int tone)196 private void playTone(int tone) { 197 if (mToneGenerator == null) { 198 return; 199 } 200 201 // Start the new tone 202 mToneGenerator.startTone(tone, TONE_LENGTH_INFINITE); 203 } 204 stopTone()205 private void stopTone() { 206 if (mToneGenerator == null) { 207 return; 208 } 209 210 mToneGenerator.stopTone(); 211 } 212 setupKeypadClickListeners(View parent)213 private void setupKeypadClickListeners(View parent) { 214 for (int i = 0; i < sRIdMap.size(); i++) { 215 int key = sRIdMap.keyAt(i); 216 DialpadClickListener clickListener = new DialpadClickListener(key); 217 View v = parent.findViewById(sRIdMap.get(key)); 218 v.setOnTouchListener(clickListener); 219 v.setOnLongClickListener(clickListener); 220 } 221 } 222 } 223