blob: 0e27a99efa7d84aebd9f3f336e5559506baafdf5 [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
Christine Chenee09a492013-08-06 16:02:29 -070034import java.util.ArrayList;
Santos Cordon2eaff902013-08-05 04:37:55 -070035import java.util.HashMap;
Christine Chendaf7bf62013-08-05 19:12:31 -070036import java.util.LinkedList;
Santos Cordon2eaff902013-08-05 04:37:55 -070037import java.util.List;
38import java.util.Map;
Christine Chendaf7bf62013-08-05 19:12:31 -070039import java.util.Queue;
Santos Cordon2eaff902013-08-05 04:37:55 -070040
41/**
42 * Playing DTMF tones through the CallManager.
43 */
44public class DTMFTonePlayer implements CallModeler.Listener {
45 private static final String LOG_TAG = DTMFTonePlayer.class.getSimpleName();
46 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
47
Christine Chendaf7bf62013-08-05 19:12:31 -070048 private static final int DTMF_SEND_CNF = 100;
49 private static final int DTMF_STOP = 101;
Santos Cordon2eaff902013-08-05 04:37:55 -070050
51 /** Hash Map to map a character to a tone*/
52 private static final Map<Character, Integer> mToneMap =
53 ImmutableMap.<Character, Integer>builder()
54 .put('1', ToneGenerator.TONE_DTMF_1)
55 .put('2', ToneGenerator.TONE_DTMF_2)
56 .put('3', ToneGenerator.TONE_DTMF_3)
57 .put('4', ToneGenerator.TONE_DTMF_4)
58 .put('5', ToneGenerator.TONE_DTMF_5)
59 .put('6', ToneGenerator.TONE_DTMF_6)
60 .put('7', ToneGenerator.TONE_DTMF_7)
61 .put('8', ToneGenerator.TONE_DTMF_8)
62 .put('9', ToneGenerator.TONE_DTMF_9)
63 .put('0', ToneGenerator.TONE_DTMF_0)
64 .put('#', ToneGenerator.TONE_DTMF_P)
65 .put('*', ToneGenerator.TONE_DTMF_S)
66 .build();
67
68 private final CallManager mCallManager;
69 private final CallModeler mCallModeler;
70 private final Object mToneGeneratorLock = new Object();
71 private ToneGenerator mToneGenerator;
72 private boolean mLocalToneEnabled;
73
Christine Chendaf7bf62013-08-05 19:12:31 -070074 // indicates that we are using automatically shortened DTMF tones
75 boolean mShortTone;
76
77 // indicate if the confirmation from TelephonyFW is pending.
78 private boolean mDTMFBurstCnfPending = false;
79
80 // Queue to queue the short dtmf characters.
81 private Queue<Character> mDTMFQueue = new LinkedList<Character>();
82
83 // Short Dtmf tone duration
84 private static final int DTMF_DURATION_MS = 120;
85
86 /**
87 * Our own handler to take care of the messages from the phone state changes
88 */
89 private final Handler mHandler = new Handler() {
90 @Override
91 public void handleMessage(Message msg) {
92 switch (msg.what) {
93 case DTMF_SEND_CNF:
94 logD("dtmf confirmation received from FW.");
95 // handle burst dtmf confirmation
96 handleBurstDtmfConfirmation();
97 break;
98 case DTMF_STOP:
99 logD("dtmf stop received");
100 stopDtmfTone();
101 break;
102 }
103 }
104 };
105
Santos Cordon2eaff902013-08-05 04:37:55 -0700106 public DTMFTonePlayer(CallManager callManager, CallModeler callModeler) {
107 mCallManager = callManager;
108 mCallModeler = callModeler;
Christine Chendaf7bf62013-08-05 19:12:31 -0700109 mCallModeler.addListener(this);
Santos Cordon2eaff902013-08-05 04:37:55 -0700110 }
111
112 @Override
113 public void onDisconnect(Call call) {
Christine Chendaf7bf62013-08-05 19:12:31 -0700114 logD("Call disconnected");
Santos Cordon2eaff902013-08-05 04:37:55 -0700115 checkCallState();
116 }
117
118 @Override
Christine Chenee09a492013-08-06 16:02:29 -0700119 public void onIncoming(Call call, ArrayList<String> textResponses) {
120 }
121
122 @Override
Santos Cordon2eaff902013-08-05 04:37:55 -0700123 public void onUpdate(List<Call> calls, boolean full) {
Christine Chendaf7bf62013-08-05 19:12:31 -0700124 logD("Call updated");
Santos Cordon2eaff902013-08-05 04:37:55 -0700125 checkCallState();
126 }
127
128 /**
129 * Allocates some resources we keep around during a "dialer session".
130 *
131 * (Currently, a "dialer session" just means any situation where we
132 * might need to play local DTMF tones, which means that we need to
133 * keep a ToneGenerator instance around. A ToneGenerator instance
134 * keeps an AudioTrack resource busy in AudioFlinger, so we don't want
135 * to keep it around forever.)
136 *
137 * Call {@link stopDialerSession} to release the dialer session
138 * resources.
139 */
140 public void startDialerSession() {
141 logD("startDialerSession()... this = " + this);
142
143 // see if we need to play local tones.
144 if (PhoneGlobals.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
145 mLocalToneEnabled = Settings.System.getInt(
146 PhoneGlobals.getInstance().getContentResolver(),
147 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
148 } else {
149 mLocalToneEnabled = false;
150 }
151 logD("- startDialerSession: mLocalToneEnabled = " + mLocalToneEnabled);
152
153 // create the tone generator
154 // if the mToneGenerator creation fails, just continue without it. It is
155 // a local audio signal, and is not as important as the dtmf tone itself.
156 if (mLocalToneEnabled) {
157 synchronized (mToneGeneratorLock) {
158 if (mToneGenerator == null) {
159 try {
160 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
161 } catch (RuntimeException e) {
162 Log.e(LOG_TAG, "Exception caught while creating local tone generator", e);
163 mToneGenerator = null;
164 }
165 }
166 }
167 }
168 }
169
170 /**
171 * Releases resources we keep around during a "dialer session"
172 * (see {@link startDialerSession}).
173 *
174 * It's safe to call this even without a corresponding
175 * startDialerSession call.
176 */
177 public void stopDialerSession() {
178 // release the tone generator.
179 synchronized (mToneGeneratorLock) {
180 if (mToneGenerator != null) {
181 mToneGenerator.release();
182 mToneGenerator = null;
183 }
184 }
Christine Chendaf7bf62013-08-05 19:12:31 -0700185
186 mHandler.removeMessages(DTMF_SEND_CNF);
187 synchronized (mDTMFQueue) {
188 mDTMFBurstCnfPending = false;
189 mDTMFQueue.clear();
190 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700191 }
192
193 /**
194 * Starts playback of the dtmf tone corresponding to the parameter.
195 */
Christine Chendaf7bf62013-08-05 19:12:31 -0700196 public void playDtmfTone(char c, boolean timedShortTone) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700197 // Only play the tone if it exists.
198 if (!mToneMap.containsKey(c)) {
199 return;
200 }
201
202 if (!okToDialDtmfTones()) {
203 return;
204 }
205
Christine Chendaf7bf62013-08-05 19:12:31 -0700206 PhoneGlobals.getInstance().pokeUserActivity();
207
Santos Cordon2eaff902013-08-05 04:37:55 -0700208 // Read the settings as it may be changed by the user during the call
209 Phone phone = mCallManager.getFgPhone();
210
Christine Chendaf7bf62013-08-05 19:12:31 -0700211 // Before we go ahead and start a tone, we need to make sure that any pending
212 // stop-tone message is processed.
213 if (mHandler.hasMessages(DTMF_STOP)) {
214 mHandler.removeMessages(DTMF_STOP);
215 stopDtmfTone();
216 }
217
218 mShortTone = useShortDtmfTones(phone, phone.getContext());
Santos Cordon2eaff902013-08-05 04:37:55 -0700219 logD("startDtmfTone()...");
220
Christine Chendaf7bf62013-08-05 19:12:31 -0700221 // For Short DTMF we need to play the local tone for fixed duration
222 if (mShortTone) {
223 sendShortDtmfToNetwork(c);
224 } else {
225 // Pass as a char to be sent to network
226 logD("send long dtmf for " + c);
227 mCallManager.startDtmf(c);
228
229 // If it is a timed tone, queue up the stop command in DTMF_DURATION_MS.
230 if (timedShortTone) {
231 mHandler.sendMessageDelayed(mHandler.obtainMessage(DTMF_STOP), DTMF_DURATION_MS);
232 }
233 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700234
235 startLocalToneIfNeeded(c);
236 }
237
Christine Chendaf7bf62013-08-05 19:12:31 -0700238 /**
239 * Sends the dtmf character over the network for short DTMF settings
240 * When the characters are entered in quick succession,
241 * the characters are queued before sending over the network.
242 */
243 private void sendShortDtmfToNetwork(char dtmfDigit) {
244 synchronized (mDTMFQueue) {
245 if (mDTMFBurstCnfPending == true) {
246 // Insert the dtmf char to the queue
247 mDTMFQueue.add(new Character(dtmfDigit));
248 } else {
249 String dtmfStr = Character.toString(dtmfDigit);
250 mCallManager.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF));
251 // Set flag to indicate wait for Telephony confirmation.
252 mDTMFBurstCnfPending = true;
253 }
254 }
255 }
256
257 /**
258 * Handles Burst Dtmf Confirmation from the Framework.
259 */
260 void handleBurstDtmfConfirmation() {
261 Character dtmfChar = null;
262 synchronized (mDTMFQueue) {
263 mDTMFBurstCnfPending = false;
264 if (!mDTMFQueue.isEmpty()) {
265 dtmfChar = mDTMFQueue.remove();
266 Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar);
267 }
268 }
269 if (dtmfChar != null) {
270 sendShortDtmfToNetwork(dtmfChar);
271 }
272 }
273
Santos Cordon2eaff902013-08-05 04:37:55 -0700274 public void stopDtmfTone() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700275 if (!mShortTone) {
276 mCallManager.stopDtmf();
277 stopLocalToneIfNeeded();
278 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700279 }
280
281 /**
282 * Plays the local tone based the phone type, optionally forcing a short
283 * tone.
284 */
285 private void startLocalToneIfNeeded(char c) {
286 if (mLocalToneEnabled) {
287 synchronized (mToneGeneratorLock) {
288 if (mToneGenerator == null) {
289 logD("startDtmfTone: mToneGenerator == null, tone: " + c);
290 } else {
291 logD("starting local tone " + c);
292 int toneDuration = -1;
Christine Chendaf7bf62013-08-05 19:12:31 -0700293 if (mShortTone) {
294 toneDuration = DTMF_DURATION_MS;
295 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700296 mToneGenerator.startTone(mToneMap.get(c), toneDuration);
297 }
298 }
299 }
300 }
301
302 /**
303 * Stops the local tone based on the phone type.
304 */
305 public void stopLocalToneIfNeeded() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700306 if (!mShortTone) {
307 // if local tone playback is enabled, stop it.
308 logD("trying to stop local tone...");
309 if (mLocalToneEnabled) {
310 synchronized (mToneGeneratorLock) {
311 if (mToneGenerator == null) {
312 logD("stopLocalTone: mToneGenerator == null");
313 } else {
314 logD("stopping local tone.");
315 mToneGenerator.stopTone();
316 }
Santos Cordon2eaff902013-08-05 04:37:55 -0700317 }
318 }
319 }
320 }
321
322 private boolean okToDialDtmfTones() {
323 boolean hasActiveCall = false;
324 boolean hasIncomingCall = false;
325
326 final List<Call> calls = mCallModeler.getFullList();
327 final int len = calls.size();
328
329 for (int i = 0; i < len; i++) {
330 hasActiveCall |= (calls.get(i).getState() == Call.State.ACTIVE);
331 hasIncomingCall |= (calls.get(i).getState() == Call.State.INCOMING);
332 }
333
334 return hasActiveCall && !hasIncomingCall;
335 }
336
337 /**
Christine Chendaf7bf62013-08-05 19:12:31 -0700338 * On GSM devices, we never use short tones.
339 * On CDMA devices, it depends upon the settings.
340 */
341 private static boolean useShortDtmfTones(Phone phone, Context context) {
342 int phoneType = phone.getPhoneType();
343 if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
344 return false;
345 } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
346 int toneType = android.provider.Settings.System.getInt(
347 context.getContentResolver(),
348 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
349 Constants.DTMF_TONE_TYPE_NORMAL);
350 if (toneType == Constants.DTMF_TONE_TYPE_NORMAL) {
351 return true;
352 } else {
353 return false;
354 }
355 } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
356 return false;
357 } else {
358 throw new IllegalStateException("Unexpected phone type: " + phoneType);
359 }
360 }
361
362 /**
Santos Cordon2eaff902013-08-05 04:37:55 -0700363 * Checks to see if there are any active calls. If there are, then we want to allocate the tone
364 * resources for playing DTMF tone, otherwise release them.
365 */
366 private void checkCallState() {
Christine Chendaf7bf62013-08-05 19:12:31 -0700367 logD("checkCallState");
Santos Cordon2eaff902013-08-05 04:37:55 -0700368 if (mCallModeler.hasOutstandingActiveCall()) {
369 startDialerSession();
370 } else {
371 stopDialerSession();
372 }
373 }
374
375 /**
376 * static logging method
377 */
378 private static void logD(String msg) {
379 if (DBG) {
380 Log.d(LOG_TAG, msg);
381 }
382 }
383
384}