Supports DTMF tone in incallui split.

- Adds accessibility code for short tone.
- Also changed CallModeler listener behaviour

Change-Id: Ia052a877bdf301f478d3fcf4fd1d337dd9bc66ff
diff --git a/common/src/com/android/services/telephony/common/ICallCommandService.aidl b/common/src/com/android/services/telephony/common/ICallCommandService.aidl
index a8cfba3..ce532bc 100644
--- a/common/src/com/android/services/telephony/common/ICallCommandService.aidl
+++ b/common/src/com/android/services/telephony/common/ICallCommandService.aidl
@@ -57,7 +57,7 @@
     /**
      * Start playing DTMF tone for the specified digit.
      */
-    void playDtmfTone(char digit);
+    void playDtmfTone(char digit, boolean timedShortTone);
 
     /**
      * Stop playing DTMF tone for the specified digit.
diff --git a/src/com/android/phone/CallCommandService.java b/src/com/android/phone/CallCommandService.java
index 7f85404..560e78c 100644
--- a/src/com/android/phone/CallCommandService.java
+++ b/src/com/android/phone/CallCommandService.java
@@ -127,9 +127,9 @@
     }
 
     @Override
-    public void playDtmfTone(char digit) {
+    public void playDtmfTone(char digit, boolean timedShortTone) {
         try {
-            mDtmfTonePlayer.playDtmfTone(digit);
+            mDtmfTonePlayer.playDtmfTone(digit, timedShortTone);
         } catch (Exception e) {
             Log.e(TAG, "Error playing DTMF tone.", e);
         }
diff --git a/src/com/android/phone/CallHandlerServiceProxy.java b/src/com/android/phone/CallHandlerServiceProxy.java
index 5d6f68d..b134310 100644
--- a/src/com/android/phone/CallHandlerServiceProxy.java
+++ b/src/com/android/phone/CallHandlerServiceProxy.java
@@ -57,7 +57,7 @@
         mCallModeler = callModeler;
 
         setupServiceConnection();
-        mCallModeler.setListener(this);
+        mCallModeler.addListener(this);
 
         // start the whole process
         onUpdate(mCallModeler.getFullList(), true);
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
index 8aed6e5..13005ee 100644
--- a/src/com/android/phone/CallModeler.java
+++ b/src/com/android/phone/CallModeler.java
@@ -75,7 +75,7 @@
     private final CallManager mCallManager;
     private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
     private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
-    private Listener mListener;
+    private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
 
     public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager) {
         mCallStateMonitor = callStateMonitor;
@@ -101,14 +101,10 @@
         }
     }
 
-    public void setListener(Listener listener) {
+    public void addListener(Listener listener) {
         Preconditions.checkNotNull(listener);
-        Preconditions.checkState(mListener == null);
-
-        // only support setting listener once.
-        // We only have one listener anyway and supporting multiple means maintaining state for
-        // each of the listeners so that we can do proper diffs.
-        mListener = listener;
+        Preconditions.checkNotNull(mListeners);
+        mListeners.add(listener);
     }
 
     public List<Call> getFullList() {
@@ -145,8 +141,8 @@
         final Call call = getCallFromConnection(conn, true);
         call.setState(Call.State.INCOMING);
 
-        if (call != null && mListener != null) {
-            mListener.onUpdate(Lists.newArrayList(call), false);
+        for (int i = 0; i < mListeners.size(); ++i) {
+            mListeners.get(i).onUpdate(Lists.newArrayList(call), false);
         }
     }
 
@@ -158,8 +154,8 @@
         if (call != null) {
             mCallMap.remove(conn);
 
-            if (mListener != null) {
-                mListener.onDisconnect(call);
+            for (int i = 0; i < mListeners.size(); ++i) {
+                mListeners.get(i).onDisconnect(call);
             }
         }
     }
@@ -171,8 +167,8 @@
         final List<Call> updatedCalls = Lists.newArrayList();
         doUpdate(false, updatedCalls);
 
-        if (mListener != null) {
-            mListener.onUpdate(updatedCalls, false);
+        for (int i = 0; i < mListeners.size(); ++i) {
+            mListeners.get(i).onUpdate(updatedCalls, false);
         }
     }
 
diff --git a/src/com/android/phone/DTMFTonePlayer.java b/src/com/android/phone/DTMFTonePlayer.java
index 682c74a..39fc4d9 100644
--- a/src/com/android/phone/DTMFTonePlayer.java
+++ b/src/com/android/phone/DTMFTonePlayer.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableMap;
 
+import android.content.Context;
 import android.media.AudioManager;
 import android.media.ToneGenerator;
 import android.os.Handler;
@@ -27,11 +28,14 @@
 
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.services.telephony.common.Call;
 
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 
 /**
  * Playing DTMF tones through the CallManager.
@@ -40,7 +44,8 @@
     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;
+    private static final int DTMF_SEND_CNF = 100;
+    private static final int DTMF_STOP = 101;
 
     /** Hash Map to map a character to a tone*/
     private static final Map<Character, Integer> mToneMap =
@@ -65,18 +70,53 @@
     private ToneGenerator mToneGenerator;
     private boolean mLocalToneEnabled;
 
