Support new WebSocket signaling format.

- Support new GAE message format and new signaling
sequence, which allows connection to 3-dot-apprtc server.
- Add UI setting to switch between GAE / WebSockets signaling.
- Some clean ups to better support command line application
execution.

BUG=3937,3995,4041
R=jiayl@webrtc.org, tkchin@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/27319004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7813 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/examples/android/res/values/strings.xml b/talk/examples/android/res/values/strings.xml
index 08ff541..c290505 100644
--- a/talk/examples/android/res/values/strings.xml
+++ b/talk/examples/android/res/values/strings.xml
@@ -57,6 +57,11 @@
     <string name="pref_hwcodec_dlg">Use VP8 VP8 hardware accelerated codec (if available).</string>
     <string name="pref_hwcodec_default">true</string>
 
+    <string name="pref_signaling_key">signaling_preference</string>
+    <string name="pref_signaling_title">Use WebSocket signaling.</string>
+    <string name="pref_signaling_dlg">Use WebSocket signaling.</string>
+    <string name="pref_signaling_default">true</string>
+
     <string name="pref_value_enabled">Enabled</string>
     <string name="pref_value_disabled">Disabled</string>
 
diff --git a/talk/examples/android/res/xml/preferences.xml b/talk/examples/android/res/xml/preferences.xml
index 94894bc..57c0315 100644
--- a/talk/examples/android/res/xml/preferences.xml
+++ b/talk/examples/android/res/xml/preferences.xml
@@ -43,5 +43,10 @@
         android:dialogTitle="@string/pref_cpu_usage_detection_dlg"
         android:defaultValue="@string/pref_cpu_usage_detection_default" />
 
+    <CheckBoxPreference
+        android:key="@string/pref_signaling_key"
+        android:title="@string/pref_signaling_title"
+        android:dialogTitle="@string/pref_signaling_dlg"
+        android:defaultValue="@string/pref_signaling_default" />
 
 </PreferenceScreen>
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
index 96816a7..9fbdb3b 100644
--- a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
@@ -75,14 +75,18 @@
     public final String roomId;
     public final String clientId;
     public final String channelToken;
