Merge "Fix documentation bug."
diff --git a/voip/java/android/net/rtp/AudioGroup.java b/voip/java/android/net/rtp/AudioGroup.java
index 43a3827..a6b54d8 100644
--- a/voip/java/android/net/rtp/AudioGroup.java
+++ b/voip/java/android/net/rtp/AudioGroup.java
@@ -21,14 +21,14 @@
 
 /**
  * An AudioGroup acts as a router connected to the speaker, the microphone, and
- * {@link AudioStream}s. Its pipeline has four steps. First, for each
- * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
- * packets and stores in its buffer. Then, if the microphone is enabled,
- * processes the recorded audio and stores in its buffer. Third, if the speaker
- * is enabled, mixes and playbacks buffers of all AudioStreams. Finally, for
- * each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
- * buffers and sends back the encoded packets. An AudioGroup does nothing if
- * there is no AudioStream in it.
+ * {@link AudioStream}s. Its execution loop consists of four steps. First, for
+ * each AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its
+ * incoming packets and stores in its buffer. Then, if the microphone is
+ * enabled, processes the recorded audio and stores in its buffer. Third, if the
+ * speaker is enabled, mixes and playbacks buffers of all AudioStreams. Finally,
+ * for each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all
+ * other buffers and sends back the encoded packets. An AudioGroup does nothing
+ * if there is no AudioStream in it.
  *
  * <p>Few things must be noticed before using these classes. The performance is
  * highly related to the system load and the network bandwidth. Usually a
@@ -47,7 +47,12 @@
  * modes other than {@link #MODE_ON_HOLD}. In addition, before adding an
  * AudioStream into an AudioGroup, one should always put all other AudioGroups
  * into {@link #MODE_ON_HOLD}. That will make sure the audio driver correctly
- * initialized.
+ * initialized.</p>
+ *
+ * <p class="note">Using this class requires
+ * {@link android.Manifest.permission#RECORD_AUDIO} permission.</p>
+ *
+ * @see AudioStream
  * @hide
  */
 public class AudioGroup {
@@ -78,6 +83,8 @@
      */
     public static final int MODE_ECHO_SUPPRESSION = 3;
 
+    private static final int MODE_LAST = 3;
+
     private final Map<AudioStream, Integer> mStreams;
     private int mMode = MODE_ON_HOLD;
 
@@ -94,6 +101,15 @@
     }
 
     /**
+     * Returns the {@link AudioStream}s in this group.
+     */
+    public AudioStream[] getStreams() {
+        synchronized (this) {
+            return mStreams.keySet().toArray(new AudioStream[mStreams.size()]);
+        }
+    }
+
+    /**
      * Returns the current mode.
      */
     public int getMode() {
@@ -108,49 +124,77 @@
      * @param mode The mode to change to.
      * @throws IllegalArgumentException if the mode is invalid.
      */
-    public synchronized native void setMode(int mode);
+    public void setMode(int mode) {
+        if (mode < 0 || mode > MODE_LAST) {
+            throw new IllegalArgumentException("Invalid mode");
+        }
+        synchronized (this) {
+            nativeSetMode(mode);
+            mMode = mode;
+        }
+    }
 
-    private native void add(int mode, int socket, String remoteAddress,
-            int remotePort, String codecSpec, int dtmfType);
+    private native void nativeSetMode(int mode);
 
-    synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) {
-        if (!mStreams.containsKey(stream)) {
-            try {
-                int socket = stream.dup();
-                String codecSpec = String.format("%d %s %s", codec.type,
-                        codec.rtpmap, codec.fmtp);
-                add(stream.getMode(), socket,
-                        stream.getRemoteAddress().getHostAddress(),
-                        stream.getRemotePort(), codecSpec, dtmfType);
-                mStreams.put(stream, socket);
-            } catch (NullPointerException e) {
-                throw new IllegalStateException(e);
+    // Package-private method used by AudioStream.join().
+    void add(AudioStream stream, AudioCodec codec, int dtmfType) {
+        synchronized (this) {
+            if (!mStreams.containsKey(stream)) {
+                try {
+                    int socket = stream.dup();
+                    String codecSpec = String.format("%d %s %s", codec.type,
+                            codec.rtpmap, codec.fmtp);
+                    nativeAdd(stream.getMode(), socket,
+                            stream.getRemoteAddress().getHostAddress(),
+                            stream.getRemotePort(), codecSpec, dtmfType);
+                    mStreams.put(stream, socket);
+                } catch (NullPointerException e) {
+                    throw new IllegalStateException(e);
+                }
             }
         }
     }
 
-    private native void remove(int socket);
+    private native void nativeAdd(int mode, int socket, String remoteAddress,
+            int remotePort, String codecSpec, int dtmfType);
 
-    synchronized void remove(AudioStream stream) {
-        Integer socket = mStreams.remove(stream);
-        if (socket != null) {
-            remove(socket);
+    // Package-private method used by AudioStream.join().
+    void remove(AudioStream stream) {
+        synchronized (this) {
+            Integer socket = mStreams.remove(stream);
+            if (socket != null) {
+                nativeRemove(socket);
+            }
         }
     }
 
+    private native void nativeRemove(int socket);
+
     /**
      * Sends a DTMF digit to every {@link AudioStream} in this group. Currently
      * only event {@code 0} to {@code 15} are supported.
      *
      * @throws IllegalArgumentException if the event is invalid.
      */
-    public native synchronized void sendDtmf(int event);
+    public void sendDtmf(int event) {
+        if (event < 0 || event > 15) {
+            throw new IllegalArgumentException("Invalid event");
+        }
+        synchronized (this) {
+            nativeSendDtmf(event);
+        }
+    }
+
+    private native void nativeSendDtmf(int event);
 
     /**
      * Removes every {@link AudioStream} in this group.
      */
-    public synchronized void clear() {
-        remove(-1);
+    public void clear() {
+        synchronized (this) {
+            mStreams.clear();
+            nativeRemove(-1);
+        }
     }
 
     @Override
diff --git a/voip/java/android/net/rtp/AudioStream.java b/voip/java/android/net/rtp/AudioStream.java
index e5197ce..0edae6b 100644
--- a/voip/java/android/net/rtp/AudioStream.java
+++ b/voip/java/android/net/rtp/AudioStream.java
@@ -34,8 +34,12 @@
  * of the setter methods are disabled. This is designed to ease the task of
  * managing native resources. One can always make an AudioStream leave its
  * AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it
- * back after the modification is done.
+ * back after the modification is done.</p>
  *
+ * <p class="note">Using this class requires
+ * {@link android.Manifest.permission#INTERNET} permission.</p>
+ *
+ * @see RtpStream
  * @see AudioGroup
  * @hide
  */
@@ -82,16 +86,18 @@
      * @see AudioGroup
      */
     public void join(AudioGroup group) {
-        if (mGroup == group) {
-            return;
-        }
-        if (mGroup != null) {
-            mGroup.remove(this);
-            mGroup = null;
-        }
-        if (group != null) {
-            group.add(this, mCodec, mDtmfType);
-            mGroup = group;
+        synchronized (this) {
+            if (mGroup == group) {
+                return;
+            }
+            if (mGroup != null) {
+                mGroup.remove(this);
+                mGroup = null;
+            }
+            if (group != null) {
+                group.add(this, mCodec, mDtmfType);
+                mGroup = group;
+            }
         }
     }
 
diff --git a/voip/java/android/net/rtp/RtpStream.java b/voip/java/android/net/rtp/RtpStream.java
index 23fb258..87d8bc6 100644
--- a/voip/java/android/net/rtp/RtpStream.java
+++ b/voip/java/android/net/rtp/RtpStream.java
@@ -24,6 +24,9 @@
 /**
  * RtpStream represents the base class of streams which send and receive network
  * packets with media payloads over Real-time Transport Protocol (RTP).
+ *
+ * <p class="note">Using this class requires
+ * {@link android.Manifest.permission#INTERNET} permission.</p>
  * @hide
  */
 public class RtpStream {
@@ -43,6 +46,8 @@
      */
     public static final int MODE_RECEIVE_ONLY = 2;
 
+    private static final int MODE_LAST = 2;
+
     private final InetAddress mLocalAddress;
     private final int mLocalPort;
 
@@ -129,7 +134,7 @@
         if (isBusy()) {
             throw new IllegalStateException("Busy");
         }
-        if (mode != MODE_NORMAL && mode != MODE_SEND_ONLY && mode != MODE_RECEIVE_ONLY) {
+        if (mode < 0 || mode > MODE_LAST) {
             throw new IllegalArgumentException("Invalid mode");
         }
         mMode = mode;
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
index 72ee44e..84c7166 100644
--- a/voip/jni/rtp/AmrCodec.cpp
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -73,7 +73,7 @@
     }
 
     // Handle mode-set and octet-align.
-    char *modes = (char*)strcasestr(fmtp, "mode-set=");
+    const char *modes = strcasestr(fmtp, "mode-set=");
     if (modes) {
         mMode = 0;
         mModeSet = 0;
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 0c8a725..cba1123 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -90,6 +90,7 @@
     void encode(int tick, AudioStream *chain);
     void decode(int tick);
 
+private:
     enum {
         NORMAL = 0,
         SEND_ONLY = 1,
@@ -97,7 +98,6 @@
         LAST_MODE = 2,
     };
 
-private:
     int mMode;
     int mSocket;
     sockaddr_storage mRemote;
@@ -463,6 +463,7 @@
     bool add(AudioStream *stream);
     bool remove(int socket);
 
+private:
     enum {
         ON_HOLD = 0,
         MUTED = 1,
@@ -471,7 +472,6 @@
         LAST_MODE = 3,
     };
 
-private:
     AudioStream *mChain;
     int mEventQueue;
     volatile int mDtmfEvent;
@@ -948,16 +948,10 @@
 
 void setMode(JNIEnv *env, jobject thiz, jint mode)
 {
-    if (mode < 0 || mode > AudioGroup::LAST_MODE) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
-    }
     AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
     if (group && !group->setMode(mode)) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
     }
-    env->SetIntField(thiz, gMode, mode);
 }
 
 void sendDtmf(JNIEnv *env, jobject thiz, jint event)
@@ -969,10 +963,10 @@
 }
 
 JNINativeMethod gMethods[] = {
-    {"add", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add},
-    {"remove", "(I)V", (void *)remove},
-    {"setMode", "(I)V", (void *)setMode},
-    {"sendDtmf", "(I)V", (void *)sendDtmf},
+    {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add},
+    {"nativeRemove", "(I)V", (void *)remove},
+    {"nativeSetMode", "(I)V", (void *)setMode},
+    {"nativeSendDtmf", "(I)V", (void *)sendDtmf},
 };
 
 } // namespace