Add RtcpMuxPolicy support to PeerConnection.

BUG=4611
R=juberti@google.com

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

Cr-Commit-Position: refs/heads/master@{#9251}
This commit is contained in:
Peter Thatcher 2015-05-21 07:48:41 -07:00
parent 02ff9117b5
commit af55ccc054
13 changed files with 314 additions and 31 deletions

View File

@ -93,6 +93,7 @@ ClassReferenceHolder::ClassReferenceHolder(JNIEnv* jni) {
LoadClass(jni, "org/webrtc/MediaStream");
LoadClass(jni, "org/webrtc/MediaStreamTrack$State");
LoadClass(jni, "org/webrtc/PeerConnection$BundlePolicy");
LoadClass(jni, "org/webrtc/PeerConnection$RtcpMuxPolicy");
LoadClass(jni, "org/webrtc/PeerConnection$IceConnectionState");
LoadClass(jni, "org/webrtc/PeerConnection$IceGatheringState");
LoadClass(jni, "org/webrtc/PeerConnection$IceTransportsType");
@ -143,4 +144,3 @@ jclass FindClass(JNIEnv* jni, const char* name) {
}
} // namespace webrtc_jni

View File

@ -1216,6 +1216,22 @@ JavaBundlePolicyToNativeType(JNIEnv* jni, jobject j_bundle_policy) {
return PeerConnectionInterface::kBundlePolicyBalanced;
}
static PeerConnectionInterface::RtcpMuxPolicy
JavaRtcpMuxPolicyToNativeType(JNIEnv* jni, jobject j_rtcp_mux_policy) {
std::string enum_name = GetJavaEnumName(
jni, "org/webrtc/PeerConnection$RtcpMuxPolicy",
j_rtcp_mux_policy);
if (enum_name == "NEGOTIATE")
return PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
if (enum_name == "REQUIRE")
return PeerConnectionInterface::kRtcpMuxPolicyRequire;
CHECK(false) << "Unexpected RtcpMuxPolicy enum_name " << enum_name;
return PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
}
static PeerConnectionInterface::TcpCandidatePolicy
JavaTcpCandidatePolicyToNativeType(
JNIEnv* jni, jobject j_tcp_candidate_policy) {
@ -1292,6 +1308,12 @@ JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)(
jobject j_bundle_policy = GetObjectField(
jni, j_rtc_config, j_bundle_policy_id);
jfieldID j_rtcp_mux_policy_id = GetFieldID(
jni, j_rtc_config_class, "rtcpMuxPolicy",
"Lorg/webrtc/PeerConnection$RtcpMuxPolicy;");
jobject j_rtcp_mux_policy = GetObjectField(
jni, j_rtc_config, j_rtcp_mux_policy_id);
jfieldID j_tcp_candidate_policy_id = GetFieldID(
jni, j_rtc_config_class, "tcpCandidatePolicy",
"Lorg/webrtc/PeerConnection$TcpCandidatePolicy;");
@ -1311,6 +1333,8 @@ JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)(
rtc_config.type =
JavaIceTransportsTypeToNativeType(jni, j_ice_transports_type);
rtc_config.bundle_policy = JavaBundlePolicyToNativeType(jni, j_bundle_policy);
rtc_config.rtcp_mux_policy =
JavaRtcpMuxPolicyToNativeType(jni, j_rtcp_mux_policy);
rtc_config.tcp_candidate_policy =
JavaTcpCandidatePolicyToNativeType(jni, j_tcp_candidate_policy);
JavaIceServersToJsepIceServers(jni, j_ice_servers, &rtc_config.servers);

View File

@ -117,7 +117,11 @@ public class PeerConnection {
BALANCED, MAXBUNDLE, MAXCOMPAT
};
/** Java version of PeerConnectionInterface.BundlePolicy */
/** Java version of PeerConnectionInterface.RtcpMuxPolicy */
public enum RtcpMuxPolicy {
NEGOTIATE, REQUIRE
};
/** Java version of PeerConnectionInterface.TcpCandidatePolicy */
public enum TcpCandidatePolicy {
ENABLED, DISABLED
};
@ -127,12 +131,14 @@ public class PeerConnection {
public IceTransportsType iceTransportsType;
public List<IceServer> iceServers;
public BundlePolicy bundlePolicy;
public RtcpMuxPolicy rtcpMuxPolicy;
public TcpCandidatePolicy tcpCandidatePolicy;
public int audioJitterBufferMaxPackets;
public RTCConfiguration(List<IceServer> iceServers) {
iceTransportsType = IceTransportsType.ALL;
bundlePolicy = BundlePolicy.BALANCED;
rtcpMuxPolicy = RtcpMuxPolicy.NEGOTIATE;
tcpCandidatePolicy = TcpCandidatePolicy.ENABLED;
this.iceServers = iceServers;
audioJitterBufferMaxPackets = 50;

View File

@ -197,6 +197,12 @@ class PeerConnectionInterface : public rtc::RefCountInterface {
kBundlePolicyMaxCompat
};
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-09#section-4.1.1
enum RtcpMuxPolicy {
kRtcpMuxPolicyNegotiate,
kRtcpMuxPolicyRequire,
};
enum TcpCandidatePolicy {
kTcpCandidatePolicyEnabled,
kTcpCandidatePolicyDisabled
@ -210,12 +216,14 @@ class PeerConnectionInterface : public rtc::RefCountInterface {
// at the same time.
IceServers servers;
BundlePolicy bundle_policy;
RtcpMuxPolicy rtcp_mux_policy;
TcpCandidatePolicy tcp_candidate_policy;
int audio_jitter_buffer_max_packets;
RTCConfiguration()
: type(kAll),
bundle_policy(kBundlePolicyBalanced),
rtcp_mux_policy(kRtcpMuxPolicyNegotiate),
tcp_candidate_policy(kTcpCandidatePolicyEnabled),
audio_jitter_buffer_max_packets(50) {}
};

View File

@ -523,6 +523,7 @@ bool WebRtcSession::Initialize(
DTLSIdentityServiceInterface* dtls_identity_service,
const PeerConnectionInterface::RTCConfiguration& rtc_configuration) {
bundle_policy_ = rtc_configuration.bundle_policy;
rtcp_mux_policy_ = rtc_configuration.rtcp_mux_policy;
// TODO(perkj): Take |constraints| into consideration. Return false if not all
// mandatory constraints can be fulfilled. Note that |constraints|
@ -1600,6 +1601,18 @@ bool WebRtcSession::CreateChannels(const SessionDescription* desc) {
}
}
if (rtcp_mux_policy_ == PeerConnectionInterface::kRtcpMuxPolicyRequire) {
if (voice_channel()) {
voice_channel()->ActivateRtcpMux();
}
if (video_channel()) {
video_channel()->ActivateRtcpMux();
}
if (data_channel()) {
data_channel()->ActivateRtcpMux();
}
}
// Enable bundle before when kMaxBundle policy is in effect.
if (bundle_policy_ == PeerConnectionInterface::kBundlePolicyMaxBundle) {
const cricket::ContentGroup* bundle_group = desc->GetGroupByName(

View File

@ -413,6 +413,9 @@ class WebRtcSession : public cricket::BaseSession,
// Declares the bundle policy for the WebRTCSession.
PeerConnectionInterface::BundlePolicy bundle_policy_;
// Declares the RTCP mux policy for the WebRTCSession.
PeerConnectionInterface::RtcpMuxPolicy rtcp_mux_policy_;
DISALLOW_COPY_AND_ASSIGN(WebRtcSession);
};
} // namespace webrtc

View File

@ -156,8 +156,6 @@ static const char kSdpWithRtx[] =
"a=rtpmap:96 rtx/90000\r\n"
"a=fmtp:96 apt=0\r\n";
static const int kAudioJitterBufferMaxPackets = 50;
// Add some extra |newlines| to the |message| after |line|.
static void InjectAfter(const std::string& line,
const std::string& newlines,
@ -405,11 +403,6 @@ class WebRtcSessionTest : public testing::Test {
void Init() {
PeerConnectionInterface::RTCConfiguration configuration;
configuration.type = PeerConnectionInterface::kAll;
configuration.bundle_policy =
PeerConnectionInterface::kBundlePolicyBalanced;
configuration.audio_jitter_buffer_max_packets =
kAudioJitterBufferMaxPackets;
Init(NULL, configuration);
}
@ -417,20 +410,20 @@ class WebRtcSessionTest : public testing::Test {
PeerConnectionInterface::IceTransportsType ice_transport_type) {
PeerConnectionInterface::RTCConfiguration configuration;
configuration.type = ice_transport_type;
configuration.bundle_policy =
PeerConnectionInterface::kBundlePolicyBalanced;
configuration.audio_jitter_buffer_max_packets =
kAudioJitterBufferMaxPackets;
Init(NULL, configuration);
}
void InitWithBundlePolicy(
PeerConnectionInterface::BundlePolicy bundle_policy) {
PeerConnectionInterface::RTCConfiguration configuration;
configuration.type = PeerConnectionInterface::kAll;
configuration.bundle_policy = bundle_policy;
configuration.audio_jitter_buffer_max_packets =
kAudioJitterBufferMaxPackets;
Init(NULL, configuration);
}
void InitWithRtcpMuxPolicy(
PeerConnectionInterface::RtcpMuxPolicy rtcp_mux_policy) {
PeerConnectionInterface::RTCConfiguration configuration;
configuration.rtcp_mux_policy = rtcp_mux_policy;
Init(NULL, configuration);
}
@ -438,11 +431,6 @@ class WebRtcSessionTest : public testing::Test {
FakeIdentityService* identity_service = new FakeIdentityService();
identity_service->set_should_fail(identity_request_should_fail);
PeerConnectionInterface::RTCConfiguration configuration;
configuration.type = PeerConnectionInterface::kAll;
configuration.bundle_policy =
PeerConnectionInterface::kBundlePolicyBalanced;
configuration.audio_jitter_buffer_max_packets =
kAudioJitterBufferMaxPackets;
Init(identity_service, configuration);
}
@ -2789,6 +2777,46 @@ TEST_F(WebRtcSessionTest, TestMaxBundleWithSetRemoteDescriptionFirst) {
session_->GetTransportProxy("video")->impl());
}
TEST_F(WebRtcSessionTest, TestRequireRtcpMux) {
InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyRequire);
mediastream_signaling_.SendAudioVideoStream1();
PeerConnectionInterface::RTCOfferAnswerOptions options;
SessionDescriptionInterface* offer = CreateOffer(options);
SetLocalDescriptionWithoutError(offer);
EXPECT_FALSE(session_->GetTransportProxy("audio")->impl()->HasChannel(2));
EXPECT_FALSE(session_->GetTransportProxy("video")->impl()->HasChannel(2));
mediastream_signaling_.SendAudioVideoStream2();
SessionDescriptionInterface* answer =
CreateRemoteAnswer(session_->local_description());
SetRemoteDescriptionWithoutError(answer);
EXPECT_FALSE(session_->GetTransportProxy("audio")->impl()->HasChannel(2));
EXPECT_FALSE(session_->GetTransportProxy("video")->impl()->HasChannel(2));
}
TEST_F(WebRtcSessionTest, TestNegotiateRtcpMux) {
InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyNegotiate);
mediastream_signaling_.SendAudioVideoStream1();
PeerConnectionInterface::RTCOfferAnswerOptions options;
SessionDescriptionInterface* offer = CreateOffer(options);
SetLocalDescriptionWithoutError(offer);
EXPECT_TRUE(session_->GetTransportProxy("audio")->impl()->HasChannel(2));
EXPECT_TRUE(session_->GetTransportProxy("video")->impl()->HasChannel(2));
mediastream_signaling_.SendAudioVideoStream2();
SessionDescriptionInterface* answer =
CreateRemoteAnswer(session_->local_description());
SetRemoteDescriptionWithoutError(answer);
EXPECT_FALSE(session_->GetTransportProxy("audio")->impl()->HasChannel(2));
EXPECT_FALSE(session_->GetTransportProxy("video")->impl()->HasChannel(2));
}
// This test verifies that SetLocalDescription and SetRemoteDescription fails
// if BUNDLE is enabled but rtcp-mux is disabled in m-lines.
TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) {

View File

@ -1042,6 +1042,18 @@ bool BaseChannel::SetSrtp_w(const std::vector<CryptoParams>& cryptos,
return true;
}
void BaseChannel::ActivateRtcpMux() {
worker_thread_->Invoke<void>(Bind(
&BaseChannel::ActivateRtcpMux_w, this));
}
void BaseChannel::ActivateRtcpMux_w() {
if (!rtcp_mux_filter_.IsActive()) {
rtcp_mux_filter_.SetActive();
set_rtcp_transport_channel(NULL);
}
}
bool BaseChannel::SetRtcpMux_w(bool enable, ContentAction action,
ContentSource src,
std::string* error_desc) {

View File

@ -108,6 +108,11 @@ class BaseChannel
bool writable() const { return writable_; }
bool IsStreamMuted(uint32 ssrc);
// Activate RTCP mux, regardless of the state so far. Once
// activated, it can not be deactivated, and if the remote
// description doesn't support RTCP mux, setting the remote
// description will fail.
void ActivateRtcpMux();
bool PushdownLocalDescription(const SessionDescription* local_desc,
ContentAction action,
std::string* error_desc);
@ -350,6 +355,7 @@ class BaseChannel
ContentAction action,
ContentSource src,
std::string* error_desc);
void ActivateRtcpMux_w();
bool SetRtcpMux_w(bool enable,
ContentAction action,
ContentSource src,

View File

@ -1140,6 +1140,89 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> {
EXPECT_TRUE(CheckNoRtcp2());
}
// Check that RTP and RTCP are transmitted ok when both sides
// support mux and one the offerer requires mux.
void SendRequireRtcpMuxToRtcpMux() {
CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
channel1_->ActivateRtcpMux();
EXPECT_TRUE(SendInitiate());
EXPECT_EQ(1U, GetTransport1()->channels().size());
EXPECT_EQ(1U, GetTransport2()->channels().size());
EXPECT_TRUE(SendAccept());
EXPECT_TRUE(SendRtp1());
EXPECT_TRUE(SendRtp2());
EXPECT_TRUE(SendRtcp1());
EXPECT_TRUE(SendRtcp2());
EXPECT_TRUE(CheckRtp1());
EXPECT_TRUE(CheckRtp2());
EXPECT_TRUE(CheckNoRtp1());
EXPECT_TRUE(CheckNoRtp2());
EXPECT_TRUE(CheckRtcp1());
EXPECT_TRUE(CheckRtcp2());
EXPECT_TRUE(CheckNoRtcp1());
EXPECT_TRUE(CheckNoRtcp2());
}
// Check that RTP and RTCP are transmitted ok when both sides
// support mux and one the answerer requires rtcp mux.
void SendRtcpMuxToRequireRtcpMux() {
CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
channel2_->ActivateRtcpMux();
EXPECT_TRUE(SendInitiate());
EXPECT_EQ(2U, GetTransport1()->channels().size());
EXPECT_EQ(1U, GetTransport2()->channels().size());
EXPECT_TRUE(SendAccept());
EXPECT_EQ(1U, GetTransport1()->channels().size());
EXPECT_TRUE(SendRtp1());
EXPECT_TRUE(SendRtp2());
EXPECT_TRUE(SendRtcp1());
EXPECT_TRUE(SendRtcp2());
EXPECT_TRUE(CheckRtp1());
EXPECT_TRUE(CheckRtp2());
EXPECT_TRUE(CheckNoRtp1());
EXPECT_TRUE(CheckNoRtp2());
EXPECT_TRUE(CheckRtcp1());
EXPECT_TRUE(CheckRtcp2());
EXPECT_TRUE(CheckNoRtcp1());
EXPECT_TRUE(CheckNoRtcp2());
}
// Check that RTP and RTCP are transmitted ok when both sides
// require mux.
void SendRequireRtcpMuxToRequireRtcpMux() {
CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
channel1_->ActivateRtcpMux();
channel2_->ActivateRtcpMux();
EXPECT_TRUE(SendInitiate());
EXPECT_EQ(1U, GetTransport1()->channels().size());
EXPECT_EQ(1U, GetTransport2()->channels().size());
EXPECT_TRUE(SendAccept());
EXPECT_EQ(1U, GetTransport1()->channels().size());
EXPECT_TRUE(SendRtp1());
EXPECT_TRUE(SendRtp2());
EXPECT_TRUE(SendRtcp1());
EXPECT_TRUE(SendRtcp2());
EXPECT_TRUE(CheckRtp1());
EXPECT_TRUE(CheckRtp2());
EXPECT_TRUE(CheckNoRtp1());
EXPECT_TRUE(CheckNoRtp2());
EXPECT_TRUE(CheckRtcp1());
EXPECT_TRUE(CheckRtcp2());
EXPECT_TRUE(CheckNoRtcp1());
EXPECT_TRUE(CheckNoRtcp2());
}
// Check that SendAccept fails if the answerer doesn't support mux
// and the offerer requires it.
void SendRequireRtcpMuxToNoRtcpMux() {
CreateChannels(RTCP | RTCP_MUX, RTCP);
channel1_->ActivateRtcpMux();
EXPECT_TRUE(SendInitiate());
EXPECT_EQ(1U, GetTransport1()->channels().size());
EXPECT_EQ(2U, GetTransport2()->channels().size());
EXPECT_FALSE(SendAccept());
}
// Check that RTCP data sent by the initiator before the accept is not muxed.
void SendEarlyRtcpMuxToRtcp() {
CreateChannels(RTCP | RTCP_MUX, RTCP);
@ -2085,6 +2168,22 @@ TEST_F(VoiceChannelTest, SendRtcpMuxToRtcpMux) {
Base::SendRtcpMuxToRtcpMux();
}
TEST_F(VoiceChannelTest, SendRequireRtcpMuxToRtcpMux) {
Base::SendRequireRtcpMuxToRtcpMux();
}
TEST_F(VoiceChannelTest, SendRtcpMuxToRequireRtcpMux) {
Base::SendRtcpMuxToRequireRtcpMux();
}
TEST_F(VoiceChannelTest, SendRequireRtcpMuxToRequireRtcpMux) {
Base::SendRequireRtcpMuxToRequireRtcpMux();
}
TEST_F(VoiceChannelTest, SendRequireRtcpMuxToNoRtcpMux) {
Base::SendRequireRtcpMuxToNoRtcpMux();
}
TEST_F(VoiceChannelTest, SendEarlyRtcpMuxToRtcp) {
Base::SendEarlyRtcpMuxToRtcp();
}
@ -2507,6 +2606,22 @@ TEST_F(VideoChannelTest, SendRtcpMuxToRtcpMux) {
Base::SendRtcpMuxToRtcpMux();
}
TEST_F(VideoChannelTest, SendRequireRtcpMuxToRtcpMux) {
Base::SendRequireRtcpMuxToRtcpMux();
}
TEST_F(VideoChannelTest, SendRtcpMuxToRequireRtcpMux) {
Base::SendRtcpMuxToRequireRtcpMux();
}
TEST_F(VideoChannelTest, SendRequireRtcpMuxToRequireRtcpMux) {
Base::SendRequireRtcpMuxToRequireRtcpMux();
}
TEST_F(VideoChannelTest, SendRequireRtcpMuxToNoRtcpMux) {
Base::SendRequireRtcpMuxToNoRtcpMux();
}
TEST_F(VideoChannelTest, SendEarlyRtcpMuxToRtcp) {
Base::SendEarlyRtcpMuxToRtcp();
}

View File

@ -40,7 +40,16 @@ bool RtcpMuxFilter::IsActive() const {
state_ == ST_ACTIVE;
}
void RtcpMuxFilter::SetActive() {
state_ = ST_ACTIVE;
}
bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource src) {
if (state_ == ST_ACTIVE) {
// Fail if we try to deactivate and no-op if we try and activate.
return offer_enable;
}
if (!ExpectOffer(offer_enable, src)) {
LOG(LS_ERROR) << "Invalid state for change of RTCP mux offer";
return false;
@ -53,6 +62,11 @@ bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource src) {
bool RtcpMuxFilter::SetProvisionalAnswer(bool answer_enable,
ContentSource src) {
if (state_ == ST_ACTIVE) {
// Fail if we try to deactivate and no-op if we try and activate.
return answer_enable;
}
if (!ExpectAnswer(src)) {
LOG(LS_ERROR) << "Invalid state for RTCP mux provisional answer";
return false;
@ -83,6 +97,11 @@ bool RtcpMuxFilter::SetProvisionalAnswer(bool answer_enable,
}
bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource src) {
if (state_ == ST_ACTIVE) {
// Fail if we try to deactivate and no-op if we try and activate.
return answer_enable;
}
if (!ExpectAnswer(src)) {
LOG(LS_ERROR) << "Invalid state for RTCP mux answer";
return false;
@ -100,19 +119,24 @@ bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource src) {
return true;
}
bool RtcpMuxFilter::DemuxRtcp(const char* data, int len) {
// If we're muxing RTP/RTCP, we must inspect each packet delivered and
// determine whether it is RTP or RTCP. We do so by checking the packet type,
// and assuming RTP if type is 0-63 or 96-127. For additional details, see
// http://tools.ietf.org/html/rfc5761.
// Note that if we offer RTCP mux, we may receive muxed RTCP before we
// receive the answer, so we operate in that state too.
if (!offer_enable_ || state_ < ST_SENTOFFER) {
// Check the RTP payload type. If 63 < payload type < 96, it's RTCP.
// For additional details, see http://tools.ietf.org/html/rfc5761.
bool IsRtcp(const char* data, int len) {
if (len < 2) {
return false;
}
char pt = data[1] & 0x7F;
return (63 < pt) && (pt < 96);
}
int type = (len >= 2) ? (static_cast<uint8>(data[1]) & 0x7F) : 0;
return (type >= 64 && type < 96);
bool RtcpMuxFilter::DemuxRtcp(const char* data, int len) {
// If we're muxing RTP/RTCP, we must inspect each packet delivered
// and determine whether it is RTP or RTCP. We do so by looking at
// the RTP payload type (see IsRtcp). Note that if we offer RTCP
// mux, we may receive muxed RTCP before we receive the answer, so
// we operate in that state too.
bool offered_mux = ((state_ == ST_SENTOFFER) && offer_enable_);
return (IsActive() || offered_mux) && IsRtcp(data, len);
}
bool RtcpMuxFilter::ExpectOffer(bool offer_enable, ContentSource source) {

View File

@ -41,6 +41,9 @@ class RtcpMuxFilter {
// Whether the filter is active, i.e. has RTCP mux been properly negotiated.
bool IsActive() const;
// Make the filter active, regardless of the current state.
void SetActive();
// Specifies whether the offer indicates the use of RTCP mux.
bool SetOffer(bool offer_enable, ContentSource src);

View File

@ -212,3 +212,44 @@ TEST(RtcpMuxFilterTest, KeepFilterDisabledDuringUpdate) {
EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
EXPECT_FALSE(filter.IsActive());
}
// Test that we can SetActive and then can't deactivate.
TEST(RtcpMuxFilterTest, SetActiveCantDeactivate) {
cricket::RtcpMuxFilter filter;
const char data[] = { 0, 73, 0, 0 };
const int len = 4;
filter.SetActive();
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.DemuxRtcp(data, len));
EXPECT_FALSE(filter.SetOffer(false, cricket::CS_LOCAL));
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.SetOffer(true, cricket::CS_LOCAL));
EXPECT_TRUE(filter.IsActive());
EXPECT_FALSE(filter.SetProvisionalAnswer(false, cricket::CS_REMOTE));
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.SetProvisionalAnswer(true, cricket::CS_REMOTE));
EXPECT_TRUE(filter.IsActive());
EXPECT_FALSE(filter.SetAnswer(false, cricket::CS_REMOTE));
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_REMOTE));
EXPECT_TRUE(filter.IsActive());
EXPECT_FALSE(filter.SetOffer(false, cricket::CS_REMOTE));
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
EXPECT_TRUE(filter.IsActive());
EXPECT_FALSE(filter.SetProvisionalAnswer(false, cricket::CS_LOCAL));
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.SetProvisionalAnswer(true, cricket::CS_LOCAL));
EXPECT_TRUE(filter.IsActive());
EXPECT_FALSE(filter.SetAnswer(false, cricket::CS_LOCAL));
EXPECT_TRUE(filter.IsActive());
EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
EXPECT_TRUE(filter.IsActive());
}