+    // indicates that we are using automatically shortened DTMF tones
+    boolean mShortTone;
+
+    // indicate if the confirmation from TelephonyFW is pending.
+    private boolean mDTMFBurstCnfPending = false;
+
+    // Queue to queue the short dtmf characters.
+    private Queue<Character> mDTMFQueue = new LinkedList<Character>();
+
+    //  Short Dtmf tone duration
+    private static final int DTMF_DURATION_MS = 120;
+
+    /**
+     * Our own handler to take care of the messages from the phone state changes
+     */
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DTMF_SEND_CNF:
+                    logD("dtmf confirmation received from FW.");
+                    // handle burst dtmf confirmation
+                    handleBurstDtmfConfirmation();
+                    break;
+                case DTMF_STOP:
+                    logD("dtmf stop received");
+                    stopDtmfTone();
+                    break;
+            }
+        }
+    };
+
     public DTMFTonePlayer(CallManager callManager, CallModeler callModeler) {
         mCallManager = callManager;
         mCallModeler = callModeler;
+        mCallModeler.addListener(this);
     }
 
     @Override
     public void onDisconnect(Call call) {
+        logD("Call disconnected");
         checkCallState();
     }
 
     @Override
     public void onUpdate(List<Call> calls, boolean full) {
+        logD("Call updated");
         checkCallState();
     }
 
@@ -137,12 +177,18 @@
                 mToneGenerator = null;
             }
         }
+
+        mHandler.removeMessages(DTMF_SEND_CNF);
+        synchronized (mDTMFQueue) {
+            mDTMFBurstCnfPending = false;
+            mDTMFQueue.clear();
+        }
     }
 
     /**
      * Starts playback of the dtmf tone corresponding to the parameter.
      */
-    public void playDtmfTone(char c) {
+    public void playDtmfTone(char c, boolean timedShortTone) {
         // Only play the tone if it exists.
         if (!mToneMap.containsKey(c)) {
             return;
@@ -152,21 +198,79 @@
             return;
         }
 
+        PhoneGlobals.getInstance().pokeUserActivity();
+
         // Read the settings as it may be changed by the user during the call
         Phone phone = mCallManager.getFgPhone();
 
+        // Before we go ahead and start a tone, we need to make sure that any pending
+        // stop-tone message is processed.
+        if (mHandler.hasMessages(DTMF_STOP)) {
+            mHandler.removeMessages(DTMF_STOP);
+            stopDtmfTone();
+        }
+
+        mShortTone = useShortDtmfTones(phone, phone.getContext());
         logD("startDtmfTone()...");
 
-        // Pass as a char to be sent to network
-        logD("send long dtmf for " + c);
-        mCallManager.startDtmf(c);
+        // For Short DTMF we need to play the local tone for fixed duration
+        if (mShortTone) {
+            sendShortDtmfToNetwork(c);
+        } else {
+            // Pass as a char to be sent to network
+            logD("send long dtmf for " + c);
+            mCallManager.startDtmf(c);
+
+            // If it is a timed tone, queue up the stop command in DTMF_DURATION_MS.
+            if (timedShortTone) {
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(DTMF_STOP), DTMF_DURATION_MS);
+            }
+        }
 
         startLocalToneIfNeeded(c);
     }
 
+    /**
+     * Sends the dtmf character over the network for short DTMF settings
+     * When the characters are entered in quick succession,
+     * the characters are queued before sending over the network.
+     */
+    private void sendShortDtmfToNetwork(char dtmfDigit) {
+        synchronized (mDTMFQueue) {
+            if (mDTMFBurstCnfPending == true) {
+                // Insert the dtmf char to the queue
+                mDTMFQueue.add(new Character(dtmfDigit));
+            } else {
+                String dtmfStr = Character.toString(dtmfDigit);
+                mCallManager.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF));
+                // Set flag to indicate wait for Telephony confirmation.
+                mDTMFBurstCnfPending = true;
+            }
+        }
+    }
+
+    /**
+     * Handles Burst Dtmf Confirmation from the Framework.
+     */
+    void handleBurstDtmfConfirmation() {
+        Character dtmfChar = null;
+        synchronized (mDTMFQueue) {
+            mDTMFBurstCnfPending = false;
+            if (!mDTMFQueue.isEmpty()) {
+                dtmfChar = mDTMFQueue.remove();
+                Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar);
+            }
+        }
+        if (dtmfChar != null) {
+            sendShortDtmfToNetwork(dtmfChar);
+        }
+    }
+
     public void stopDtmfTone() {
-        mCallManager.stopDtmf();
-        stopLocalToneIfNeeded();
+        if (!mShortTone) {
+            mCallManager.stopDtmf();
+            stopLocalToneIfNeeded();
+        }
     }
 
     /**
@@ -181,6 +285,9 @@
                 } else {
                     logD("starting local tone " + c);
                     int toneDuration = -1;
+                    if (mShortTone) {
+                        toneDuration = DTMF_DURATION_MS;
+                    }
                     mToneGenerator.startTone(mToneMap.get(c), toneDuration);
                 }
             }
@@ -191,15 +298,17 @@
      * 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();
+        if (!mShortTone) {
+            // 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();
+                    }
                 }
             }
         }
@@ -221,10 +330,36 @@
     }
 
     /**
+     * On GSM devices, we never use short tones.
+     * On CDMA devices, it depends upon the settings.
+     */
+    private static boolean useShortDtmfTones(Phone phone, Context context) {
+        int phoneType = phone.getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+            return false;
+        } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            int toneType = android.provider.Settings.System.getInt(
+                    context.getContentResolver(),
+                    Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+                    Constants.DTMF_TONE_TYPE_NORMAL);
+            if (toneType == Constants.DTMF_TONE_TYPE_NORMAL) {
+                return true;
+            } else {
+                return false;
+            }
+        } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
+            return false;
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+    }
+
+    /**
      * 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() {
+        logD("checkCallState");
         if (mCallModeler.hasOutstandingActiveCall()) {
             startDialerSession();
         } else {