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 { 47 sToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1); 48 sToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2); 49 sToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3); 50 sToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4); 51 sToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5); 52 sToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6); 53 sToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7); 54 sToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8); 55 sToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9); 56 sToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0); 57 sToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S); 58 sToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P); 59 60 sDialValueMap.put(KeyEvent.KEYCODE_1, "1"); 61 sDialValueMap.put(KeyEvent.KEYCODE_2, "2"); 62 sDialValueMap.put(KeyEvent.KEYCODE_3, "3"); 63 sDialValueMap.put(KeyEvent.KEYCODE_4, "4"); 64 sDialValueMap.put(KeyEvent.KEYCODE_5, "5"); 65 sDialValueMap.put(KeyEvent.KEYCODE_6, "6"); 66 sDialValueMap.put(KeyEvent.KEYCODE_7, "7"); 67 sDialValueMap.put(KeyEvent.KEYCODE_8, "8"); 68 sDialValueMap.put(KeyEvent.KEYCODE_9, "9"); 69 sDialValueMap.put(KeyEvent.KEYCODE_0, "0"); 70 sDialValueMap.put(KeyEvent.KEYCODE_STAR, "*"); 71 sDialValueMap.put(KeyEvent.KEYCODE_POUND, "#"); 72 73 sRIdMap.put(KeyEvent.KEYCODE_1, R.id.one); 74 sRIdMap.put(KeyEvent.KEYCODE_2, R.id.two); 75 sRIdMap.put(KeyEvent.KEYCODE_3, R.id.three); 76 sRIdMap.put(KeyEvent.KEYCODE_4, R.id.four); 77 sRIdMap.put(KeyEvent.KEYCODE_5, R.id.five); 78 sRIdMap.put(KeyEvent.KEYCODE_6, R.id.six); 79 sRIdMap.put(KeyEvent.KEYCODE_7, R.id.seven); 80 sRIdMap.put(KeyEvent.KEYCODE_8, R.id.eight); 81 sRIdMap.put(KeyEvent.KEYCODE_9, R.id.nine); 82 sRIdMap.put(KeyEvent.KEYCODE_0, R.id.zero); 83 sRIdMap.put(KeyEvent.KEYCODE_STAR, R.id.star); 84 sRIdMap.put(KeyEvent.KEYCODE_POUND, R.id.pound); 85 } 86 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 */ 98 void onDialVoiceMail(); 99 100 /** 101 * Called when a digit should be append. 102 */ 103 void onAppendDigit(String digit); 104 } 105 106 private ToneGenerator mToneGenerator; 107 private DialpadCallback mDialpadCallback; 108 109 @Override 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 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 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 144 DialpadClickListener(int keyCode) { 145 mTone = sToneMap.get(keyCode); 146 mValue = sDialValueMap.get(keyCode); 147 } 148 149 @Override 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 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 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 205 private void stopTone() { 206 if (mToneGenerator == null) { 207 return; 208 } 209 210 mToneGenerator.stopTone(); 211 } 212 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