blob: 39fc4d9e0c9fd44619324e89fadcd6eec9d3f5ee [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;
30import com.android.internal.telephony.Phone;
Christine Chendaf7bf62013-08-05 19:12:31 -070031import com.android.internal.telephony.PhoneConstants;
Santos Cordon2eaff902013-08-05 04:37:55 -070032import com.android.services.telephony.common.Call;
33
34import java.util.HashMap;
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
118 public void onUpdate(List<Call> calls, boolean full) {
Christine Chendaf7bf62013-08-05 19:12:31 -0700119 logD("Call updated");
Santos Cordon2eaff902013-08-05 04:37:55 -0700120 checkCallState();
121 }
122
123 /**
124 * Allocates some resources we keep around during a "dialer session".
125 *
126 * (Currently, a "dialer session" just means any situation where we
127 * might need to play local DTMF tones, which means that we need to
128 * keep a ToneGenerator instance around. A ToneGenerator instance
129 * keeps an AudioTrack resource busy in AudioFlinger, so we don't want
130 * to keep it around forever.)
131 *
132 * Call {@link stopDialerSession} to release the dialer session
133 * resources.
134 */
135 public void startDialerSession() {
136 logD("startDialerSession()... this = " + this);
137
138 // see if we need to play local tones.
139 if (PhoneGlobals.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
140 mLocalToneEnabled = Settings.System.getInt(
141 PhoneGlobals.getInstance().getContentResolver(),
142 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
143 } else {
144 mLocalToneEnabled = false;
145 }
146 logD("- startDialerSession: mLocalToneEnabled = " + mLocalToneEnabled);
147
148 // create the tone generator
149 // if the mToneGenerator creation fails, just continue without it. It is
150 // a local audio signal, and is not as important as the dtmf tone itself.
151 if (mLocalToneEnabled) {
152 synchronized (mToneGeneratorLock) {
153 if (mToneGenerator == null) {
154 try {
155 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
156 } catch (RuntimeException e) {
157 Log.e(LOG_TAG, "Exception caught while creating local tone generator", e);
158 mToneGenerator = null;
159 }
160 }
161 }
162 }
163 }
164
165 /**
166 * Releases resources we keep around during a "dialer session"
167 * (see {@link startDialerSession}).
168 *
169 * It's safe to call this even without a corresponding
170 * startDialerSession call.
171 */
172 public void stopDialerSession() {
173 // release the tone generator.
174 synchronized (mToneGeneratorLock) {
175 if (mToneGenerator != null) {
176 mToneGenerator.release();
177 mToneGenerator = null;
178 }
179 }
Christine Chendaf7bf62013-08-05 19:12:31 -0700180
181 mHandler.removeMessages(DTMF_SEND_CNF);
182 synchronized (mDTMFQueue) {
183 mDTMFBurstCnfPending = false;
184 mDTMFQueue.clear();
185 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700186 }
187
188 /**
189 * Starts playback of the dtmf tone corresponding to the parameter.
190 */
Christine Chendaf7bf62013-08-05 19:12:31 -0700191 public void playDtmfTone(char c, boolean timedShortTone) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700192 // Only play the tone if it exists.
193 if (!mToneMap.containsKey(c)) {
194 return;
195 }
196
197 if (!okToDialDtmfTones()) {
198 return;
199 }
200
Christine Chendaf7bf62013-08-05 19:12:31 -0700201 PhoneGlobals.getInstance().pokeUserActivity();
202
Santos Cordon2eaff902013-08-05 04:37:55 -0700203 // Read the settings as it may be changed by the user during the call
204 Phone phone = mCallManager.getFgPhone();
205
Christine Chendaf7bf62013-08-05 19:12:31 -0700206 // Before we go ahead and start a tone, we need to make sure that any pending
207 // stop-tone message is processed.
208 if (mHandler.hasMessages(DTMF_STOP)) {
209 mHandler.removeMessages(DTMF_STOP);
210 stopDtmfTone();
211 }
212
213 mShortTone = useShortDtmfTones(phone, phone.getContext());
Santos Cordon2eaff902013-08-05 04:37:55 -0700214 logD("startDtmfTone()...");
215
Christine Chendaf7bf62013-08-05 19:12:31 -0700216 // For Short DTMF we need to play the local tone for fixed duration
217 if (mShortTone) {
218 sendShortDtmfToNetwork(c);
219 } else {
220 // Pass as a char to be sent to network
221 logD("send long dtmf for " + c);
222 mCallManager.startDtmf(c);
223
224 // If it is a timed tone, queue up the stop command in DTMF_DURATION_MS.
225 if (timedShortTone) {
226 mHandler.sendMessageDelayed(mHandler.obtainMessage(DTMF_STOP), DTMF_DURATION_MS);
227 }
228 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700229
230 startLocalToneIfNeeded(c);
231 }
232
Christine Chendaf7bf62013-08-05 19:12:31 -0700233 /**
234 * Sends the dtmf character over the network for short DTMF settings
235 * When the characters are entered in quick succession,
236 * the characters are queued before sending over the network.
237 */
238 private void sendShortDtmfToNetwork(char dtmfDigit) {
239 synchronized (mDTMFQueue) {
240 if (mDTMFBurstCnfPending == true) {
241 // Insert the dtmf char to the queue
242 mDTMFQueue.add(new Character(dtmfDigit));
243 } else {
244 String dtmfStr = Character.toString(dtmfDigit);
245 mCallManager.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF));
246 // Set flag to indicate wait for Telephony confirmation.
247 mDTMFBurstCnfPending = true;
248 }
249 }
250 }
251
252 /**
253 * Handles Burst Dtmf Confirmation from the Framework.
254 */
255 void handleBurstDtmfConfirmation() {
256 Character dtmfChar = null;
257 synchronized (mDTMFQueue) {
258 mDTMFBurstCnfPending = false;
259 if (!mDTMFQueue.isEmpty()) {
260 dtmfChar = mDTMFQueue.remove();
261 Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar);
262 }
263 }
264 if (dtmfChar != null) {
265 sendShortDtmfToNetwork(dtmfChar);
266 }
267 }
268
Santos Cordon2eaff902013-08-05 04:37:55 -0700269 public void stopDtmfTone() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700270 if (!mShortTone) {
271 mCallManager.stopDtmf();
272 stopLocalToneIfNeeded();
273 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700274 }
275
276 /**
277 * Plays the local tone based the phone type, optionally forcing a short
278 * tone.
279 */
280 private void startLocalToneIfNeeded(char c) {
281 if (mLocalToneEnabled) {
282 synchronized (mToneGeneratorLock) {
283 if (mToneGenerator == null) {
284 logD("startDtmfTone: mToneGenerator == null, tone: " + c);
285 } else {
286 logD("starting local tone " + c);
287 int toneDuration = -1;
Christine Chendaf7bf62013-08-05 19:12:31 -0700288 if (mShortTone) {
289 toneDuration = DTMF_DURATION_MS;
290 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700291 mToneGenerator.startTone(mToneMap.get(c), toneDuration);
292 }
293 }
294 }
295 }
296
297 /**
298 * Stops the local tone based on the phone type.
299 */
300 public void stopLocalToneIfNeeded() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700301 if (!mShortTone) {
302 // if local tone playback is enabled, stop it.
303 logD("trying to stop local tone...");
304 if (mLocalToneEnabled) {
305 synchronized (mToneGeneratorLock) {
306 if (mToneGenerator == null) {
307 logD("stopLocalTone: mToneGenerator == null");
308 } else {
309 logD("stopping local tone.");
310 mToneGenerator.stopTone();
311 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700312 }
313 }
314 }
315 }
316
317 private boolean okToDialDtmfTones() {
318 boolean hasActiveCall = false;
319 boolean hasIncomingCall = false;
320
321 final List<Call> calls = mCallModeler.getFullList();
322 final int len = calls.size();
323
324 for (int i = 0; i < len; i++) {
325 hasActiveCall |= (calls.get(i).getState() == Call.State.ACTIVE);
326 hasIncomingCall |= (calls.get(i).getState() == Call.State.INCOMING);
327 }
328
329 return hasActiveCall && !hasIncomingCall;
330 }
331
332 /**
Christine Chendaf7bf62013-08-05 19:12:31 -0700333 * On GSM devices, we never use short tones.
334 * On CDMA devices, it depends upon the settings.
335 */
336 private static boolean useShortDtmfTones(Phone phone, Context context) {
337 int phoneType = phone.getPhoneType();
338 if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
339 return false;
340 } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
341 int toneType = android.provider.Settings.System.getInt(
342 context.getContentResolver(),
343 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
344 Constants.DTMF_TONE_TYPE_NORMAL);
345 if (toneType == Constants.DTMF_TONE_TYPE_NORMAL) {
346 return true;
347 } else {
348 return false;
349 }
350 } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
351 return false;
352 } else {
353 throw new IllegalStateException("Unexpected phone type: " + phoneType);
354 }
355 }
356
357 /**
Santos Cordon2eaff902013-08-05 04:37:55 -0700358 * Checks to see if there are any active calls. If there are, then we want to allocate the tone
359 * resources for playing DTMF tone, otherwise release them.
360 */
361 private void checkCallState() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700362 logD("checkCallState");
Santos Cordon2eaff902013-08-05 04:37:55 -0700363 if (mCallModeler.hasOutstandingActiveCall()) {
364 startDialerSession();
365 } else {
366 stopDialerSession();
367 }
368 }
369
370 /**
371 * static logging method
372 */
373 private static void logD(String msg) {
374 if (DBG) {
375 Log.d(LOG_TAG, msg);
376 }
377 }
378
379}