am 3294d44b: Add confcall management to SIP calls

Merge commit '3294d44b96f63f647fba3a03604eb028e28a42bc' into gingerbread-plus-aosp

* commit '3294d44b96f63f647fba3a03604eb028e28a42bc':
  Add confcall management to SIP calls
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 8e08592..980affa 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -322,7 +322,9 @@
                 }
                 break;
         }
-        audioManager.setMode(mode);
+        // calling audioManager.setMode() multiple times in a short period of
+        // time seems to break the audio recorder in in-call mode
+        if (audioManager.getMode() != mode) audioManager.setMode(mode);
     }
 
     private Context getContext() {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
index d48f94a..6c989b4 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -85,8 +85,10 @@
     protected void setState(Call.State state) {
         switch (state) {
             case ACTIVE:
-                connectTimeReal = SystemClock.elapsedRealtime();
-                connectTime = System.currentTimeMillis();
+                if (connectTime == 0) {
+                    connectTimeReal = SystemClock.elapsedRealtime();
+                    connectTime = System.currentTimeMillis();
+                }
                 break;
             case DISCONNECTED:
                 duration = SystemClock.elapsedRealtime() - connectTimeReal;
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index a052db0..1325dd3 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -100,7 +100,7 @@
     }
 
     public String getPhoneName() {
-        return mProfile.getProfileName();
+        return "SIP:" + getUriString(mProfile);
     }
 
     public String getSipUri() {
@@ -222,21 +222,25 @@
     }
 
     public void conference() throws CallStateException {
-        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());
+        synchronized (SipPhone.class) {
+            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);
         }
-        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());
+        synchronized (SipPhone.class) {
+            if (!(that instanceof SipCall)) {
+                throw new CallStateException("expect " + SipCall.class
+                        + ", cannot merge with " + that.getClass());
+            }
+            foregroundCall.merge((SipCall) that);
         }
-        foregroundCall.merge((SipCall) that);
     }
 
     public boolean canTransfer() {
@@ -248,12 +252,14 @@
     }
 
     public void clearDisconnected() {
-        ringingCall.clearDisconnected();
-        foregroundCall.clearDisconnected();
-        backgroundCall.clearDisconnected();
+        synchronized (SipPhone.class) {
+            ringingCall.clearDisconnected();
+            foregroundCall.clearDisconnected();
+            backgroundCall.clearDisconnected();
 
-        updatePhoneState();
-        notifyPreciseCallStateChanged();
+            updatePhoneState();
+            notifyPreciseCallStateChanged();
+        }
     }
 
     public void sendDtmf(char c) {
@@ -261,7 +267,9 @@
             Log.e(LOG_TAG,
                     "sendDtmf called with invalid character '" + c + "'");
         } else if (foregroundCall.getState().isAlive()) {
-            foregroundCall.sendDtmf(c);
+            synchronized (SipPhone.class) {
+                foregroundCall.sendDtmf(c);
+            }
         }
     }
 
