Fix mute/unmute operation with CallManager.

It is for bug 2968263.

The Mute concept is call based. PhoneUitls maintains a connectionMuteTable to track

the mute state of each connection. For every preciseCallSateChanged event, the table

gets updated for all foreground and background connections. For every UI mute/unmute

operation the table gets updated for affected foreground connections.

Meanwhile, clean up setMute() and getMute() APIs in PhoneUtils since all in-call operation

can only mute/unmute foreground call, which means works on foreground phone.

Also enforce all in-call UI use setMute() instead of setMuteInternal().

Change-Id: Ib608e040230d46b57434a0ac2597a6fb676ad639
diff --git a/src/com/android/phone/BluetoothHandsfree.java b/src/com/android/phone/BluetoothHandsfree.java
index b76580e..a01e4b4 100644
--- a/src/com/android/phone/BluetoothHandsfree.java
+++ b/src/com/android/phone/BluetoothHandsfree.java
@@ -1220,7 +1220,7 @@
         Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];  // indexed by CLCC index
         LinkedList<Connection> newConnections = new LinkedList<Connection>();
         LinkedList<Connection> connections = new LinkedList<Connection>();
-        
+
         Call foregroundCall = mCM.getActiveFgCall();
         Call backgroundCall = mCM.getFirstActiveBgCall();
         Call ringingCall = mCM.getFirstActiveRingingCall();
@@ -1831,7 +1831,7 @@
                                 // put the first call on hold.
                                 if (VDBG) log("CHLD:1 Callwaiting Answer call");
                                 PhoneUtils.answerCall(ringingCall);
