| Index: webrtc/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java
 | 
| diff --git a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java
 | 
| index 44c537031ffd171ca357c8da32588953da88b53e..bd3341f98f3021161e571049e0f961c1605fad0d 100644
 | 
| --- a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java
 | 
| +++ b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java
 | 
| @@ -806,6 +806,156 @@ public class PeerConnectionTest {
 | 
|  
 | 
|    @Test
 | 
|    @MediumTest
 | 
| +  public void testDataChannelOnlySession() throws Exception {
 | 
| +    // Allow loopback interfaces too since our Android devices often don't
 | 
| +    // have those.
 | 
| +    PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
 | 
| +    options.networkIgnoreMask = 0;
 | 
| +    PeerConnectionFactory factory = new PeerConnectionFactory(options);
 | 
| +
 | 
| +    MediaConstraints pcConstraints = new MediaConstraints();
 | 
| +    pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
 | 
| +
 | 
| +    LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
 | 
| +    iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
 | 
| +    iceServers.add(
 | 
| +        new PeerConnection.IceServer("turn:fake.example.com", "fakeUsername", "fakePassword"));
 | 
| +    ObserverExpectations offeringExpectations = new ObserverExpectations("PCTest:offerer");
 | 
| +    PeerConnection offeringPC =
 | 
| +        factory.createPeerConnection(iceServers, pcConstraints, offeringExpectations);
 | 
| +    assertNotNull(offeringPC);
 | 
| +
 | 
| +    ObserverExpectations answeringExpectations = new ObserverExpectations("PCTest:answerer");
 | 
| +    PeerConnection answeringPC =
 | 
| +        factory.createPeerConnection(iceServers, pcConstraints, answeringExpectations);
 | 
| +    assertNotNull(answeringPC);
 | 
| +
 | 
| +    offeringExpectations.expectRenegotiationNeeded();
 | 
| +    DataChannel offeringDC = offeringPC.createDataChannel("offeringDC", new DataChannel.Init());
 | 
| +    assertEquals("offeringDC", offeringDC.label());
 | 
| +
 | 
| +    offeringExpectations.setDataChannel(offeringDC);
 | 
| +    SdpObserverLatch sdpLatch = new SdpObserverLatch();
 | 
| +    offeringPC.createOffer(sdpLatch, new MediaConstraints());
 | 
| +    assertTrue(sdpLatch.await());
 | 
| +    SessionDescription offerSdp = sdpLatch.getSdp();
 | 
| +    assertEquals(offerSdp.type, SessionDescription.Type.OFFER);
 | 
| +    assertFalse(offerSdp.description.isEmpty());
 | 
| +
 | 
| +    sdpLatch = new SdpObserverLatch();
 | 
| +    answeringExpectations.expectSignalingChange(SignalingState.HAVE_REMOTE_OFFER);
 | 
| +    // SCTP DataChannels are announced via OPEN messages over the established
 | 
| +    // connection (not via SDP), so answeringExpectations can only register
 | 
| +    // expecting the channel during ICE, below.
 | 
| +    answeringPC.setRemoteDescription(sdpLatch, offerSdp);
 | 
| +    assertEquals(PeerConnection.SignalingState.STABLE, offeringPC.signalingState());
 | 
| +    assertTrue(sdpLatch.await());
 | 
| +    assertNull(sdpLatch.getSdp());
 | 
| +
 | 
| +    sdpLatch = new SdpObserverLatch();
 | 
| +    answeringPC.createAnswer(sdpLatch, new MediaConstraints());
 | 
| +    assertTrue(sdpLatch.await());
 | 
| +    SessionDescription answerSdp = sdpLatch.getSdp();
 | 
| +    assertEquals(answerSdp.type, SessionDescription.Type.ANSWER);
 | 
| +    assertFalse(answerSdp.description.isEmpty());
 | 
| +
 | 
| +    offeringExpectations.expectIceCandidates(2);
 | 
| +    answeringExpectations.expectIceCandidates(2);
 | 
| +
 | 
| +    offeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE);
 | 
| +    answeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE);
 | 
| +
 | 
| +    sdpLatch = new SdpObserverLatch();
 | 
| +    answeringExpectations.expectSignalingChange(SignalingState.STABLE);
 | 
| +    answeringPC.setLocalDescription(sdpLatch, answerSdp);
 | 
| +    assertTrue(sdpLatch.await());
 | 
| +    assertNull(sdpLatch.getSdp());
 | 
| +
 | 
| +    sdpLatch = new SdpObserverLatch();
 | 
| +    offeringExpectations.expectSignalingChange(SignalingState.HAVE_LOCAL_OFFER);
 | 
| +    offeringPC.setLocalDescription(sdpLatch, offerSdp);
 | 
| +    assertTrue(sdpLatch.await());
 | 
| +    assertNull(sdpLatch.getSdp());
 | 
| +    sdpLatch = new SdpObserverLatch();
 | 
