Add DTMF dial tone commands to CallCommandsService.

Moved over much of the code from DTMFTwelveKeyDialer except for UI code
and short-Tone.  When we copy over short tone, we should be able to
delete DTMFTwelveKeyDialer.

Change-Id: Ibb954cdeb30bec99d717569f9254892661533699
diff --git a/src/com/android/phone/CallCommandService.java b/src/com/android/phone/CallCommandService.java
index f216a48..7f85404 100644
--- a/src/com/android/phone/CallCommandService.java
+++ b/src/com/android/phone/CallCommandService.java
@@ -35,11 +35,14 @@
     private final Context mContext;
     private final CallManager mCallManager;
     private final CallModeler mCallModeler;
+    private final DTMFTonePlayer mDtmfTonePlayer;
 
-    public CallCommandService(Context context, CallManager callManager, CallModeler callModeler) {
+    public CallCommandService(Context context, CallManager callManager, CallModeler callModeler,
+            DTMFTonePlayer dtmfTonePlayer) {
         mContext = context;
         mCallManager = callManager;
         mCallModeler = callModeler;
+        mDtmfTonePlayer = dtmfTonePlayer;
     }
 
     /**
@@ -93,7 +96,7 @@
             CallResult result = mCallModeler.getCallWithId(callId);
             if (result != null) {
                 int state = result.getCall().getState();
-                if (hold && Call.State.ACTIVE == state ) {
+                if (hold && Call.State.ACTIVE == state) {
                     PhoneUtils.switchHoldingAndActive(mCallManager.getFirstActiveBgCall());
                 } else if (!hold && Call.State.ONHOLD == state) {
                     PhoneUtils.switchHoldingAndActive(result.getConnection().getCall());
@@ -122,4 +125,22 @@
             Log.e(TAG, "Error during speaker().", e);
         }
     }
+
+    @Override
+    public void playDtmfTone(char digit) {
+        try {
+            mDtmfTonePlayer.playDtmfTone(digit);
+        } catch (Exception e) {
+            Log.e(TAG, "Error playing DTMF tone.", e);
+        }
+    }
+
+    @Override
+    public void stopDtmfTone() {
+        try {
+            mDtmfTonePlayer.stopDtmfTone();
+        } catch (Exception e) {
+            Log.e(TAG, "Error stopping DTMF tone.", e);
+        }
+    }
 }
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
index 01f571f..8aed6e5 100644
--- a/src/com/android/phone/CallModeler.java
+++ b/src/com/android/phone/CallModeler.java
@@ -127,6 +127,19 @@
         return null;
     }
 
+    public boolean hasOutstandingActiveCall() {
+        for (Call call : mCallMap.values()) {
+            int state = call.getState();
+            if (Call.State.INVALID != state &&
+                    Call.State.IDLE != state &&
+                    Call.State.INCOMING != state) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private void onNewRingingConnection(AsyncResult r) {
         final Connection conn = (Connection) r.result;
         final Call call = getCallFromConnection(conn, true);
diff --git a/src/com/android/phone/DTMFTonePlayer.java b/src/com/android/phone/DTMFTonePlayer.java
new file mode 100644
index 0000000..682c74a
--- /dev/null
+++ b/src/com/android/phone/DTMFTonePlayer.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.google.common.collect.ImmutableMap;
+
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.services.telephony.common.Call;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Playing DTMF tones through the CallManager.
+ */
+public class DTMFTonePlayer implements CallModeler.Listener {
+    private static final String LOG_TAG = DTMFTonePlayer.class.getSimpleName();
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private static final int DTMF_STOP = 100;
+
+    /** Hash Map to map a character to a tone*/
+    private static final Map<Character, Integer> mToneMap =
+            ImmutableMap.<Character, Integer>builder()
+                    .put('1', ToneGenerator.TONE_DTMF_1)
+                    .put('2', ToneGenerator.TONE_DTMF_2)
+                    .put('3', ToneGenerator.TONE_DTMF_3)
+                    .put('4', ToneGenerator.TONE_DTMF_4)
+                    .put('5', ToneGenerator.TONE_DTMF_5)
+                    .put('6', ToneGenerator.TONE_DTMF_6)
+                    .put('7', ToneGenerator.TONE_DTMF_7)
+                    .put('8', ToneGenerator.TONE_DTMF_8)
+                    .put('9', ToneGenerator.TONE_DTMF_9)
+                    .put('0', ToneGenerator.TONE_DTMF_0)
+                    .put('#', ToneGenerator.TONE_DTMF_P)
+                    .put('*', ToneGenerator.TONE_DTMF_S)
+                    .build();
+
+    private final CallManager mCallManager;
+    private final CallModeler mCallModeler;
+    private final Object mToneGeneratorLock = new Object();
+    private ToneGenerator mToneGenerator;
+    private boolean mLocalToneEnabled;
+
+    public DTMFTonePlayer(CallManager callManager, CallModeler callModeler) {
+        mCallManager = callManager;
+        mCallModeler = callModeler;
+    }
+
+    @Override
+    public void onDisconnect(Call call) {
+        checkCallState();
+    }
+
+    @Override
+    public void onUpdate(List<Call> calls, boolean full) {
+        checkCallState();
+    }
+
+    /**
+     * Allocates some resources we keep around during a "dialer session".
+     *
+     * (Currently, a "dialer session" just means any situation where we
+     * might need to play local DTMF tones, which means that we need to
+     * keep a ToneGenerator instance around.  A ToneGenerator instance
+     * keeps an AudioTrack resource busy in AudioFlinger, so we don't want
+     * to keep it around forever.)
+     *
+     * Call {@link stopDialerSession} to release the dialer session
+     * resources.
+     */
+    public void startDialerSession() {
+        logD("startDialerSession()... this = " + this);
+
+        // see if we need to play local tones.
+        if (PhoneGlobals.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
+            mLocalToneEnabled = Settings.System.getInt(
+                    PhoneGlobals.getInstance().getContentResolver(),
+                    Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
+        } else {
+            mLocalToneEnabled = false;
+        }
+        logD("- startDialerSession: mLocalToneEnabled = " + mLocalToneEnabled);
+
+        // create the tone generator
+        // if the mToneGenerator creation fails, just continue without it.  It is
+        // a local audio signal, and is not as important as the dtmf tone itself.
+        if (mLocalToneEnabled) {
+            synchronized (mToneGeneratorLock) {
+                if (mToneGenerator == null) {
+                    try {
+                        mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
+                    } catch (RuntimeException e) {
+                        Log.e(LOG_TAG, "Exception caught while creating local tone generator", e);
+                        mToneGenerator = null;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Releases resources we keep around during a "dialer session"
+     * (see {@link startDialerSession}).
+     *
+     * It's safe to call this even without a corresponding
+     * startDialerSession call.
+     */
+    public void stopDialerSession() {
+        // release the tone generator.
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator != null) {
+                mToneGenerator.release();
+                mToneGenerator = null;
+            }
+        }
+    }
+
+    /**
+     * Starts playback of the dtmf tone corresponding to the parameter.
+     */
+    public void playDtmfTone(char c) {
+        // Only play the tone if it exists.
+        if (!mToneMap.containsKey(c)) {
+            return;
+        }
+
+        if (!okToDialDtmfTones()) {
+            return;
+        }
+
+        // Read the settings as it may be changed by the user during the call
+        Phone phone = mCallManager.getFgPhone();
+
+        logD("startDtmfTone()...");
+
+        // Pass as a char to be sent to network
+        logD("send long dtmf for " + c);
+        mCallManager.startDtmf(c);
+
+        startLocalToneIfNeeded(c);
+    }
+
+    public void stopDtmfTone() {
+        mCallManager.stopDtmf();
+        stopLocalToneIfNeeded();
+    }
+
+    /**
+     * Plays the local tone based the phone type, optionally forcing a short
+     * tone.
+     */
+    private void startLocalToneIfNeeded(char c) {
+        if (mLocalToneEnabled) {
+            synchronized (mToneGeneratorLock) {
+                if (mToneGenerator == null) {
+                    logD("startDtmfTone: mToneGenerator == null, tone: " + c);
+                } else {
+                    logD("starting local tone " + c);
+                    int toneDuration = -1;
+                    mToneGenerator.startTone(mToneMap.get(c), toneDuration);
+                }
+            }
+        }
+    }
+
+    /**
+     * Stops the local tone based on the phone type.
+     */
+    public void stopLocalToneIfNeeded() {
+        // if local tone playback is enabled, stop it.
+        logD("trying to stop local tone...");
+        if (mLocalToneEnabled) {
+            synchronized (mToneGeneratorLock) {
+                if (mToneGenerator == null) {
+                    logD("stopLocalTone: mToneGenerator == null");
+                } else {
+                    logD("stopping local tone.");
+                    mToneGenerator.stopTone();
+                }
+            }
+        }
+    }
+
+    private boolean okToDialDtmfTones() {
+        boolean hasActiveCall = false;
+        boolean hasIncomingCall = false;
+
+        final List<Call> calls = mCallModeler.getFullList();
+        final int len = calls.size();
+
+        for (int i = 0; i < len; i++) {
+            hasActiveCall |= (calls.get(i).getState() == Call.State.ACTIVE);
+            hasIncomingCall |= (calls.get(i).getState() == Call.State.INCOMING);
+        }
+
+        return hasActiveCall && !hasIncomingCall;
+    }
+
+    /**
+     * Checks to see if there are any active calls. If there are, then we want to allocate the tone
+     * resources for playing DTMF tone, otherwise release them.
+     */
+    private void checkCallState() {
+        if (mCallModeler.hasOutstandingActiveCall()) {
+            startDialerSession();
+        } else {
+            stopDialerSession();
+        }
+    }
+
+    /**
+     * static logging method
+     */
+    private static void logD(String msg) {
+        if (DBG) {
+            Log.d(LOG_TAG, msg);
+        }
+    }
+
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 650e2eb..b3a01e2 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -174,6 +174,7 @@
     private CallHandlerServiceProxy callHandlerServiceProxy;
     private CallModeler callModeler;
     private CallStateMonitor callStateMonitor;
+    private DTMFTonePlayer dtmfTonePlayer;
     private IBluetoothHeadsetPhone mBluetoothPhone;
     private Ringer ringer;
 
@@ -545,8 +546,11 @@
             // Creates call models for use with CallHandlerService.
             callModeler = new CallModeler(callStateMonitor, mCM);
 
+            // Plays DTMF Tones
+            dtmfTonePlayer = new DTMFTonePlayer(mCM, callModeler);
+
             // Service used by in-call UI to control calls
-            callCommandService = new CallCommandService(this, mCM, callModeler);
+            callCommandService = new CallCommandService(this, mCM, callModeler, dtmfTonePlayer);
 
             // Sends call state to the UI
             callHandlerServiceProxy = new CallHandlerServiceProxy(this, callModeler,