Merge "Change the 0_fully signal icon to the new style" into gingerbread
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 12cf853..bc3e767 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -165,7 +165,7 @@
     private void drawOval(Canvas canvas, float x, float y, float major, float minor,
             float angle, Paint paint) {
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
-        canvas.rotate((float) (angle * 180 / Math.PI), x, y);
+        canvas.rotate((float) (-angle * 180 / Math.PI), x, y);
         mReusableOvalRect.left = x - minor / 2;
         mReusableOvalRect.right = x + minor / 2;
         mReusableOvalRect.top = y - major / 2;
diff --git a/core/res/res/drawable-hdpi/overscroll_edge.png b/core/res/res/drawable-hdpi/overscroll_edge.png
index f8e40ec..e8c1aa3 100644
--- a/core/res/res/drawable-hdpi/overscroll_edge.png
+++ b/core/res/res/drawable-hdpi/overscroll_edge.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/overscroll_glow.png b/core/res/res/drawable-hdpi/overscroll_glow.png
index a8a62c4..3418384 100644
--- a/core/res/res/drawable-hdpi/overscroll_glow.png
+++ b/core/res/res/drawable-hdpi/overscroll_glow.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
index c299e12..ae90cc8 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
Binary files differ
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index bceceda..af3e0886 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -126,14 +126,27 @@
                 return false;
             }
 
-            SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
-            Log.v(LOG_TAG, "  ++++++ taking call from: "
-                    + sipAudioCall.getPeerProfile().getUriString());
-            String localUri = sipAudioCall.getLocalProfile().getUriString();
-            if (localUri.equals(mProfile.getUriString())) {
-                boolean makeCallWait = foregroundCall.getState().isAlive();
-                ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
-                return true;
+            try {
+                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
+                Log.d(LOG_TAG, "+++ taking call from: "
+                        + sipAudioCall.getPeerProfile().getUriString());
+                String localUri = sipAudioCall.getLocalProfile().getUriString();
+                if (localUri.equals(mProfile.getUriString())) {
+                    boolean makeCallWait = foregroundCall.getState().isAlive();
+                    ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
+                    if (sipAudioCall.getState()
+                            != SipSession.State.INCOMING_CALL) {
+                        // Peer cancelled the call!
+                        Log.d(LOG_TAG, "    call cancelled !!");
+                        ringingCall.reset();
+                    }
+                    return true;
+                }
+            } catch (Exception e) {
+                // Peer may cancel the call at any time during the time we hook
+                // up ringingCall with sipAudioCall. Clean up ringingCall when
+                // that happens.
+                ringingCall.reset();
             }
             return false;
         }