@@ -307,7 +315,9 @@
     }
 
     public void setMute(boolean muted) {
-        foregroundCall.setMute(muted);
+        synchronized (SipPhone.class) {
+            foregroundCall.setMute(muted);
+        }
     }
 
     public boolean getMute() {
@@ -410,18 +420,20 @@
 
         @Override
         public void hangup() throws CallStateException {
-            Log.v(LOG_TAG, "hang up call: " + getState() + ": " + this
-                    + " on phone " + getPhone());
-            CallStateException excp = null;
-            for (Connection c : connections) {
-                try {
-                    c.hangup();
-                } catch (CallStateException e) {
-                    excp = e;
+            synchronized (SipPhone.class) {
+                Log.v(LOG_TAG, "hang up call: " + getState() + ": " + this
+                        + " on phone " + getPhone());
+                CallStateException excp = null;
+                for (Connection c : connections) {
+                    try {
+                        c.hangup();
+                    } catch (CallStateException e) {
+                        excp = e;
+                    }
                 }
+                if (excp != null) throw excp;
+                setState(State.DISCONNECTING);
             }
-            if (excp != null) throw excp;
-            setState(State.DISCONNECTING);
         }
 
         void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
@@ -454,19 +466,20 @@
         }
 
         void hold() throws CallStateException {
-            AudioGroup audioGroup = getAudioGroup();
-            if (audioGroup == null) return;
-            audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
             setState(State.HOLDING);
+            AudioGroup audioGroup = getAudioGroup();
+            if (audioGroup != null) {
+                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+            }
             for (Connection c : connections) ((SipConnection) c).hold();
         }
 
         void unhold() throws CallStateException {
-            AudioGroup audioGroup = getAudioGroup();
-            if (audioGroup == null) return;
-            audioGroup.setMode(AudioGroup.MODE_NORMAL);
             setState(State.ACTIVE);
-            for (Connection c : connections) ((SipConnection) c).unhold();
+            AudioGroup audioGroup = new AudioGroup();
+            for (Connection c : connections) {
+                ((SipConnection) c).unhold(audioGroup);
+            }
         }
 
         void setMute(boolean muted) {
@@ -483,17 +496,26 @@
         }
 
         void merge(SipCall that) throws CallStateException {
-            AudioGroup myGroup = getAudioGroup();
+            AudioGroup audioGroup = getAudioGroup();
             for (Connection c : that.connections) {
                 SipConnection conn = (SipConnection) c;
-                conn.mergeTo(myGroup);
-                connections.add(conn);
-                conn.changeOwner(this);
+                add(conn);
+                if (conn.getState() == Call.State.HOLDING) {
+                    conn.unhold(audioGroup);
+                }
             }
-            that.connections.clear();
             that.setState(Call.State.IDLE);
         }
 
+        private void add(SipConnection conn) {
+            SipCall call = conn.getCall();
+            if (call == this) return;
+            if (call != null) call.connections.remove(conn);
+
+            connections.add(conn);
+            conn.changeOwner(this);
+        }
+
         void sendDtmf(char c) {
             AudioGroup audioGroup = getAudioGroup();
             if (audioGroup == null) return;
@@ -568,7 +590,6 @@
     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;
@@ -673,6 +694,7 @@
         }
 
         void hold() throws CallStateException {
+            setState(Call.State.HOLDING);
             try {
                 mSipAudioCall.holdCall();
             } catch (SipException e) {
@@ -680,7 +702,9 @@
             }
         }
 
-        void unhold() throws CallStateException {
+        void unhold(AudioGroup audioGroup) throws CallStateException {
+            mSipAudioCall.setAudioGroup(audioGroup);
+            setState(Call.State.ACTIVE);
             try {
                 mSipAudioCall.continueCall();
             } catch (SipException e) {
@@ -688,16 +712,6 @@
             }
         }
 
-        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;
@@ -732,29 +746,36 @@
 
         @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 {
-                mSipAudioCall.endCall();
-                setState(Call.State.DISCONNECTING);
-                setDisconnectCause(DisconnectCause.LOCAL);
-            } catch (SipException e) {
-                throw new CallStateException("hangup(): " + e);
+            synchronized (SipPhone.class) {
+                Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
+                        + ": on phone " + getPhone().getPhoneName());
+                try {
+                    mSipAudioCall.endCall();
+                    setState(Call.State.DISCONNECTING);
+                    setDisconnectCause(DisconnectCause.LOCAL);
+                } catch (SipException e) {
+                    throw new CallStateException("hangup(): " + e);
+                }
             }
         }
 
         @Override
         public void separate() throws CallStateException {
-            // TODO: what's this for SIP?
-            /*
-            if (!disconnected) {
-                owner.separate(this);
-            } else {
-                throw new CallStateException ("disconnected");
+            synchronized (SipPhone.class) {
+                SipCall call = (SipCall) SipPhone.this.getBackgroundCall();
+                if (call.getState() != Call.State.IDLE) {
+                    throw new CallStateException(
+                            "cannot put conn back to a call in non-idle state: "
+                            + call.getState());
+                }
+                Log.v(LOG_TAG, "separate conn: " + mPeer.getUriString()
+                        + " from " + mOwner + " back to " + call);
+
+                AudioGroup audioGroup = call.getAudioGroup(); // may be null
+                call.add(this);
+                mSipAudioCall.setAudioGroup(audioGroup);
+                call.hold();
             }
-            */
         }
 
         @Override
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
index 36d65db..4b7e991 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -175,10 +175,6 @@
         return state;
     }
 
-    public String getPhoneName() {
-        return "SIP";
-    }
-
     public int getPhoneType() {
         // FIXME: add SIP phone type
         return Phone.PHONE_TYPE_GSM;
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index f4be839..3cdd114 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -244,7 +244,8 @@
      * Also, the {@code AudioStream} may change its group during a call (e.g.,
      * after the call is held/un-held). Finally, the {@code AudioGroup} object
      * returned by this method is undefined after the call ends or the
-     * {@link #close} method is called.
+     * {@link #close} method is called. If a group object is set by
+     * {@link #setAudioGroup(AudioGroup)}, then this method returns that object.
      *
      * @return the {@link AudioGroup} object or null if the RTP stream has not
      *      yet been set up
@@ -253,6 +254,15 @@
     AudioGroup getAudioGroup();
 
     /**
+     * Sets the {@link AudioGroup} object which the {@link AudioStream} object
+     * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
+     * will be dynamically created when needed.
+     *
+     * @see #getAudioStream
+     */
+    void setAudioGroup(AudioGroup audioGroup);
+
+    /**
      * Checks if the call is established.
      *
      * @return true if the call is established
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index 7161309..b8ac6d7 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -70,7 +70,8 @@
     private ISipSession mSipSession;
     private SdpSessionDescription mPeerSd;
 
-    private AudioStream mRtpSession;
+    private AudioStream mAudioStream;
+    private AudioGroup mAudioGroup;
     private SdpSessionDescription.AudioCodec mCodec;
     private long mSessionId = -1L; // SDP session ID
     private boolean mInCall = false;
@@ -505,11 +506,19 @@
     }
 
     public synchronized AudioStream getAudioStream() {
-        return mRtpSession;
+        return mAudioStream;
     }
 
     public synchronized AudioGroup getAudioGroup() {
-        return ((mRtpSession == null) ? null : mRtpSession.getAudioGroup());
+        if (mAudioGroup != null) return mAudioGroup;
+        return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup());
+    }
+
+    public synchronized void setAudioGroup(AudioGroup group) {
+        if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) {
+            mAudioStream.join(group);
+        }
+        mAudioGroup = group;
     }
 
     private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) {
@@ -561,7 +570,7 @@
             // TODO: get sample rate from sdp
             mCodec = getCodec(peerSd);
 
-            AudioStream audioStream = mRtpSession;
+            AudioStream audioStream = mAudioStream;
             audioStream.associate(InetAddress.getByName(peerMediaAddress),
                     peerMediaPort);
             audioStream.setCodec(convert(mCodec), mCodec.payloadType);
@@ -580,7 +589,7 @@
                     Log.d(TAG, "   not sending");
                     audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
                 }
-            } else {
+
                 /* The recorder volume will be very low if the device is in
                  * IN_CALL mode. Therefore, we have to set the mode to NORMAL
                  * in order to have the normal microphone level.
@@ -590,14 +599,22 @@
                         .setMode(AudioManager.MODE_NORMAL);
             }
 
-            AudioGroup audioGroup = new AudioGroup();
-            audioStream.join(audioGroup);
+            // AudioGroup logic:
+            AudioGroup audioGroup = getAudioGroup();
             if (mHold) {
-                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
-            } else if (mMuted) {
-                audioGroup.setMode(AudioGroup.MODE_MUTED);
+                if (audioGroup != null) {
+                    audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+                }
+                // don't create an AudioGroup here; doing so will fail if
+                // there's another AudioGroup out there that's active
             } else {
-                audioGroup.setMode(AudioGroup.MODE_NORMAL);
+                if (audioGroup == null) audioGroup = new AudioGroup();
+                audioStream.join(audioGroup);
+                if (mMuted) {
+                    audioGroup.setMode(AudioGroup.MODE_MUTED);
+                } else {
+                    audioGroup.setMode(AudioGroup.MODE_NORMAL);
+                }
             }
         } catch (Exception e) {
             Log.e(TAG, "call()", e);
@@ -606,20 +623,20 @@
 
     private void stopCall(boolean releaseSocket) {
         Log.d(TAG, "stop audiocall");
-        if (mRtpSession != null) {
-            mRtpSession.join(null);
+        if (mAudioStream != null) {
+            mAudioStream.join(null);
 
             if (releaseSocket) {
-                mRtpSession.release();
-                mRtpSession = null;
+                mAudioStream.release();
+                mAudioStream = null;
             }
         }
     }
 
     private int getLocalMediaPort() {
-        if (mRtpSession != null) return mRtpSession.getLocalPort();
+        if (mAudioStream != null) return mAudioStream.getLocalPort();
         try {
-            AudioStream s = mRtpSession =
+            AudioStream s = mAudioStream =
                     new AudioStream(InetAddress.getByName(getLocalIp()));
             return s.getLocalPort();
         } catch (IOException e) {