-                                PhoneUtils.setMute(phone, false);
+                                PhoneUtils.setMute(false);
                                 // Setting the second callers state flag to TRUE (i.e. active)
                                 cdmaSetSecondCallState(true);
                             } else {
@@ -1863,7 +1863,7 @@
                             if (ringingCall.isRinging()) {
                                 if (VDBG) log("CHLD:2 Callwaiting Answer call");
                                 PhoneUtils.answerCall(ringingCall);
-                                PhoneUtils.setMute(phone, false);
+                                PhoneUtils.setMute(false);
                                 // Setting the second callers state flag to TRUE (i.e. active)
                                 cdmaSetSecondCallState(true);
                             } else if (PhoneApp.getInstance().cdmaPhoneCallState
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 05a2c6f..2b0ad52 100755
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -1881,9 +1881,9 @@
      * Toggle mute and unmute requests while keeping the same mute state
      */
     private void onResendMute() {
-        boolean muteState = PhoneUtils.getMute(mPhone);
-        PhoneUtils.setMuteInternal(mPhone, !muteState);
-        PhoneUtils.setMuteInternal(mPhone, muteState);
+        boolean muteState = PhoneUtils.getMute();
+        PhoneUtils.setMute(!muteState);
+        PhoneUtils.setMute(muteState);
     }
 
     /**
diff --git a/src/com/android/phone/InCallControlState.java b/src/com/android/phone/InCallControlState.java
index 71db99c..e0a4f9a 100644
--- a/src/com/android/phone/InCallControlState.java
+++ b/src/com/android/phone/InCallControlState.java
@@ -147,7 +147,7 @@
             muteIndicatorOn = false;
         } else {
             canMute = hasActiveForegroundCall;
-            muteIndicatorOn = PhoneUtils.getMute(fgCall.getPhone());
+            muteIndicatorOn = PhoneUtils.getMute();
         }
 
         // "Dialpad": Enabled only when it's OK to use the dialpad in the
diff --git a/src/com/android/phone/InCallScreen.java b/src/com/android/phone/InCallScreen.java
index cd7ab95..011d285 100755
--- a/src/com/android/phone/InCallScreen.java
+++ b/src/com/android/phone/InCallScreen.java
@@ -813,7 +813,7 @@
         // done by the user.
         if (app.getRestoreMuteOnInCallResume()) {
             // Mute state is based on the foreground call
-            PhoneUtils.restoreMuteState(mCM.getFgPhone());
+            PhoneUtils.restoreMuteState();
             app.setRestoreMuteOnInCallResume(false);
         }
 
@@ -1577,8 +1577,7 @@
                 break;
 
             case KeyEvent.KEYCODE_MUTE:
-                Phone phone = mCM.getFgPhone();
-                PhoneUtils.setMute(phone, !PhoneUtils.getMute(phone));
+                onMuteClick();
                 return true;
 
             // Various testing/debugging features, enabled ONLY when VDBG == true.
@@ -2653,7 +2652,7 @@
                     if (app.cdmaPhoneCallState.getCurrentCallState()
                             == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
                         //Unmute for the second MO call
-                        PhoneUtils.setMuteInternal(phone, false);
+                        PhoneUtils.setMute(false);
 
                         //Start the timer for displaying "Dialing" for second call
                         Message msg = Message.obtain(mHandler, THREEWAY_CALLERINFO_DISPLAY_DONE);
@@ -3091,10 +3090,13 @@
         }
     }
 
+    /*
+     * onMuteClick is called only when there is a foreground call
+     */
     private void onMuteClick() {
         if (VDBG) log("onMuteClick()...");
-        boolean newMuteState = !PhoneUtils.getMute(mCM.getFgPhone());
-        PhoneUtils.setMute(mCM.getFgPhone(), newMuteState);
+        boolean newMuteState = !PhoneUtils.getMute();
+        PhoneUtils.setMute(newMuteState);
     }
 
     private void onBluetoothClick() {
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 5793a03..d6230a1 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -461,14 +461,14 @@
         }
     }
 
-    void notifyMute() {
+    private void notifyMute() {
         if (mShowingMuteIcon) {
             mStatusBar.setIcon("mute", android.R.drawable.stat_notify_call_mute, 0);
             mShowingMuteIcon = true;
         }
     }
 
-    void cancelMute() {
+    private void cancelMute() {
         if (mShowingMuteIcon) {
             mStatusBar.removeIcon("mute");
             mShowingMuteIcon = false;
@@ -480,7 +480,7 @@
      * the actual current mute state of the Phone.
      */
     void updateMuteNotification() {
-        if ((mPhone.getState() == Phone.State.OFFHOOK) && mPhone.getMute()) {
+        if ((mCM.getState() == Phone.State.OFFHOOK) && PhoneUtils.getMute()) {
             if (DBG) log("updateMuteNotification: MUTED");
             notifyMute();
         } else {
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index cf42636..5aa6c1b 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -63,6 +63,7 @@
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ArrayList;
 
 /**
  * Misc utilities for the Phone app.
@@ -127,28 +128,35 @@
 
                     CallManager cm = (CallManager) ar.userObj;
 
-                    // TODO check if mute is a phone base, or system wide base operation
-                    // only to get active connection unmute here
-                    Phone phone = cm.getDefaultPhone();
-                    if (cm.hasActiveFgCall()) {
-                        phone = cm.getActiveFgCall().getPhone();
-                    } else if (cm.hasActiveRingingCall()) {
-                        phone = cm.getFirstActiveRingingCall().getPhone();
-                    }
-
                     // update the foreground connections, if there are new connections.
-                    List<Connection> fgConnections = cm.getActiveFgCall().getConnections();
+                    // Have to get all foreground calls instead of the active one
+                    // because there may two foreground calls co-exist in shore period
+                    // (a racing condition based on which phone changes firstly)
+                    // Otherwise the connection may get deleted.
+                    List<Connection> fgConnections = new ArrayList<Connection>();
+                    for (Call fgCall : cm.getForegroundCalls()) {
+                        if (!fgCall.isIdle()) {
+                            fgConnections.addAll(fgCall.getConnections());
+                        }
+                    }
                     for (Connection cn : fgConnections) {
                         if (sConnectionMuteTable.get(cn) == null) {
                             sConnectionMuteTable.put(cn, Boolean.FALSE);
                         }
                     }
 
-                    // update the background connections, if there are new connections.
-                    List<Connection> bgConnections = cm.getFirstActiveBgCall().getConnections();
+                    // mute is connection based operation, we need loop over
+                    // all background calls instead of the first one to update
+                    // the background connections, if there are new connections.
+                    List<Connection> bgConnections = new ArrayList<Connection>();
+                    for (Call bgCall : cm.getBackgroundCalls()) {
+                        if (!bgCall.isIdle()) {
+                            bgConnections.addAll(bgCall.getConnections());
+                        }
+                    }
                     for (Connection cn : bgConnections) {
                         if (sConnectionMuteTable.get(cn) == null) {
-                            sConnectionMuteTable.put(cn, Boolean.FALSE);
+                          sConnectionMuteTable.put(cn, Boolean.FALSE);
                         }
                     }
 
@@ -172,9 +180,9 @@
                     // call, and that with no connections, we should be back to a
                     // non-mute state.
                     if (cm.getState() != Phone.State.IDLE) {
-                        restoreMuteState(phone);
+                        restoreMuteState();
                     } else {
-                        setMuteInternal(phone, false);
+                        setMuteInternal(cm.getFgPhone(), false);
                     }
 
                     break;
@@ -282,7 +290,7 @@
                 answered = true;
 
                 // Always reset to "unmuted" for a freshly-answered call
-                setMute(phone, false);
+                setMute(false);
 
                 setAudioMode();
 
@@ -705,7 +713,9 @@
      * Restore the mute setting from the earliest connection of the
      * foreground call.
      */
-    static Boolean restoreMuteState(Phone phone) {
+    static Boolean restoreMuteState() {
+        Phone phone = PhoneApp.getInstance().mCM.getFgPhone();
+
         //get the earliest connection
         Connection c = phone.getForegroundCall().getEarliestConnection();
 
@@ -725,8 +735,7 @@
                 shouldMute = sConnectionMuteTable.get(
                         phone.getForegroundCall().getLatestConnection());
             } else if (phoneType == Phone.PHONE_TYPE_GSM) {
-                shouldMute = sConnectionMuteTable.get(
-                        phone.getForegroundCall().getEarliestConnection());
+                shouldMute = sConnectionMuteTable.get(c);
             }
             if (shouldMute == null) {
                 if (DBG) log("problem retrieving mute value for this connection.");
@@ -734,10 +743,10 @@
             }
 
             // set the mute value and return the result.
-            setMute (phone, shouldMute.booleanValue());
+            setMute (shouldMute.booleanValue());
             return shouldMute;
         }
-        return Boolean.valueOf(getMute (phone));
+        return Boolean.valueOf(getMute());
     }
 
     static void mergeCalls() {
@@ -1734,19 +1743,26 @@
     }
 
     /**
-     * Wrapper around Phone.setMute() that also updates the mute icon in
-     * the status bar.
+     *
+     * Mute / umute the foreground phone, which has the current foreground call
      *
      * All muting / unmuting from the in-call UI should go through this
      * wrapper.
+     *
+     * Wrapper around Phone.setMute() and setMicrophoneMute().
+     * It also updates the connectionMuteTable and mute icon in the status bar.
+     *
      */
-    static void setMute(Phone phone, boolean muted) {
+    static void setMute(boolean muted) {
+        CallManager cm = PhoneApp.getInstance().mCM;
+
         // make the call to mute the audio
-        setMuteInternal(phone, muted);
+        setMuteInternal(cm.getFgPhone(), muted);
+
 
         // update the foreground connections to match.  This includes
         // all the connections on conference calls.
-        for (Connection cn : phone.getForegroundCall().getConnections()) {
+        for (Connection cn : cm.getActiveFgCall().getConnections()) {
             if (sConnectionMuteTable.get(cn) == null) {
                 if (DBG) log("problem retrieving mute value for this connection.");
             }
@@ -1755,29 +1771,31 @@
     }
 
     /**
-     * Internally used muting function.  All UI calls should use {@link setMute}
+     * Internally used muting function.
      */
-    static void setMuteInternal(Phone phone, boolean muted) {
-        if (DBG) log("setMute: " + muted);
+    private static void setMuteInternal(Phone phone, boolean muted) {
+        if (DBG) log("setMuteInternal: " + muted);
         Context context = phone.getContext();
         boolean routeToAudioManager =
             context.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
         if (routeToAudioManager) {
             AudioManager audioManager =
                 (AudioManager) phone.getContext().getSystemService(Context.AUDIO_SERVICE);
-            if (DBG) log(" setMicrophoneMute: " + muted);
+            if (DBG) log("setMicrophoneMute: " + muted);
             audioManager.setMicrophoneMute(muted);
         } else {
             phone.setMute(muted);
         }
-        if (muted) {
-            NotificationMgr.getDefault().notifyMute();
-        } else {
-            NotificationMgr.getDefault().cancelMute();
-        }
+        NotificationMgr.getDefault().updateMuteNotification();
     }
 
-    static boolean getMute(Phone phone) {
+    /**
+     * Get the mute state of foreground phone, which has the current
+     * foreground call
+     */
+    static boolean getMute() {
+        Phone phone = PhoneApp.getInstance().mCM.getFgPhone();
+
         Context context = phone.getContext();
         boolean routeToAudioManager =
             context.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
@@ -1888,12 +1906,12 @@
                 Connection c = phone.getForegroundCall().getLatestConnection();
                 // If it is NOT an emg #, toggle the mute state. Otherwise, ignore the hook.
                 if (c != null && !PhoneNumberUtils.isEmergencyNumber(c.getAddress())) {
-                    if (getMute(phone)) {
+                    if (getMute()) {
                         if (DBG) log("handleHeadsetHook: UNmuting...");
-                        setMute(phone, false);
+                        setMute(false);
                     } else {
                         if (DBG) log("handleHeadsetHook: muting...");
-                        setMute(phone, true);
+                        setMute(true);
                     }
                 }
             }