blob: b8adaf1b4c98c06b6133af50592f00528ddfdf08 [file] [log] [blame]
Santos Cordon2eaff902013-08-05 04:37:55 -07001/*
2 * Copyright (C) 2013 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
17package com.android.phone;
18
19import com.google.common.collect.ImmutableMap;
20
Christine Chendaf7bf62013-08-05 19:12:31 -070021import android.content.Context;
Santos Cordon2eaff902013-08-05 04:37:55 -070022import android.media.AudioManager;
23import android.media.ToneGenerator;
24import android.os.Handler;
25import android.os.Message;
26import android.provider.Settings;
27import android.util.Log;
28
29import com.android.internal.telephony.CallManager;
Yorke Leede41f672013-09-19 13:46:55 -070030import com.android.internal.telephony.Connection.PostDialState;
Santos Cordon2eaff902013-08-05 04:37:55 -070031import com.android.internal.telephony.Phone;
Christine Chendaf7bf62013-08-05 19:12:31 -070032import com.android.internal.telephony.PhoneConstants;
Santos Cordon2eaff902013-08-05 04:37:55 -070033import com.android.services.telephony.common.Call;
34
Christine Chendaf7bf62013-08-05 19:12:31 -070035import java.util.LinkedList;
Santos Cordon2eaff902013-08-05 04:37:55 -070036import java.util.List;
37import java.util.Map;
Christine Chendaf7bf62013-08-05 19:12:31 -070038import java.util.Queue;
Santos Cordon2eaff902013-08-05 04:37:55 -070039
40/**
41 * Playing DTMF tones through the CallManager.
42 */
43public class DTMFTonePlayer implements CallModeler.Listener {
44 private static final String LOG_TAG = DTMFTonePlayer.class.getSimpleName();
45 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
46
Christine Chendaf7bf62013-08-05 19:12:31 -070047 private static final int DTMF_SEND_CNF = 100;
48 private static final int DTMF_STOP = 101;
Santos Cordon2eaff902013-08-05 04:37:55 -070049
50 /** Hash Map to map a character to a tone*/
51 private static final Map<Character, Integer> mToneMap =
52 ImmutableMap.<Character, Integer>builder()
53 .put('1', ToneGenerator.TONE_DTMF_1)
54 .put('2', ToneGenerator.TONE_DTMF_2)
55 .put('3', ToneGenerator.TONE_DTMF_3)
56 .put('4', ToneGenerator.TONE_DTMF_4)
57 .put('5', ToneGenerator.TONE_DTMF_5)
58 .put('6', ToneGenerator.TONE_DTMF_6)
59 .put('7', ToneGenerator.TONE_DTMF_7)
60 .put('8', ToneGenerator.TONE_DTMF_8)
61 .put('9', ToneGenerator.TONE_DTMF_9)
62 .put('0', ToneGenerator.TONE_DTMF_0)
63 .put('#', ToneGenerator.TONE_DTMF_P)
64 .put('*', ToneGenerator.TONE_DTMF_S)
65 .build();
66
67 private final CallManager mCallManager;
68 private final CallModeler mCallModeler;
69 private final Object mToneGeneratorLock = new Object();
70 private ToneGenerator mToneGenerator;
71 private boolean mLocalToneEnabled;
72
Christine Chendaf7bf62013-08-05 19:12:31 -070073 // indicates that we are using automatically shortened DTMF tones
74 boolean mShortTone;
75
76 // indicate if the confirmation from TelephonyFW is pending.
77 private boolean mDTMFBurstCnfPending = false;
78
79 // Queue to queue the short dtmf characters.
80 private Queue<Character> mDTMFQueue = new LinkedList<Character>();
81
82 // Short Dtmf tone duration
83 private static final int DTMF_DURATION_MS = 120;
84
85 /**
86 * Our own handler to take care of the messages from the phone state changes
87 */
88 private final Handler mHandler = new Handler() {
89 @Override
90 public void handleMessage(Message msg) {
91 switch (msg.what) {
92 case DTMF_SEND_CNF:
93 logD("dtmf confirmation received from FW.");
94 // handle burst dtmf confirmation
95 handleBurstDtmfConfirmation();
96 break;
97 case DTMF_STOP:
98 logD("dtmf stop received");
99 stopDtmfTone();
100 break;
101 }
102 }
103 };
104
Santos Cordon2eaff902013-08-05 04:37:55 -0700105 public DTMFTonePlayer(CallManager callManager, CallModeler callModeler) {
106 mCallManager = callManager;
107 mCallModeler = callModeler;
Christine Chendaf7bf62013-08-05 19:12:31 -0700108 mCallModeler.addListener(this);
Santos Cordon2eaff902013-08-05 04:37:55 -0700109 }
110
111 @Override
112 public void onDisconnect(Call call) {
Christine Chendaf7bf62013-08-05 19:12:31 -0700113 logD("Call disconnected");
Santos Cordon2eaff902013-08-05 04:37:55 -0700114 checkCallState();
115 }
116
117 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700118 public void onIncoming(Call call) {
Christine Chenee09a492013-08-06 16:02:29 -0700119 }
120
121 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700122 public void onUpdate(List<Call> calls) {
Christine Chendaf7bf62013-08-05 19:12:31 -0700123 logD("Call updated");
Santos Cordon2eaff902013-08-05 04:37:55 -0700124 checkCallState();
125 }
126
Chiao Cheng3f015c92013-09-06 15:56:27 -0700127 @Override
Yorke Leede41f672013-09-19 13:46:55 -0700128 public void onPostDialAction(PostDialState state, int callId, String remainingChars,
129 char currentChar) {
130 switch (state) {
131 case STARTED:
132 stopLocalToneIfNeeded();
133 if (!mToneMap.containsKey(currentChar)) {
134 return;
135 }
136 startLocalToneIfNeeded(currentChar);
137 break;
138 case PAUSE:
139 case WAIT:
140 case WILD:
141 case COMPLETE:
142 stopLocalToneIfNeeded();
143 break;
144 default:
145 break;
146 }
Chiao Cheng3f015c92013-09-06 15:56:27 -0700147 }
148
Santos Cordon2eaff902013-08-05 04:37:55 -0700149 /**
150 * Allocates some resources we keep around during a "dialer session".
151 *
152 * (Currently, a "dialer session" just means any situation where we
153 * might need to play local DTMF tones, which means that we need to
154 * keep a ToneGenerator instance around. A ToneGenerator instance
155 * keeps an AudioTrack resource busy in AudioFlinger, so we don't want
156 * to keep it around forever.)
157 *
158 * Call {@link stopDialerSession} to release the dialer session
159 * resources.
160 */
161 public void startDialerSession() {
162 logD("startDialerSession()... this = " + this);
163
164 // see if we need to play local tones.
165 if (PhoneGlobals.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
166 mLocalToneEnabled = Settings.System.getInt(
167 PhoneGlobals.getInstance().getContentResolver(),
168 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
169 } else {
170 mLocalToneEnabled = false;
171 }
172 logD("- startDialerSession: mLocalToneEnabled = " + mLocalToneEnabled);
173
174 // create the tone generator
175 // if the mToneGenerator creation fails, just continue without it. It is
176 // a local audio signal, and is not as important as the dtmf tone itself.
177 if (mLocalToneEnabled) {
178 synchronized (mToneGeneratorLock) {
179 if (mToneGenerator == null) {
180 try {
181 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
182 } catch (RuntimeException e) {
183 Log.e(LOG_TAG, "Exception caught while creating local tone generator", e);
184 mToneGenerator = null;
185 }
186 }
187 }
188 }
189 }
190
191 /**
192 * Releases resources we keep around during a "dialer session"
193 * (see {@link startDialerSession}).
194 *
195 * It's safe to call this even without a corresponding
196 * startDialerSession call.
197 */
198 public void stopDialerSession() {
199 // release the tone generator.
200 synchronized (mToneGeneratorLock) {
201 if (mToneGenerator != null) {
202 mToneGenerator.release();
203 mToneGenerator = null;
204 }
205 }
Christine Chendaf7bf62013-08-05 19:12:31 -0700206
207 mHandler.removeMessages(DTMF_SEND_CNF);
208 synchronized (mDTMFQueue) {
209 mDTMFBurstCnfPending = false;
210 mDTMFQueue.clear();
211 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700212 }
213
214 /**
215 * Starts playback of the dtmf tone corresponding to the parameter.
216 */
Christine Chendaf7bf62013-08-05 19:12:31 -0700217 public void playDtmfTone(char c, boolean timedShortTone) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700218 // Only play the tone if it exists.
219 if (!mToneMap.containsKey(c)) {
220 return;
221 }
222
223 if (!okToDialDtmfTones()) {
224 return;
225 }
226
Christine Chendaf7bf62013-08-05 19:12:31 -0700227 PhoneGlobals.getInstance().pokeUserActivity();
228
Santos Cordon2eaff902013-08-05 04:37:55 -0700229 // Read the settings as it may be changed by the user during the call
230 Phone phone = mCallManager.getFgPhone();
231
Christine Chendaf7bf62013-08-05 19:12:31 -0700232 // Before we go ahead and start a tone, we need to make sure that any pending
233 // stop-tone message is processed.
234 if (mHandler.hasMessages(DTMF_STOP)) {
235 mHandler.removeMessages(DTMF_STOP);
236 stopDtmfTone();
237 }
238
239 mShortTone = useShortDtmfTones(phone, phone.getContext());
Santos Cordon2eaff902013-08-05 04:37:55 -0700240 logD("startDtmfTone()...");
241
Christine Chendaf7bf62013-08-05 19:12:31 -0700242 // For Short DTMF we need to play the local tone for fixed duration
243 if (mShortTone) {
244 sendShortDtmfToNetwork(c);
245 } else {
246 // Pass as a char to be sent to network
247 logD("send long dtmf for " + c);
248 mCallManager.startDtmf(c);
249
250 // If it is a timed tone, queue up the stop command in DTMF_DURATION_MS.
251 if (timedShortTone) {
252 mHandler.sendMessageDelayed(mHandler.obtainMessage(DTMF_STOP), DTMF_DURATION_MS);
253 }
254 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700255
256 startLocalToneIfNeeded(c);
257 }
258
Christine Chendaf7bf62013-08-05 19:12:31 -0700259 /**
260 * Sends the dtmf character over the network for short DTMF settings
261 * When the characters are entered in quick succession,
262 * the characters are queued before sending over the network.
263 */
264 private void sendShortDtmfToNetwork(char dtmfDigit) {
265 synchronized (mDTMFQueue) {
266 if (mDTMFBurstCnfPending == true) {
267 // Insert the dtmf char to the queue
268 mDTMFQueue.add(new Character(dtmfDigit));
269 } else {
270 String dtmfStr = Character.toString(dtmfDigit);
271 mCallManager.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF));
272 // Set flag to indicate wait for Telephony confirmation.
273 mDTMFBurstCnfPending = true;
274 }
275 }
276 }
277
278 /**
279 * Handles Burst Dtmf Confirmation from the Framework.
280 */
281 void handleBurstDtmfConfirmation() {
282 Character dtmfChar = null;
283 synchronized (mDTMFQueue) {
284 mDTMFBurstCnfPending = false;
285 if (!mDTMFQueue.isEmpty()) {
286 dtmfChar = mDTMFQueue.remove();
287 Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar);
288 }
289 }
290 if (dtmfChar != null) {
291 sendShortDtmfToNetwork(dtmfChar);
292 }
293 }
294
Santos Cordon2eaff902013-08-05 04:37:55 -0700295 public void stopDtmfTone() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700296 if (!mShortTone) {
297 mCallManager.stopDtmf();
298 stopLocalToneIfNeeded();
299 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700300 }
301
302 /**
303 * Plays the local tone based the phone type, optionally forcing a short
304 * tone.
305 */
306 private void startLocalToneIfNeeded(char c) {
307 if (mLocalToneEnabled) {
308 synchronized (mToneGeneratorLock) {
309 if (mToneGenerator == null) {
310 logD("startDtmfTone: mToneGenerator == null, tone: " + c);
311 } else {
312 logD("starting local tone " + c);
313 int toneDuration = -1;
Christine Chendaf7bf62013-08-05 19:12:31 -0700314 if (mShortTone) {
315 toneDuration = DTMF_DURATION_MS;
316 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700317 mToneGenerator.startTone(mToneMap.get(c), toneDuration);
318 }
319 }
320 }
321 }
322
323 /**
324 * Stops the local tone based on the phone type.
325 */
326 public void stopLocalToneIfNeeded() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700327 if (!mShortTone) {
328 // if local tone playback is enabled, stop it.
329 logD("trying to stop local tone...");
330 if (mLocalToneEnabled) {
331 synchronized (mToneGeneratorLock) {
332 if (mToneGenerator == null) {
333 logD("stopLocalTone: mToneGenerator == null");
334 } else {
335 logD("stopping local tone.");
336 mToneGenerator.stopTone();
337 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700338 }
339 }
340 }
341 }
342
343 private boolean okToDialDtmfTones() {
344 boolean hasActiveCall = false;
345 boolean hasIncomingCall = false;
346
347 final List<Call> calls = mCallModeler.getFullList();
348 final int len = calls.size();
349
350 for (int i = 0; i < len; i++) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700351 // We can also dial while in DIALING state because there are
352 // some connections that never update to an ACTIVE state (no
353 // indication from the network).
354 hasActiveCall |= (calls.get(i).getState() == Call.State.ACTIVE)
355 || (calls.get(i).getState() == Call.State.DIALING);
Santos Cordon2eaff902013-08-05 04:37:55 -0700356 hasIncomingCall |= (calls.get(i).getState() == Call.State.INCOMING);
357 }
358
359 return hasActiveCall && !hasIncomingCall;
360 }
361
362 /**
Christine Chendaf7bf62013-08-05 19:12:31 -0700363 * On GSM devices, we never use short tones.
364 * On CDMA devices, it depends upon the settings.
365 */
366 private static boolean useShortDtmfTones(Phone phone, Context context) {
367 int phoneType = phone.getPhoneType();
368 if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
369 return false;
370 } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
371 int toneType = android.provider.Settings.System.getInt(
372 context.getContentResolver(),
373 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
374 Constants.DTMF_TONE_TYPE_NORMAL);
375 if (toneType == Constants.DTMF_TONE_TYPE_NORMAL) {
376 return true;
377 } else {
378 return false;
379 }
380 } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
381 return false;
382 } else {
383 throw new IllegalStateException("Unexpected phone type: " + phoneType);
384 }
385 }
386
387 /**
Santos Cordon2eaff902013-08-05 04:37:55 -0700388 * Checks to see if there are any active calls. If there are, then we want to allocate the tone
389 * resources for playing DTMF tone, otherwise release them.
390 */
391 private void checkCallState() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700392 logD("checkCallState");
Santos Cordon2b73bd62013-08-27 14:53:43 -0700393 if (mCallModeler.hasOutstandingActiveOrDialingCall()) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700394 startDialerSession();
395 } else {
396 stopDialerSession();
397 }
398 }
399
400 /**
401 * static logging method
402 */
403 private static void logD(String msg) {
404 if (DBG) {
405 Log.d(LOG_TAG, msg);
406 }
407 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700408}