@@ -358,6 +371,11 @@
     }
 
     private class SipCall extends SipCallBase {
+        void reset() {
+            connections.clear();
+            setState(Call.State.IDLE);
+        }
+
         void switchWith(SipCall that) {
             synchronized (SipPhone.class) {
                 SipCall tmp = new SipCall();
@@ -444,6 +462,7 @@
                 if (state.isAlive()) {
                     Log.d(LOG_TAG, "hang up call: " + getState() + ": " + this
                             + " on phone " + getPhone());
+                    setState(State.DISCONNECTING);
                     CallStateException excp = null;
                     for (Connection c : connections) {
                         try {
@@ -453,7 +472,6 @@
                         }
                     }
                     if (excp != null) throw excp;
-                    setState(State.DISCONNECTING);
                 } else {
                     Log.d(LOG_TAG, "hang up dead call: " + getState() + ": "
                             + this + " on phone " + getPhone());
@@ -630,13 +648,20 @@
                 }
                 synchronized (SipPhone.class) {
                     setState(Call.State.DISCONNECTED);
-                    mSipAudioCall.close();
-                    mOwner.onConnectionEnded(SipConnection.this);
-                    Log.v(LOG_TAG, "-------- connection ended: "
-                            + mPeer.getUriString() + ": "
-                            + mSipAudioCall.getState() + ", cause: "
-                            + getDisconnectCause() + ", on phone "
+                    SipAudioCall sipAudioCall = mSipAudioCall;
+                    mSipAudioCall = null;
+                    String sessionState = (sipAudioCall == null)
+                            ? ""
+                            : (sipAudioCall.getState() + ", ");
+                    Log.v(LOG_TAG, "--- connection ended: "
+                            + mPeer.getUriString() + ": " + sessionState
+                            + "cause: " + getDisconnectCause() + ", on phone "
                             + getPhone());
+                    if (sipAudioCall != null) {
+                        sipAudioCall.setListener(null);
+                        sipAudioCall.close();
+                    }
+                    mOwner.onConnectionEnded(SipConnection.this);
                 }
             }
 
@@ -790,14 +815,17 @@
             synchronized (SipPhone.class) {
                 Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
                         + mState + ": on phone " + getPhone().getPhoneName());
+                if (!mState.isAlive()) return;
                 try {
-                    if (mState.isAlive()) {
-                        if (mSipAudioCall != null) mSipAudioCall.endCall();
-                        setState(Call.State.DISCONNECTING);
-                        setDisconnectCause(DisconnectCause.LOCAL);
+                    SipAudioCall sipAudioCall = mSipAudioCall;
+                    if (sipAudioCall != null) {
+                        sipAudioCall.setListener(null);
+                        sipAudioCall.endCall();
                     }
                 } catch (SipException e) {
                     throw new CallStateException("hangup(): " + e);
+                } finally {
+                    mAdapter.onCallEnded(DisconnectCause.LOCAL);
                 }
             }
         }
diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java
index f171806..3877aeb 100644
--- a/voip/java/android/net/rtp/AudioCodec.java
+++ b/voip/java/android/net/rtp/AudioCodec.java
@@ -80,8 +80,7 @@
      */
     public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
 
-    // TODO: add rest of the codecs when the native part is done.
-    private static final AudioCodec[] sCodecs = {GSM_EFR, GSM, PCMU, PCMA};
+    private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
 
     private AudioCodec(int type, String rtpmap, String fmtp) {
         this.type = type;
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 59631c1..52f5716 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -520,7 +520,9 @@
 
         private String getUri(ISipSession session) {
             try {
-                return session.getLocalProfile().getUriString();
+                return ((session == null)
+                        ? "no session"
+                        : session.getLocalProfile().getUriString());
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 0ff5586..130fe9f 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -510,31 +510,43 @@
         }
     }
 
+    // KeepAliveProcess is controlled by AutoRegistrationProcess.
+    // All methods will be invoked in sync with SipService.this except realRun()
     private class KeepAliveProcess implements Runnable {
         private static final String TAG = "\\KEEPALIVE/";
         private static final int INTERVAL = 10;
         private SipSessionGroup.SipSessionImpl mSession;
+        private boolean mRunning = false;
 
         public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
             mSession = session;
         }
 
         public void start() {
+            if (mRunning) return;
+            mRunning = true;
             mTimer.set(INTERVAL * 1000, this);
         }
 
+        // timeout handler
         public void run() {
+            if (!mRunning) return;
+            final SipSessionGroup.SipSessionImpl session = mSession;
+
             // delegate to mExecutor
             getExecutor().addTask(new Runnable() {
                 public void run() {
-                    realRun();
+                    realRun(session);
                 }
             });
         }
 
-        private void realRun() {
+        // real timeout handler
+        private void realRun(SipSessionGroup.SipSessionImpl session) {
             synchronized (SipService.this) {
-                SipSessionGroup.SipSessionImpl session = mSession.duplicate();
+                if (notCurrentSession(session)) return;
+
+                session = session.duplicate();
                 if (DEBUG) Log.d(TAG, "~~~ keepalive");
                 mTimer.cancel(this);
                 session.sendKeepAlive();
@@ -547,8 +559,14 @@
         }
 
         public void stop() {
+            mRunning = false;
+            mSession = null;
             mTimer.cancel(this);
         }
+
+        private boolean notCurrentSession(ISipSession session) {
+            return (session != mSession) || !mRunning;
+        }
     }
 
     private class AutoRegistrationProcess extends SipSessionAdapter
@@ -561,13 +579,15 @@
         private long mExpiryTime;
         private int mErrorCode;
         private String mErrorMessage;
+        private boolean mRunning = false;
 
         private String getAction() {
             return toString();
         }
 
         public void start(SipSessionGroup group) {
-            if (mSession == null) {
+            if (!mRunning) {
+                mRunning = true;
                 mBackoff = 1;
                 mSession = (SipSessionGroup.SipSessionImpl)
                         group.createSession(this);
@@ -584,35 +604,24 @@
         }
 
         public void stop() {
-            stop(false);
-        }
-
-        private void stopButKeepStates() {
-            stop(true);
-        }
-
-        private void stop(boolean keepStates) {
-            if (mSession == null) return;
+            if (!mRunning) return;
+            mRunning = false;
+            mSession.setListener(null);
             if (mConnected && mRegistered) mSession.unregister();
+
             mTimer.cancel(this);
             if (mKeepAliveProcess != null) {
                 mKeepAliveProcess.stop();
                 mKeepAliveProcess = null;
             }
-            if (!keepStates) {
-                mSession = null;
-                mRegistered = false;
-            }
-        }
 
-        private boolean isStopped() {
-            return (mSession == null);
+            mRegistered = false;
+            setListener(mProxy.getListener());
         }
 
         public void setListener(ISipSessionListener listener) {
             synchronized (SipService.this) {
                 mProxy.setListener(listener);
-                if (mSession == null) return;
 
                 try {
                     int state = (mSession == null)
@@ -632,6 +641,18 @@
                             mProxy.onRegistrationFailed(mSession, mErrorCode,
                                     mErrorMessage);
                         }
+                    } else if (!mConnected) {
+                        mProxy.onRegistrationFailed(mSession,
+                                SipErrorCode.DATA_CONNECTION_LOST,
+                                "no data connection");
+                    } else if (!mRunning) {
+                        mProxy.onRegistrationFailed(mSession,
+                                SipErrorCode.CLIENT_ERROR,
+                                "registration not running");
+                    } else {
+                        mProxy.onRegistrationFailed(mSession,
+                                SipErrorCode.IN_PROGRESS,
+                                String.valueOf(state));
                     }
                 } catch (Throwable t) {
                     Log.w(TAG, "setListener(): " + t);
@@ -643,21 +664,29 @@
             return mRegistered;
         }
 
+        // timeout handler
         public void run() {
-            // delegate to mExecutor
-            getExecutor().addTask(new Runnable() {
-                public void run() {
-                    realRun();
-                }
-            });
+            synchronized (SipService.this) {
+                if (!mRunning) return;
+                final SipSessionGroup.SipSessionImpl session = mSession;
+
+                // delegate to mExecutor
+                getExecutor().addTask(new Runnable() {
+                    public void run() {
+                        realRun(session);
+                    }
+                });
+            }
         }
 
-        private void realRun() {
-            mErrorCode = SipErrorCode.NO_ERROR;
-            mErrorMessage = null;
-            if (DEBUG) Log.d(TAG, "~~~ registering");
+        // real timeout handler
+        private void realRun(SipSessionGroup.SipSessionImpl session) {
             synchronized (SipService.this) {
-                if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
+                if (notCurrentSession(session)) return;
+                mErrorCode = SipErrorCode.NO_ERROR;
+                mErrorMessage = null;
+                if (DEBUG) Log.d(TAG, "~~~ registering");
+                if (mConnected) session.register(EXPIRY_TIME);
             }
         }
 
@@ -697,22 +726,29 @@
         public void onRegistering(ISipSession session) {
             if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
+                if (notCurrentSession(session)) return;
+
                 mRegistered = false;
                 mProxy.onRegistering(session);
             }
         }
 
+        private boolean notCurrentSession(ISipSession session) {
+            if (session != mSession) {
+                ((SipSessionGroup.SipSessionImpl) session).setListener(null);
+                return true;
+            }
+            return !mRunning;
+        }
+
         @Override
         public void onRegistrationDone(ISipSession session, int duration) {
             if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
+                if (notCurrentSession(session)) return;
 
                 mProxy.onRegistrationDone(session, duration);
 
-                if (isStopped()) return;
-
                 if (duration > 0) {
                     mSession.clearReRegisterRequired();
                     mExpiryTime = SystemClock.elapsedRealtime()
@@ -751,17 +787,18 @@
             if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
                     + SipErrorCode.toString(errorCode) + ": " + message);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
-                mErrorCode = errorCode;
-                mErrorMessage = message;
-                mProxy.onRegistrationFailed(session, errorCode, message);
+                if (notCurrentSession(session)) return;
 
                 if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
                     if (DEBUG) Log.d(TAG, "   pause auto-registration");
-                    stopButKeepStates();
-                } else if (!isStopped()) {
+                    stop();
+                } else {
                     onError();
                 }
+
+                mErrorCode = errorCode;
+                mErrorMessage = message;
+                mProxy.onRegistrationFailed(session, errorCode, message);
             }
         }
 
@@ -769,14 +806,11 @@
         public void onRegistrationTimeout(ISipSession session) {
             if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
             synchronized (SipService.this) {
-                if (!isStopped() && (session != mSession)) return;
+                if (notCurrentSession(session)) return;
+
                 mErrorCode = SipErrorCode.TIME_OUT;
                 mProxy.onRegistrationTimeout(session);
-
-                if (!isStopped()) {
-                    mRegistered = false;
-                    onError();
-                }
+                onError();
             }
         }
 
@@ -883,6 +917,7 @@
                 mConnected = connected;
             }
 
+            // timeout handler
             @Override
             public void run() {
                 // delegate to mExecutor
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 4321d7b..c68fa1b 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -334,12 +334,12 @@
             if (isRequestEvent(Request.INVITE, evt)) {
                 RequestEvent event = (RequestEvent) evt;
                 SipSessionImpl newSession = new SipSessionImpl(mProxy);
+                newSession.mState = SipSession.State.INCOMING_CALL;
                 newSession.mServerTransaction = mSipHelper.sendRinging(event,
                         generateTag());
                 newSession.mDialog = newSession.mServerTransaction.getDialog();
                 newSession.mInviteReceived = event;
                 newSession.mPeerProfile = createPeerProfile(event.getRequest());
-                newSession.mState = SipSession.State.INCOMING_CALL;
                 newSession.mPeerSessionDescription =
                         extractContent(event.getRequest());
                 addSipSession(newSession);
@@ -708,7 +708,6 @@
                 case SipSession.State.PINGING:
                     reset();
                     mReRegisterFlag = true;
-                    mState = SipSession.State.READY_TO_CALL;
                     break;
 
                 default:
@@ -877,6 +876,7 @@
         private boolean readyForCall(EventObject evt) throws SipException {
             // expect MakeCallCommand, RegisterCommand, DEREGISTER
             if (evt instanceof MakeCallCommand) {
+                mState = SipSession.State.OUTGOING_CALL;
                 MakeCallCommand cmd = (MakeCallCommand) evt;
                 mPeerProfile = cmd.getPeerProfile();
                 mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
@@ -884,25 +884,24 @@
                         generateTag());
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSession.State.OUTGOING_CALL;
                 mProxy.onCalling(this);
                 startSessionTimer(cmd.getTimeout());
                 return true;
             } else if (evt instanceof RegisterCommand) {
+                mState = SipSession.State.REGISTERING;
                 int duration = ((RegisterCommand) evt).getDuration();
                 mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
                         generateTag(), duration);
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSession.State.REGISTERING;
                 mProxy.onRegistering(this);
                 return true;
             } else if (DEREGISTER == evt) {
+                mState = SipSession.State.DEREGISTERING;
                 mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
                         generateTag(), 0);
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSession.State.DEREGISTERING;
                 mProxy.onRegistering(this);
                 return true;
             }
@@ -913,11 +912,11 @@
             // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
             if (evt instanceof MakeCallCommand) {
                 // answer call
+                mState = SipSession.State.INCOMING_CALL_ANSWERING;
                 mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
                         mLocalProfile,
                         ((MakeCallCommand) evt).getSessionDescription(),
                         mServerTransaction);
-                mState = SipSession.State.INCOMING_CALL_ANSWERING;
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             } else if (END_CALL == evt) {
@@ -1009,8 +1008,8 @@
                 // RFC says that UA should not send out cancel when no
                 // response comes back yet. We are cheating for not checking
                 // response.
-                mSipHelper.sendCancel(mClientTransaction);
                 mState = SipSession.State.OUTGOING_CALL_CANCELING;
+                mSipHelper.sendCancel(mClientTransaction);
                 startSessionTimer(CANCEL_CALL_TIMER);
                 return true;
             }
@@ -1065,8 +1064,8 @@
                 return true;
             } else if (isRequestEvent(Request.INVITE, evt)) {
                 // got Re-INVITE
-                RequestEvent event = mInviteReceived = (RequestEvent) evt;
                 mState = SipSession.State.INCOMING_CALL;
+                RequestEvent event = mInviteReceived = (RequestEvent) evt;
                 mPeerSessionDescription = extractContent(event.getRequest());
                 mServerTransaction = null;
                 mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
@@ -1077,9 +1076,9 @@
                 return true;
             } else if (evt instanceof MakeCallCommand) {
                 // to change call
+                mState = SipSession.State.OUTGOING_CALL;
                 mClientTransaction = mSipHelper.sendReinvite(mDialog,
                         ((MakeCallCommand) evt).getSessionDescription());
-                mState = SipSession.State.OUTGOING_CALL;
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             }
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
index 9a2227d..f3ecac2 100644
--- a/voip/jni/rtp/AmrCodec.cpp
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <string.h>
+
 #include "AudioCodec.h"
 
 #include "gsmamr_dec.h"
@@ -21,6 +23,170 @@
 
 namespace {
 
+const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244};
+
+//------------------------------------------------------------------------------
+
+// See RFC 4867 for the encoding details.
+
+class AmrCodec : public AudioCodec
+{
+public:
+    AmrCodec() {
+        if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
+            mEncoder = NULL;
+        }
+        if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
+            mDecoder = NULL;
+        }
+    }
+
+    ~AmrCodec() {
+        if (mEncoder) {
+            AMREncodeExit(&mEncoder, &mSidSync);
+        }
+        if (mDecoder) {
+            GSMDecodeFrameExit(&mDecoder);
+        }
+    }
+
+    int set(int sampleRate, const char *fmtp);
+    int encode(void *payload, int16_t *samples);
+    int decode(int16_t *samples, void *payload, int length);
+
+private:
+    void *mEncoder;
+    void *mSidSync;
+    void *mDecoder;
+
+    int mMode;
+    int mModeSet;
+    bool mOctetAligned;
+};
+
+int AmrCodec::set(int sampleRate, const char *fmtp)
+{
+    // These parameters are not supported.
+    if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") ||
+        strcasestr(fmtp, "interleaving=")) {
+        return -1;
+    }
+
+    // Handle mode-set and octet-align.
+    char *modes = strcasestr(fmtp, "mode-set=");
+    if (modes) {
+        mMode = 0;
+        mModeSet = 0;
+        for (char c = *modes; c && c != ' '; c = *++modes) {
+            if (c >= '0' && c <= '7') {
+                int mode = c - '0';
+                if (mode > mMode) {
+                    mMode = mode;
+                }
+                mModeSet |= 1 << mode;
+            }
+        }
+    } else {
+        mMode = 7;
+        mModeSet = 0xFF;
+    }
+    mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL);
+
+    // TODO: handle mode-change-*.
+
+    return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
+}
+
+int AmrCodec::encode(void *payload, int16_t *samples)
+{
+    unsigned char *bytes = (unsigned char *)payload;
+    Frame_Type_3GPP type;
+
+    int length = AMREncode(mEncoder, mSidSync, (Mode)mMode,
+        samples, bytes + 1, &type, AMR_TX_WMF);
+
+    if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) {
+        return -1;
+    }
+
+    if (mOctetAligned) {
+        bytes[0] = 0xF0;
+        bytes[1] = (mMode << 3) | 0x04;
+        ++length;
+    } else {
+        // CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit).
+        bytes[0] = 0xFF;
+        bytes[1] = 0xC0 | (mMode << 1) | 1;
+
+        // Shift left 6 bits and update the length.
+        bytes[length + 1] = 0;
+        for (int i = 0; i <= length; ++i) {
+            bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2);
+        }
+        length = (10 + gFrameBits[mMode] + 7) >> 3;
+    }
+    return length;
+}
+
+int AmrCodec::decode(int16_t *samples, void *payload, int length)
+{
+    unsigned char *bytes = (unsigned char *)payload;
+    Frame_Type_3GPP type;
+    if (length < 2) {
+        return -1;
+    }
+    int request = bytes[0] >> 4;
+
+    if (mOctetAligned) {
+        if ((bytes[1] & 0xC4) != 0x04) {
+            return -1;
+        }
+        type = (Frame_Type_3GPP)(bytes[1] >> 3);
+        if (length != (16 + gFrameBits[type] + 7) >> 3) {
+            return -1;
+        }
+        length -= 2;
+        bytes += 2;
+    } else {
+        if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) {
+            return -1;
+        }
+        type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07);
+        if (length != (10 + gFrameBits[type] + 7) >> 3) {
+            return -1;
+        }
+
+        // Shift left 2 bits and update the length.
+        --length;
+        for (int i = 1; i < length; ++i) {
+            bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6);
+        }
+        bytes[length] <<= 2;
+        length = (gFrameBits[type] + 7) >> 3;
+        ++bytes;
+    }
+
+    if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) {
+        return -1;
+    }
+
+    // Handle CMR
+    if (request < 8 && request != mMode) {
+        for (int i = request; i >= 0; --i) {
+            if (mModeSet & (1 << i)) {
+                mMode = request;
+                break;
+            }
+        }
+    }
+
+    return 160;
+}
+
+//------------------------------------------------------------------------------
+
+// See RFC 3551 for the encoding details.
+
 class GsmEfrCodec : public AudioCodec
 {
 public:
@@ -91,6 +257,11 @@
 
 } // namespace
 
+AudioCodec *newAmrCodec()
+{
+    return new AmrCodec;
+}
+
 AudioCodec *newGsmEfrCodec()
 {
     return new GsmEfrCodec;
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
index afc193c..2267ea0 100644
--- a/voip/jni/rtp/AudioCodec.cpp
+++ b/voip/jni/rtp/AudioCodec.cpp
@@ -21,6 +21,7 @@
 extern AudioCodec *newAlawCodec();
 extern AudioCodec *newUlawCodec();
 extern AudioCodec *newGsmCodec();
+extern AudioCodec *newAmrCodec();
 extern AudioCodec *newGsmEfrCodec();
 
 struct AudioCodecType {
@@ -30,6 +31,7 @@
     {"PCMA", newAlawCodec},
     {"PCMU", newUlawCodec},
     {"GSM", newGsmCodec},
+    {"AMR", newAmrCodec},
     {"GSM-EFR", newGsmEfrCodec},
     {NULL, NULL},
 };