| +    offeringExpectations.expectSignalingChange(SignalingState.STABLE);
 | 
| +
 | 
| +    offeringExpectations.expectIceConnectionChange(IceConnectionState.CHECKING);
 | 
| +    offeringExpectations.expectIceConnectionChange(IceConnectionState.CONNECTED);
 | 
| +    // TODO(bemasc): uncomment once delivery of ICECompleted is reliable
 | 
| +    // (https://code.google.com/p/webrtc/issues/detail?id=3021).
 | 
| +    answeringExpectations.expectIceConnectionChange(IceConnectionState.CHECKING);
 | 
| +    answeringExpectations.expectIceConnectionChange(IceConnectionState.CONNECTED);
 | 
| +
 | 
| +    offeringPC.setRemoteDescription(sdpLatch, answerSdp);
 | 
| +    assertTrue(sdpLatch.await());
 | 
| +    assertNull(sdpLatch.getSdp());
 | 
| +
 | 
| +    assertEquals(offeringPC.getLocalDescription().type, offerSdp.type);
 | 
| +    assertEquals(offeringPC.getRemoteDescription().type, answerSdp.type);
 | 
| +    assertEquals(answeringPC.getLocalDescription().type, answerSdp.type);
 | 
| +    assertEquals(answeringPC.getRemoteDescription().type, offerSdp.type);
 | 
| +
 | 
| +    offeringExpectations.expectStateChange(DataChannel.State.OPEN);
 | 
| +    // See commentary about SCTP DataChannels above for why this is here.
 | 
| +    answeringExpectations.expectDataChannel("offeringDC");
 | 
| +    answeringExpectations.expectStateChange(DataChannel.State.OPEN);
 | 
| +
 | 
| +    // Wait for at least one ice candidate from the offering PC and forward them to the answering
 | 
| +    // PC.
 | 
| +    for (IceCandidate candidate : offeringExpectations.getAtLeastOneIceCandidate()) {
 | 
| +      answeringPC.addIceCandidate(candidate);
 | 
| +    }
 | 
| +
 | 
| +    // Wait for at least one ice candidate from the answering PC and forward them to the offering
 | 
| +    // PC.
 | 
| +    for (IceCandidate candidate : answeringExpectations.getAtLeastOneIceCandidate()) {
 | 
| +      offeringPC.addIceCandidate(candidate);
 | 
| +    }
 | 
| +
 | 
| +    assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
 | 
| +    assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
 | 
| +
 | 
| +    assertEquals(PeerConnection.SignalingState.STABLE, offeringPC.signalingState());
 | 
| +    assertEquals(PeerConnection.SignalingState.STABLE, answeringPC.signalingState());
 | 
| +
 | 
| +    // Test send & receive UTF-8 text.
 | 
| +    answeringExpectations.expectMessage(
 | 
| +        ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false);
 | 
| +    DataChannel.Buffer buffer =
 | 
| +        new DataChannel.Buffer(ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false);
 | 
| +    assertTrue(offeringExpectations.dataChannel.send(buffer));
 | 
| +    assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
 | 
| +
 | 
| +    // Construct this binary message two different ways to ensure no
 | 
| +    // shortcuts are taken.
 | 
| +    ByteBuffer expectedBinaryMessage = ByteBuffer.allocateDirect(5);
 | 
| +    for (byte i = 1; i < 6; ++i) {
 | 
| +      expectedBinaryMessage.put(i);
 | 
| +    }
 | 
| +    expectedBinaryMessage.flip();
 | 
| +    offeringExpectations.expectMessage(expectedBinaryMessage, true);
 | 
| +    assertTrue(answeringExpectations.dataChannel.send(
 | 
| +        new DataChannel.Buffer(ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5}), true)));
 | 
| +    assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS));
 | 
| +
 | 
| +    offeringExpectations.expectStateChange(DataChannel.State.CLOSING);
 | 
| +    answeringExpectations.expectStateChange(DataChannel.State.CLOSING);
 | 
| +    offeringExpectations.expectStateChange(DataChannel.State.CLOSED);
 | 
| +    answeringExpectations.expectStateChange(DataChannel.State.CLOSED);
 | 
| +    answeringExpectations.dataChannel.close();
 | 
| +    offeringExpectations.dataChannel.close();
 | 
| +
 | 
| +    // Free the Java-land objects and collect them.
 | 
| +    shutdownPC(offeringPC, offeringExpectations);
 | 
| +    offeringPC = null;
 | 
| +    shutdownPC(answeringPC, answeringExpectations);
 | 
| +    answeringPC = null;
 | 
| +    factory.dispose();
 | 
| +    System.gc();
 | 
| +  }
 | 
| +
 | 
| +  @Test
 | 
| +  @MediumTest
 | 
|    public void testTrackRemovalAndAddition() throws Exception {
 | 
|      // Allow loopback interfaces too since our Android devices often don't
 | 
|      // have those.
 | 
| 
 |