SIP: implement conference call

Change-Id: Ifd420ed95e77e744c6aff28ac63e7363f97d9dc6
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 784f022..511440e 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import com.android.internal.telephony.sip.SipPhone;
+
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -453,7 +455,7 @@
             heldPhone = heldCall.getPhone();
         }
 
-        return (heldPhone == activePhone);
+        return heldPhone.getClass().equals(activePhone.getClass());
     }
 
     /**
@@ -466,10 +468,14 @@
      * In these cases, this operation may not be performed.
      */
     public void conference(Call heldCall) throws CallStateException {
-        if (canConference(heldCall))
+        Phone fgPhone = getFgPhone();
+        if (fgPhone instanceof SipPhone) {
+            ((SipPhone) fgPhone).conference(heldCall);
+        } else if (canConference(heldCall)) {
+            fgPhone.conference();
+        } else {
             throw(new CallStateException("Can't conference foreground and selected background call"));
-
-        heldCall.getPhone().conference();
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index a94518f..a052db0 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -21,6 +21,7 @@
 import android.content.SharedPreferences;
 import android.net.Uri;
 import android.net.rtp.AudioGroup;
+import android.net.rtp.AudioStream;
 import android.net.sip.SipAudioCall;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
@@ -221,7 +222,21 @@
     }
 
     public void conference() throws CallStateException {
-        // TODO
+        if ((foregroundCall.getState() != SipCall.State.ACTIVE)
+                || (foregroundCall.getState() != SipCall.State.ACTIVE)) {
+            throw new CallStateException("wrong state to merge calls: fg="
+                    + foregroundCall.getState() + ", bg="
+                    + backgroundCall.getState());
+        }
+        foregroundCall.merge(backgroundCall);
+    }
+
+    public void conference(Call that) throws CallStateException {
+        if (!(that instanceof SipCall)) {
+            throw new CallStateException("expect " + SipCall.class
+                    + ", cannot merge with " + that.getClass());
+        }
+        foregroundCall.merge((SipCall) that);
     }
 
     public boolean canTransfer() {
@@ -467,6 +482,18 @@
             return (audioGroup.getMode() == AudioGroup.MODE_MUTED);
         }
 
+        void merge(SipCall that) throws CallStateException {
+            AudioGroup myGroup = getAudioGroup();
+            for (Connection c : that.connections) {
+                SipConnection conn = (SipConnection) c;
+                conn.mergeTo(myGroup);
+                connections.add(conn);
+                conn.changeOwner(this);
+            }
+            that.connections.clear();
+            that.setState(Call.State.IDLE);
+        }
+
         void sendDtmf(char c) {
             AudioGroup audioGroup = getAudioGroup();
             if (audioGroup == null) return;
@@ -541,6 +568,7 @@
     private class SipConnection extends SipConnectionBase {
         private SipCall mOwner;
         private SipAudioCall mSipAudioCall;
+        private AudioGroup mOriginalGroup;
         private Call.State mState = Call.State.IDLE;
         private SipProfile mPeer;
         private boolean mIncoming = false;
@@ -660,6 +688,16 @@
             }
         }
 
+        void mergeTo(AudioGroup group) throws CallStateException {
+            AudioStream stream = mSipAudioCall.getAudioStream();
+            if (stream == null) {
+                throw new CallStateException("wrong state to merge: "
+                        + mSipAudioCall.getState());
+            }
+            if (mOriginalGroup == null) mOriginalGroup = getAudioGroup();
+            stream.join(group);
+        }
+
         @Override
         protected void setState(Call.State state) {
             if (state == mState) return;
@@ -694,6 +732,8 @@
 
         @Override
         public void hangup() throws CallStateException {
+            // TODO: need to pull AudioStream out of the AudioGroup in case
+            // this conn was part of a conf call
             Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
                     + ": on phone " + getPhone());
             try {