-    public final String offerSdp;
+    public final String wssUrl;
+    public final String wssPostUrl;
+    public final SessionDescription offerSdp;
+    public final List<IceCandidate> iceCandidates;
 
     public SignalingParameters(
         List<PeerConnection.IceServer> iceServers,
         boolean initiator, MediaConstraints pcConstraints,
         MediaConstraints videoConstraints, MediaConstraints audioConstraints,
         String roomUrl, String roomId, String clientId,
-        String channelToken, String offerSdp ) {
+        String wssUrl, String wssPostUrl, String channelToken,
+        SessionDescription offerSdp, List<IceCandidate> iceCandidates) {
       this.iceServers = iceServers;
       this.initiator = initiator;
       this.pcConstraints = pcConstraints;
@@ -91,8 +95,11 @@
       this.roomUrl = roomUrl;
       this.roomId = roomId;
       this.clientId = clientId;
+      this.wssUrl = wssUrl;
+      this.wssPostUrl = wssPostUrl;
       this.channelToken = channelToken;
       this.offerSdp = offerSdp;
+      this.iceCandidates = iceCandidates;
       if (channelToken == null || channelToken.length() == 0) {
         this.websocketSignaling = true;
       } else {
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
index 85e81e7..a83e669 100644
--- a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
@@ -71,7 +71,6 @@
     implements AppRTCClient.SignalingEvents,
       PeerConnectionClient.PeerConnectionEvents {
   private static final String TAG = "AppRTCClient";
-  private final boolean USE_WEBSOCKETS = false;
   private PeerConnectionClient pc;
   private AppRTCClient appRtcClient;
   private SignalingParameters signalingParameters;
@@ -87,8 +86,9 @@
       new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   private TextView hudView;
   private TextView encoderStatView;
-  private TextView roomName;
+  private TextView roomNameView;
   private ImageButton videoScalingButton;
+  private String roomName;
   private boolean commandLineRun;
   private int runTimeMs;
   private int startBitrate;
@@ -119,7 +119,7 @@
     rootView = findViewById(android.R.id.content);
     encoderStatView = (TextView)findViewById(R.id.encoder_stat);
     menuBar = findViewById(R.id.menubar_fragment);
-    roomName = (TextView) findViewById(R.id.room_name);
+    roomNameView = (TextView) findViewById(R.id.room_name);
     videoView = (GLSurfaceView) findViewById(R.id.glview);
 
     VideoRendererGui.setView(videoView);
@@ -135,11 +135,11 @@
                     ? View.INVISIBLE : View.VISIBLE;
             encoderStatView.setVisibility(visibility);
             menuBar.setVisibility(visibility);
-            roomName.setVisibility(visibility);
+            roomNameView.setVisibility(visibility);
             if (visibility == View.VISIBLE) {
               encoderStatView.bringToFront();
               menuBar.bringToFront();
-              roomName.bringToFront();
+              roomNameView.bringToFront();
               rootView.invalidate();
             }
           }
@@ -206,6 +206,7 @@
 
     final Intent intent = getIntent();
     Uri url = intent.getData();
+    roomName = intent.getStringExtra(ConnectActivity.EXTRA_ROOMNAME);
     boolean loopback = intent.getBooleanExtra(
         ConnectActivity.EXTRA_LOOPBACK, false);
     commandLineRun = intent.getBooleanExtra(
@@ -213,21 +214,22 @@
     runTimeMs = intent.getIntExtra(ConnectActivity.EXTRA_RUNTIME, 0);
     startBitrate = intent.getIntExtra(ConnectActivity.EXTRA_BITRATE, 0);
     hwCodec = intent.getBooleanExtra(ConnectActivity.EXTRA_HWCODEC, true);
+    boolean useWebsocket = intent.getBooleanExtra(
+        ConnectActivity.EXTRA_WEBSOCKET, false);
 
     if (url != null) {
-      String room = url.getQueryParameter("r");
-      if (loopback || (room != null && !room.equals(""))) {
+      if (loopback || (roomName != null && !roomName.equals(""))) {
         logAndToast(getString(R.string.connecting_to, url));
-        if (USE_WEBSOCKETS) {
+        if (useWebsocket) {
           appRtcClient = new WebSocketRTCClient(this);
         } else {
           appRtcClient = new GAERTCClient(this, this);
         }
         appRtcClient.connectToRoom(url.toString(), loopback);
         if (loopback) {
-          roomName.setText("loopback");
+          roomNameView.setText("loopback");
         } else {
-          roomName.setText(room);
+          roomNameView.setText(roomName);
         }
         if (commandLineRun && runTimeMs > 0) {
           // For command line execution run connection for <runTimeMs> and exit.
diff --git a/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java b/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java
index 9fe6f51..b8e0e6a 100644
--- a/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java
@@ -51,6 +51,7 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.Random;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -62,16 +63,18 @@
  */
 public class ConnectActivity extends Activity {
 
+  public static final String EXTRA_ROOMNAME = "org.appspot.apprtc.ROOMNAME";
   public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";
   public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";
   public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";
   public static final String EXTRA_BITRATE = "org.appspot.apprtc.BITRATE";
   public static final String EXTRA_HWCODEC = "org.appspot.apprtc.HWCODEC";
-  private static final String TAG = "ConnectActivity";
-  private final boolean USE_WEBSOCKETS = false;
+  public static final String EXTRA_WEBSOCKET = "org.appspot.apprtc.WEBSOCKET";
+  private static final String TAG = "ConnectRTCClient";
   private final String APPRTC_SERVER = "https://apprtc.appspot.com";
-  private final String APPRTC_WS_SERVER = "https://8-dot-apprtc.appspot.com";
+  private final String APPRTC_WS_SERVER = "https://3-dot-apprtc.appspot.com";
   private final int CONNECTION_REQUEST = 1;
+  private static boolean commandLineRun = false;
 
   private ImageButton addRoomButton;
   private ImageButton removeRoomButton;
@@ -86,12 +89,11 @@
   private String keyprefBitrateValue;
   private String keyprefHwCodec;
   private String keyprefCpuUsageDetection;
+  private String keyprefWebsocketSignaling;
   private String keyprefRoom;
   private String keyprefRoomList;
   private ArrayList<String> roomList;
   private ArrayAdapter<String> adapter;
-  private boolean commandLineRun;
-  private int runTimeMs;
 
   @Override
   public void onCreate(Bundle savedInstanceState) {
@@ -106,6 +108,7 @@
     keyprefBitrateValue = getString(R.string.pref_startbitratevalue_key);
     keyprefHwCodec = getString(R.string.pref_hwcodec_key);
     keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key);
+    keyprefWebsocketSignaling = getString(R.string.pref_signaling_key);
     keyprefRoom = getString(R.string.pref_room_key);
     keyprefRoomList = getString(R.string.pref_room_list_key);
 
@@ -140,17 +143,15 @@
     connectLoopbackButton.setOnClickListener(connectListener);
 
     // If an implicit VIEW intent is launching the app, go directly to that URL.
-    commandLineRun = false;
     final Intent intent = getIntent();
-    if ("android.intent.action.VIEW".equals(intent.getAction())) {
+    if ("android.intent.action.VIEW".equals(intent.getAction()) &&
+        !commandLineRun) {
       commandLineRun = true;
       boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
-      runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
-      String url = intent.getData().toString();
-      if (loopback && !url.contains("debug=loopback")) {
-        url += "/?debug=loopback";
-      }
-      connectToRoom(url, loopback, 0, true);
+      int runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
+      String room = sharedPref.getString(keyprefRoom, "");
+      roomEditText.setText(room);
+      connectToRoom(loopback, runTimeMs);
       return;
     }
   }
@@ -227,108 +228,141 @@
       if (view.getId() == R.id.connect_loopback_button) {
         loopback = true;
       }
-      String url;
-      if (USE_WEBSOCKETS) {
-        url = APPRTC_WS_SERVER;
-      } else {
-        url = APPRTC_SERVER;
-      }
-      if (loopback) {
-        url += "/?debug=loopback";
-      } else {
-        String roomName = getSelectedItem();
-        if (roomName == null) {
-          roomName = roomEditText.getText().toString();
-        }
-        url += "/?r=" + roomName;
-      }
-      // Check HW codec flag.
-      boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec,
-          Boolean.valueOf(getString(R.string.pref_hwcodec_default)));
-      // Add video resolution constraints.
-      String parametersResolution = null;
-      String parametersFps = null;
-      String resolution = sharedPref.getString(keyprefResolution,
-          getString(R.string.pref_resolution_default));
-      String[] dimensions = resolution.split("[ x]+");
-      if (dimensions.length == 2) {
-        try {
-          int maxWidth = Integer.parseInt(dimensions[0]);
-          int maxHeight = Integer.parseInt(dimensions[1]);
-          if (maxWidth > 0 && maxHeight > 0) {
-            parametersResolution = "minHeight=" + maxHeight + ",maxHeight=" +
-                maxHeight + ",minWidth=" + maxWidth + ",maxWidth=" + maxWidth;
-          }
-        } catch (NumberFormatException e) {
-          Log.e(TAG, "Wrong video resolution setting: " + resolution);
-        }
-      }
-      // Add camera fps constraints.
-      String fps = sharedPref.getString(keyprefFps,
-          getString(R.string.pref_fps_default));
-      String[] fpsValues = fps.split("[ x]+");
-      if (fpsValues.length == 2) {
-        try {
-          int cameraFps = Integer.parseInt(fpsValues[0]);
-          if (cameraFps > 0) {
-            parametersFps = "minFrameRate=" + cameraFps +
-                ",maxFrameRate=" + cameraFps;
-          }
-        } catch (NumberFormatException e) {
-          Log.e(TAG, "Wrong camera fps setting: " + fps);
-        }
-      }
-      // Modify connection URL.
-      if (parametersResolution != null || parametersFps != null) {
-        url += "&video=";
-        if (parametersResolution != null) {
-          url += parametersResolution;
-          if (parametersFps != null) {
-            url += ",";
-          }
-        }
-        if (parametersFps != null) {
-          url += parametersFps;
-        }
-      } else {
-        if (hwCodec && MediaCodecVideoEncoder.isPlatformSupported()) {
-          url += "&hd=true";
-        }
-      }
-      // Get start bitrate.
-      int startBitrate = 0;
-      String bitrateTypeDefault = getString(R.string.pref_startbitrate_default);
-      String bitrateType = sharedPref.getString(
-          keyprefBitrateType, bitrateTypeDefault);
-      if (!bitrateType.equals(bitrateTypeDefault)) {
-        String bitrateValue = sharedPref.getString(keyprefBitrateValue,
-            getString(R.string.pref_startbitratevalue_default));
-        startBitrate = Integer.parseInt(bitrateValue);
-      }
-      // Test if CpuOveruseDetection should be disabled. By default is on.
-      boolean cpuOveruseDetection = sharedPref.getBoolean(
-          keyprefCpuUsageDetection,
-          Boolean.valueOf(
-              getString(R.string.pref_cpu_usage_detection_default)));
-      if (!cpuOveruseDetection) {
-        url += "&googCpuOveruseDetection=false";
-      }
-      // TODO(kjellander): Add support for custom parameters to the URL.
-      connectToRoom(url, loopback, startBitrate, hwCodec);
+      commandLineRun = false;
+      connectToRoom(loopback, 0);
     }
   };
 
-  private void connectToRoom(
-      String roomUrl, boolean loopback, int startBitrate, boolean hwCodec) {
-    if (validateUrl(roomUrl)) {
-      Uri url = Uri.parse(roomUrl);
+  private String appendQueryParameter(String url, String parameter) {
+    String newUrl = url;
+    if (newUrl.contains("?")) {
+      newUrl += "&" + parameter;
+    } else {
+      newUrl += "?" + parameter;
+    }
+    return newUrl;
+  }
+
+  private void connectToRoom(boolean loopback, int runTimeMs) {
+    // Check webSocket signaling flag.
+    boolean useWebsocket = sharedPref.getBoolean(keyprefWebsocketSignaling,
+        Boolean.valueOf(getString(R.string.pref_signaling_default)));
+
+    // Get room name (random for loopback).
+    String roomName;
+    if (loopback) {
+      roomName = Integer.toString((new Random()).nextInt(100000000));
+    } else {
+      roomName = getSelectedItem();
+      if (roomName == null) {
+        roomName = roomEditText.getText().toString();
+      }
+    }
+
+    // Build room URL.
+    String url;
+    if (useWebsocket) {
+      url = APPRTC_WS_SERVER;
+      url += "/register/" + roomName;
+    } else {
+      url = APPRTC_SERVER;
+      url = appendQueryParameter(url, "r=" + roomName);
+      if (loopback) {
+        url = appendQueryParameter(url, "debug=loopback");
+      }
+    }
+
+    // Check HW codec flag.
+    boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec,
+        Boolean.valueOf(getString(R.string.pref_hwcodec_default)));
+
+    // Add video resolution constraints.
+    String parametersResolution = null;
+    String parametersFps = null;
+    String resolution = sharedPref.getString(keyprefResolution,
+        getString(R.string.pref_resolution_default));
+    String[] dimensions = resolution.split("[ x]+");
+    if (dimensions.length == 2) {
+      try {
+        int maxWidth = Integer.parseInt(dimensions[0]);
+        int maxHeight = Integer.parseInt(dimensions[1]);
+        if (maxWidth > 0 && maxHeight > 0) {
+          parametersResolution = "minHeight=" + maxHeight + ",maxHeight=" +
+              maxHeight + ",minWidth=" + maxWidth + ",maxWidth=" + maxWidth;
+        }
+      } catch (NumberFormatException e) {
+        Log.e(TAG, "Wrong video resolution setting: " + resolution);
+      }
+    }
+
+    // Add camera fps constraints.
+    String fps = sharedPref.getString(keyprefFps,
+        getString(R.string.pref_fps_default));
+    String[] fpsValues = fps.split("[ x]+");
+    if (fpsValues.length == 2) {
+      try {
+        int cameraFps = Integer.parseInt(fpsValues[0]);
+        if (cameraFps > 0) {
+          parametersFps = "minFrameRate=" + cameraFps +
+              ",maxFrameRate=" + cameraFps;
+        }
+      } catch (NumberFormatException e) {
+        Log.e(TAG, "Wrong camera fps setting: " + fps);
+      }
+    }
+
+    // Modify connection URL.
+    if (parametersResolution != null || parametersFps != null) {
+      String urlVideoParameters = "video=";
+      if (parametersResolution != null) {
+        urlVideoParameters += parametersResolution;
+        if (parametersFps != null) {
+          urlVideoParameters += ",";
+        }
+      }
+      if (parametersFps != null) {
+        urlVideoParameters += parametersFps;
+      }
+      url = appendQueryParameter(url, urlVideoParameters);
+    } else {
+      if (hwCodec && MediaCodecVideoEncoder.isPlatformSupported()) {
+        url = appendQueryParameter(url, "hd=true");
+      }
+    }
+
+    // Get start bitrate.
+    int startBitrate = 0;
+    String bitrateTypeDefault = getString(R.string.pref_startbitrate_default);
+    String bitrateType = sharedPref.getString(
+        keyprefBitrateType, bitrateTypeDefault);
+    if (!bitrateType.equals(bitrateTypeDefault)) {
+      String bitrateValue = sharedPref.getString(keyprefBitrateValue,
+          getString(R.string.pref_startbitratevalue_default));
+      startBitrate = Integer.parseInt(bitrateValue);
+    }
+
+    // Test if CpuOveruseDetection should be disabled. By default is on.
+    boolean cpuOveruseDetection = sharedPref.getBoolean(
+        keyprefCpuUsageDetection,
+        Boolean.valueOf(
+            getString(R.string.pref_cpu_usage_detection_default)));
+    if (!cpuOveruseDetection) {
+      url = appendQueryParameter(url, "googCpuOveruseDetection=false");
+    }
+
+    // Start AppRTCDemo activity.
+    Log.d(TAG, "Connecting to room " + roomName + " at URL " + url);
+    if (validateUrl(url)) {
+      Uri uri = Uri.parse(url);
       Intent intent = new Intent(this, AppRTCDemoActivity.class);
-      intent.setData(url);
+      intent.setData(uri);
+      intent.putExtra(EXTRA_ROOMNAME, roomName);
       intent.putExtra(EXTRA_LOOPBACK, loopback);
       intent.putExtra(EXTRA_CMDLINE, commandLineRun);
       intent.putExtra(EXTRA_RUNTIME, runTimeMs);
       intent.putExtra(EXTRA_BITRATE, startBitrate);
       intent.putExtra(EXTRA_HWCODEC, hwCodec);
+      intent.putExtra(EXTRA_WEBSOCKET, useWebsocket);
       startActivityForResult(intent, CONNECTION_REQUEST);
     }
   }
diff --git a/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java b/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java
index fb0f9f0..c1d7a51 100644
--- a/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java
@@ -76,7 +76,7 @@
    */
   @Override
   public void connectToRoom(String url, boolean loopback) {
-    fetcher = new RoomParametersFetcher(this, loopback);
+    fetcher = new RoomParametersFetcher(this, false, loopback);
     fetcher.execute(url);
   }
 
@@ -246,12 +246,12 @@
           }
           try {
             JSONObject json = new JSONObject(msg);
-            String type = (String) json.get("type");
+            String type = json.getString("type");
             if (type.equals("candidate")) {
               IceCandidate candidate = new IceCandidate(
-                  (String) json.get("id"),
+                  json.getString("id"),
                   json.getInt("label"),
-                  (String) json.get("candidate"));
+                  json.getString("candidate"));
               events.onRemoteIceCandidate(candidate);
             } else if (type.equals("answer") || type.equals("offer")) {
               SessionDescription sdp = new SessionDescription(
diff --git a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
index 9d53049..da94cdb 100644
--- a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
@@ -283,6 +283,7 @@
   }
 
   private void reportError(final String errorMessage) {
+    Log.e(TAG, "Peerconnection error: " + errorMessage);
     activity.runOnUiThread(new Runnable() {
       public void run() {
         events.onPeerConnectionError(errorMessage);
diff --git a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
index f49a873..bdeb9fb 100644
--- a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
+++ b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
@@ -33,12 +33,14 @@
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.webrtc.IceCandidate;
 import org.webrtc.MediaConstraints;
 import org.webrtc.PeerConnection;
+import org.webrtc.SessionDescription;
 
-import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.LinkedList;
@@ -51,6 +53,7 @@
   private static final String TAG = "RoomRTCClient";
   private Exception exception = null;
   private RoomParametersFetcherEvents events = null;
+  private boolean useNewSignaling;
   private boolean loopback;
 
   /**
@@ -69,10 +72,11 @@
     public void onSignalingParametersError(final String description);
   }
 
-  public RoomParametersFetcher(
-      RoomParametersFetcherEvents events, boolean loopback) {
+  public RoomParametersFetcher(RoomParametersFetcherEvents events,
+      boolean useNewSignaling, boolean loopback) {
     super();
     this.events = events;
+    this.useNewSignaling = useNewSignaling;
     this.loopback = loopback;
   }
 
@@ -115,45 +119,92 @@
   // Fetches |url| and fishes the signaling parameters out of the JSON.
   private SignalingParameters getParametersForRoomUrl(String url)
       throws IOException, JSONException {
-    url = url + "&t=json";
+    if (!useNewSignaling) {
+      if (url.contains("?")) {
+        url += "&t=json";
+      } else {
+        url += "?t=json";
+      }
+    }
     Log.d(TAG, "Connecting to room: " + url);
-    InputStream responseStream = new BufferedInputStream(
-        (new URL(url)).openConnection().getInputStream());
+    HttpURLConnection connection =
+        (HttpURLConnection) new URL(url).openConnection();
+    if (useNewSignaling) {
+      connection.setDoOutput(true);
+      connection.setRequestMethod("POST");
+    } else {
+      connection.setRequestMethod("GET");
+    }
+    connection.setDoInput(true);
+
+    InputStream responseStream = connection.getInputStream();
     String response = drainStream(responseStream);
+    responseStream.close();
     Log.d(TAG, "Room response: " + response);
     JSONObject roomJson = new JSONObject(response);
 
-    if (roomJson.has("error")) {
-      JSONArray errors = roomJson.getJSONArray("error_messages");
-      throw new IOException(errors.toString());
-    }
-
-    String roomId = roomJson.getString("room_key");
-    String clientId = roomJson.getString("me");
-    Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId);
-    String channelToken = roomJson.optString("token");
-    String offerSdp = roomJson.optString("offer");
-    if (offerSdp != null && offerSdp.length() > 0) {
-      JSONObject offerJson = new JSONObject(offerSdp);
-      offerSdp = offerJson.getString("sdp");
-      Log.d(TAG, "SDP type: " + offerJson.getString("type"));
-    } else {
-      offerSdp = null;
-    }
-
-    String roomUrl = url.substring(0, url.indexOf('?'));
-    Log.d(TAG, "Room url: " + roomUrl);
-
+    String roomId;
+    String clientId;
+    String roomUrl;
+    String channelToken = "";
+    String wssUrl = "";
+    String wssPostUrl = "";
     boolean initiator;
-    if (loopback) {
-      // In loopback mode caller should always be call initiator.
-      // TODO(glaznev): remove this once 8-dot-apprtc server will set initiator
-      // flag to true for loopback calls.
-      initiator = true;
+    LinkedList<IceCandidate> iceCandidates = null;
+    SessionDescription offerSdp = null;
+
+    if (useNewSignaling) {
+      String result = roomJson.getString("result");
+      if (!result.equals("SUCCESS")) {
+        throw new JSONException(result);
+      }
+      response = roomJson.getString("params");
+      roomJson = new JSONObject(response);
+      roomId = roomJson.getString("room_id");
+      clientId = roomJson.getString("client_id");
+      wssUrl = roomJson.getString("wss_url");
+      wssPostUrl = roomJson.getString("wss_post_url");
+      initiator = (roomJson.getBoolean("is_initiator"));
+      roomUrl = url.substring(0, url.indexOf("/register"));
+      if (!initiator) {
+        iceCandidates = new LinkedList<IceCandidate>();
+        String messagesString = roomJson.getString("messages");
+        JSONArray messages = new JSONArray(messagesString);
+        for (int i = 0; i < messages.length(); ++i) {
+          String messageString = messages.getString(i);
+          JSONObject message = new JSONObject(messageString);
+          String messageType = message.getString("type");
+          Log.d(TAG, "GAE->C #" + i + " : " + messageString);
+          if (messageType.equals("offer")) {
+            offerSdp = new SessionDescription(
+                SessionDescription.Type.fromCanonicalForm(messageType),
+                message.getString("sdp"));
+          } else if (messageType.equals("candidate")) {
+            IceCandidate candidate = new IceCandidate(
+                message.getString("id"),
+                message.getInt("label"),
+                message.getString("candidate"));
+            iceCandidates.add(candidate);
+          } else {
+            Log.e(TAG, "Unknown message: " + messageString);
+          }
+        }
+      }
     } else {
-      initiator = roomJson.getInt("initiator") == 1;
+      if (roomJson.has("error")) {
+        JSONArray errors = roomJson.getJSONArray("error_messages");
+        throw new IOException(errors.toString());
+      }
+      roomId = roomJson.getString("room_key");
+      clientId = roomJson.getString("me");
+      channelToken = roomJson.optString("token");
+      initiator = (roomJson.getInt("initiator") == 1);
+      roomUrl = url.substring(0, url.indexOf('?'));
     }
+
+    Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId);
     Log.d(TAG, "Initiator: " + initiator);
+    Log.d(TAG, "Room url: " + roomUrl);
 
     LinkedList<PeerConnection.IceServer> iceServers =
         iceServersFromPCConfigJSON(roomJson.getString("pc_config"));
@@ -191,7 +242,8 @@
         iceServers, initiator,
         pcConstraints, videoConstraints, audioConstraints,
         roomUrl, roomId, clientId,
-        channelToken, offerSdp);
+        wssUrl, wssPostUrl, channelToken,
+        offerSdp, iceCandidates);
   }
 
   // Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by
diff --git a/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java b/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java
index 4ca81ef..0bed403 100644
--- a/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java
@@ -42,6 +42,7 @@
   private String keyprefStartBitrateValue;
   private String keyprefHwCodec;
   private String keyprefCpuUsageDetection;
+  private String keyprefSignaling;
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
@@ -52,6 +53,7 @@
     keyprefStartBitrateValue = getString(R.string.pref_startbitratevalue_key);
     keyprefHwCodec = getString(R.string.pref_hwcodec_key);
     keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key);
+    keyprefSignaling = getString(R.string.pref_signaling_key);
 
     // Display the fragment as the main content.
     settingsFragment = new SettingsFragment();
@@ -74,6 +76,7 @@
     setBitrateEnable(sharedPreferences);
     updateSummaryB(sharedPreferences, keyprefHwCodec);
     updateSummaryB(sharedPreferences, keyprefCpuUsageDetection);
+    updateSummaryB(sharedPreferences, keyprefSignaling);
   }
 
   @Override
@@ -93,7 +96,7 @@
     } else if (key.equals(keyprefStartBitrateValue)) {
       updateSummaryBitrate(sharedPreferences, key);
     } else if (key.equals(keyprefCpuUsageDetection) ||
-        key.equals(keyprefHwCodec)) {
+        key.equals(keyprefHwCodec) || key.equals(keyprefSignaling)) {
       updateSummaryB(sharedPreferences, key);
     }
     if (key.equals(keyprefStartBitrateType)) {
diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
index 170c807..33aa63b 100644
--- a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
@@ -140,7 +140,7 @@
       json.put("cmd", "register");
       json.put("roomid", roomID);
       json.put("clientid", clientID);
-      Log.d(TAG, "WS SEND: " + json.toString());
+      Log.d(TAG, "C->WSS: " + json.toString());
       ws.sendTextMessage(json.toString());
       state = WebSocketConnectionState.REGISTERED;
       // Send any previously accumulated messages.
@@ -176,7 +176,7 @@
           json.put("cmd", "send");
           json.put("msg", message);
           message = json.toString();
-          Log.d(TAG, "WS SEND: " + message);
+          Log.d(TAG, "C->WSS: " + message);
           ws.sendTextMessage(message);
         } catch (JSONException e) {
           reportError("WebSocket send JSON error: " + e.getMessage());
@@ -279,9 +279,9 @@
       try {
         for (WsHttpMessage wsHttpMessage : wsHttpQueue) {
           // Send POST request.
-          Log.d(TAG, "WS " + wsHttpMessage.method + " : " +
+          String postUrl = postServerUrl + "/" + roomID + "/" + clientID;
+          Log.d(TAG, "WS " + wsHttpMessage.method + " : " + postUrl + " : " +
               wsHttpMessage.message);
-          String postUrl = postServerUrl + roomID + "/" + clientID;
           HttpURLConnection connection =
               (HttpURLConnection) new URL(postUrl).openConnection();
           connection.setDoOutput(true);
@@ -333,7 +333,7 @@
 
     @Override
     public void onTextMessage(String payload) {
-      Log.d(TAG, "WS GET: " + payload);
+      Log.d(TAG, "WSS->C: " + payload);
       final String message = payload;
       uiHandler.post(new Runnable() {
         public void run() {
diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java
index ef6ef28..4bfb418 100644
--- a/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java
@@ -32,9 +32,12 @@
 import android.util.Log;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.LinkedList;
+import java.util.Scanner;
 
 import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
 import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents;
@@ -57,31 +60,25 @@
 public class WebSocketRTCClient implements AppRTCClient,
     RoomParametersFetcherEvents, WebSocketChannelEvents {
   private static final String TAG = "WSRTCClient";
-  private static final String WSS_SERVER =
-      "wss://apprtc-ws.webrtc.org:8089/ws";
-  // TODO(glaznev): remove this hard-coded URL and instead get WebSocket http
-  // server URL from room response once it will be supported by 8-dot-apprtc.
-  private static final String WSS_POST_URL =
-      "https://apprtc-ws.webrtc.org:8089/";
 
   private enum ConnectionState {
     NEW, CONNECTED, CLOSED, ERROR
   };
   private final Handler uiHandler;
   private boolean loopback;
+  private boolean initiator;
   private SignalingEvents events;
-  private SignalingParameters signalingParameters;
   private WebSocketChannelClient wsClient;
   private RoomParametersFetcher fetcher;
   private ConnectionState roomState;
-  private LinkedList<GAEMessage> gaePostQueue;
+  private LinkedList<PostMessage> postQueue;
   private String postMessageUrl;
   private String byeMessageUrl;
 
   public WebSocketRTCClient(SignalingEvents events) {
     this.events = events;
     uiHandler = new Handler(Looper.getMainLooper());
-    gaePostQueue = new LinkedList<GAEMessage>();
+    postQueue = new LinkedList<PostMessage>();
   }
 
   // --------------------------------------------------------------------
@@ -90,31 +87,38 @@
   @Override
   public void onSignalingParametersReady(final SignalingParameters params) {
     Log.d(TAG, "Room connection completed.");
-    if (!loopback && !params.initiator && params.offerSdp == null) {
-      reportError("Offer SDP is not available.");
-      return;
-    }
-    if (loopback && params.offerSdp != null) {
+    if (loopback && (!params.initiator || params.offerSdp != null)) {
       reportError("Loopback room is busy.");
       return;
     }
-    signalingParameters = params;
-    postMessageUrl = params.roomUrl + "message?r=" +
-        params.roomId + "&u=" + params.clientId;
-    byeMessageUrl = params.roomUrl + "bye/" +
+    if (!loopback && !params.initiator && params.offerSdp == null) {
+      Log.w(TAG, "No offer SDP in room response.");
+    }
+    initiator = params.initiator;
+    postMessageUrl = params.roomUrl + "/message/" +
+        params.roomId + "/" + params.clientId;
+    byeMessageUrl = params.roomUrl + "/bye/" +
         params.roomId + "/" + params.clientId;
     roomState = ConnectionState.CONNECTED;
-    wsClient.setClientParameters(
-        signalingParameters.roomId, signalingParameters.clientId);
-    wsClient.register();
-    events.onConnectedToRoom(signalingParameters);
+
+    // Connect to WebSocket server.
+    wsClient.connect(params.wssUrl, params.wssPostUrl);
+    wsClient.setClientParameters(params.roomId, params.clientId);
+
+    // Fire connection and signaling parameters events.
+    events.onConnectedToRoom(params);
     events.onChannelOpen();
-    if (!signalingParameters.initiator) {
-      // For call receiver get sdp offer from room parameters.
-      SessionDescription sdp = new SessionDescription(
-          SessionDescription.Type.fromCanonicalForm("offer"),
-          signalingParameters.offerSdp);
-      events.onRemoteDescription(sdp);
+    if (!params.initiator) {
+      // For call receiver get sdp offer and ice candidates
+      // from room parameters.
+      if (params.offerSdp != null) {
+        events.onRemoteDescription(params.offerSdp);
+      }
+      if (params.iceCandidates != null) {
+        for (IceCandidate iceCandidate : params.iceCandidates) {
+          events.onRemoteIceCandidate(iceCandidate);
+        }
+      }
     }
   }
 
@@ -128,10 +132,8 @@
   // All events are called on UI thread.
   @Override
   public void onWebSocketOpen() {
-    Log.d(TAG, "Websocket connection completed.");
-    if (roomState == ConnectionState.CONNECTED) {
-      wsClient.register();
-    }
+    Log.d(TAG, "Websocket connection completed. Registering...");
+    wsClient.register();
   }
 
   @Override
@@ -149,15 +151,28 @@
         String type = json.optString("type");
         if (type.equals("candidate")) {
           IceCandidate candidate = new IceCandidate(
-              (String) json.get("id"),
+              json.getString("id"),
               json.getInt("label"),
-              (String) json.get("candidate"));
+              json.getString("candidate"));
           events.onRemoteIceCandidate(candidate);
         } else if (type.equals("answer")) {
-          SessionDescription sdp = new SessionDescription(
-              SessionDescription.Type.fromCanonicalForm(type),
-              (String)json.get("sdp"));
-          events.onRemoteDescription(sdp);
+          if (initiator) {
+            SessionDescription sdp = new SessionDescription(
+                SessionDescription.Type.fromCanonicalForm(type),
+                json.getString("sdp"));
+            events.onRemoteDescription(sdp);
+          } else {
+            reportError("Received answer for call initiator: " + msg);
+          }
+        } else if (type.equals("offer")) {
+          if (!initiator) {
+            SessionDescription sdp = new SessionDescription(
+                SessionDescription.Type.fromCanonicalForm(type),
+                json.getString("sdp"));
+            events.onRemoteDescription(sdp);
+          } else {
+            reportError("Received offer for call receiver: " + msg);
+          }
         } else if (type.equals("bye")) {
           events.onChannelClose();
         } else {
@@ -189,32 +204,28 @@
   // --------------------------------------------------------------------
   // AppRTCClient interface implementation.
   // Asynchronously connect to an AppRTC room URL, e.g.
-  // https://apprtc.appspot.com/?r=NNN, retrieve room parameters
+  // https://apprtc.appspot.com/register/<room>, retrieve room parameters
   // and connect to WebSocket server.
   @Override
   public void connectToRoom(String url, boolean loopback) {
     this.loopback = loopback;
+    // Create WebSocket client.
+    wsClient = new WebSocketChannelClient(this);
     // Get room parameters.
     roomState = ConnectionState.NEW;
-    fetcher = new RoomParametersFetcher(this, loopback);
+    fetcher = new RoomParametersFetcher(this, true, loopback);
     fetcher.execute(url);
-    // Connect to WebSocket server.
-    wsClient = new WebSocketChannelClient(this);
-    if (!loopback) {
-      wsClient.connect(WSS_SERVER, WSS_POST_URL);
-    }
   }
 
   @Override
   public void disconnect() {
     Log.d(TAG, "Disconnect. Room state: " + roomState);
-    wsClient.disconnect();
     if (roomState == ConnectionState.CONNECTED) {
       Log.d(TAG, "Closing room.");
-      // TODO(glaznev): Remove json bye message sending once new bye will
-      // be supported on 8-dot.
-      //sendGAEMessage(byeMessageUrl, "");
-      sendGAEMessage(postMessageUrl, "{\"type\": \"bye\"}");
+      sendGAEMessage(byeMessageUrl, "");
+    }
+    if (wsClient != null) {
+      wsClient.disconnect();
     }
   }
 
@@ -225,17 +236,16 @@
   // we might want to filter elsewhere.
   @Override
   public void sendOfferSdp(final SessionDescription sdp) {
+    JSONObject json = new JSONObject();
+    jsonPut(json, "sdp", sdp.description);
+    jsonPut(json, "type", "offer");
+    sendGAEMessage(postMessageUrl, json.toString());
     if (loopback) {
-      // In loopback mode rename this offer to answer and send it back.
+      // In loopback mode rename this offer to answer and route it back.
       SessionDescription sdpAnswer = new SessionDescription(
           SessionDescription.Type.fromCanonicalForm("answer"),
           sdp.description);
       events.onRemoteDescription(sdpAnswer);
-    } else {
-      JSONObject json = new JSONObject();
-      jsonPut(json, "sdp", sdp.description);
-      jsonPut(json, "type", "offer");
-      sendGAEMessage(postMessageUrl, json.toString());
     }
   }
 
@@ -258,18 +268,27 @@
   // Send Ice candidate to the other participant.
   @Override
   public void sendLocalIceCandidate(final IceCandidate candidate) {
-    if (loopback) {
-      events.onRemoteIceCandidate(candidate);
+    JSONObject json = new JSONObject();
+    jsonPut(json, "type", "candidate");
+    jsonPut(json, "label", candidate.sdpMLineIndex);
+    jsonPut(json, "id", candidate.sdpMid);
+    jsonPut(json, "candidate", candidate.sdp);
+    if (initiator) {
+      // Call initiator sends ice candidates to GAE server.
+      if (roomState != ConnectionState.CONNECTED) {
+        reportError("Sending ICE candidate in non connected state.");
+        return;
+      }
+      sendGAEMessage(postMessageUrl, json.toString());
+      if (loopback) {
+        events.onRemoteIceCandidate(candidate);
+      }
     } else {
+      // Call receiver sends ice candidates to websocket server.
       if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
         reportError("Sending ICE candidate in non registered state.");
         return;
       }
-      JSONObject json = new JSONObject();
-      jsonPut(json, "type", "candidate");
-      jsonPut(json, "label", candidate.sdpMLineIndex);
-      jsonPut(json, "id", candidate.sdpMid);
-      jsonPut(json, "candidate", candidate.sdp);
       wsClient.send(json.toString());
     }
   }
@@ -297,8 +316,8 @@
     }
   }
 
-  private class GAEMessage {
-    GAEMessage(String postUrl, String message) {
+  private class PostMessage {
+    PostMessage(String postUrl, String message) {
       this.postUrl = postUrl;
       this.message = message;
     }
@@ -308,8 +327,8 @@
 
   // Queue a message for sending to the room  and send it if already connected.
   private synchronized void sendGAEMessage(String url, String message) {
-    synchronized (gaePostQueue) {
-      gaePostQueue.add(new GAEMessage(url, message));
+    synchronized (postQueue) {
+      postQueue.add(new PostMessage(url, message));
     }
     (new AsyncTask<Void, Void, Void>() {
       public Void doInBackground(Void... unused) {
@@ -321,37 +340,58 @@
 
   // Send all queued messages if connected to the room.
   private void maybeDrainGAEPostQueue() {
-    synchronized (gaePostQueue) {
-      if (roomState != ConnectionState.CONNECTED) {
-        return;
+    if (roomState != ConnectionState.CONNECTED) {
+      return;
+    }
+    PostMessage postMessage = null;
+    while (true) {
+      synchronized (postQueue) {
+        postMessage = postQueue.poll();
+      }
+      if (postMessage == null) {
+        break;
       }
       try {
-        for (GAEMessage gaeMessage : gaePostQueue) {
-          Log.d(TAG, "ROOM SEND to " + gaeMessage.postUrl +
-              ". Message: " + gaeMessage.message);
-          // Check if this is 'bye' message and update room connection state.
-          // TODO(glaznev): Uncomment this check and remove check below
-          // once new bye message will be supported by 8-dot.
-          //if (gaeMessage.postUrl.contains("bye")) {
-          //  roomState = ConnectionState.CLOSED;
-          //}
-          JSONObject json = new JSONObject(gaeMessage.message);
-          String type = json.optString("type");
-          if (type != null && type.equals("bye")) {
-            roomState = ConnectionState.CLOSED;
-          }
-          // Send POST request.
-          HttpURLConnection connection =
-              (HttpURLConnection) new URL(gaeMessage.postUrl).openConnection();
-          connection.setDoOutput(true);
-          connection.setRequestProperty(
-              "content-type", "text/plain; charset=utf-8");
-          connection.getOutputStream().write(
-              gaeMessage.message.getBytes("UTF-8"));
-          String replyHeader = connection.getHeaderField(null);
-          if (!replyHeader.startsWith("HTTP/1.1 200 ")) {
-            reportError("Non-200 response to POST: " +
-                connection.getHeaderField(null));
+
+        // Check if this is 'bye' message and update room connection state.
+        if (postMessage.postUrl.contains("bye")) {
+          roomState = ConnectionState.CLOSED;
+          Log.d(TAG, "C->GAE: " + postMessage.postUrl);
+        } else {
+          Log.d(TAG, "C->GAE: " + postMessage.message);
+        }
+
+        // Get connection.
+        HttpURLConnection connection =
+          (HttpURLConnection) new URL(postMessage.postUrl).openConnection();
+        byte[] postData = postMessage.message.getBytes("UTF-8");
+        connection.setUseCaches(false);
+        connection.setDoOutput(true);
+        connection.setDoInput(true);
+        connection.setRequestMethod("POST");
+        connection.setFixedLengthStreamingMode(postData.length);
+        connection.setRequestProperty(
+            "content-type", "text/plain; charset=utf-8");
+
+        // Send POST request.
+        OutputStream outStream = connection.getOutputStream();
+        outStream.write(postData);
+        outStream.close();
+
+        // Get response.
+        int responseCode = connection.getResponseCode();
+        if (responseCode != 200) {
+          reportError("Non-200 response to POST: " +
+              connection.getHeaderField(null));
+        }
+        InputStream responseStream = connection.getInputStream();
+        String response = drainStream(responseStream);
+        responseStream.close();
+        if (roomState != ConnectionState.CLOSED) {
+          JSONObject roomJson = new JSONObject(response);
+          String result = roomJson.getString("result");
+          if (!result.equals("SUCCESS")) {
+            reportError("Room POST error: " + result);
           }
         }
       } catch (IOException e) {
@@ -359,9 +399,13 @@
       } catch (JSONException e) {
         reportError("GAE POST JSON error: " + e.getMessage());
       }
-
-      gaePostQueue.clear();
     }
   }
 
+  // Return the contents of an InputStream as a String.
+  private String drainStream(InputStream in) {
+    Scanner s = new Scanner(in).useDelimiter("\\A");
+    return s.hasNext() ? s.next() : "";
+  }
+
 }