Merge branch 'coin-refactor'

cusax-fix
Boris Grozev 13 years ago
commit 4643383af8

@ -658,4 +658,12 @@ protected void sendCandidates(
protocolProvider.getConnection().sendPacket(candidatesIQ);
}
/**
* {@inheritDoc}
*/
public String getEntity()
{
return getAddress();
}
}

@ -71,6 +71,17 @@ public class CallPeerJabberImpl
*/
private final Object sidSyncRoot = new Object();
/**
* Whether a COIN has been scheduled to be sent to this
* <tt>CallPeerJabberImpl</tt>
*/
private boolean coinScheduled = false;
/**
* Synchronization object for coinScheduled
*/
private final Object coinScheduledSyncRoot = new Object();
/**
* Creates a new call peer with address <tt>peerAddress</tt>.
*
@ -1453,4 +1464,40 @@ else if (((IQ) result).getType() != IQ.Type.RESULT)
new TransferredPacketExtension()));
}
}
/**
* Check whether a COIN is scheduled to be sent to this <tt>CallPeer</tt>
* (i.e. there is a thread which will eventually (after sleeping a certain
* amount of time) trigger a COIN to be sent)
* @return <tt>true</tt> if there is a COIN scheduled to be sent to this
* <tt>CallPeer</tt> and <tt>false</tt> otherwise
*/
public boolean isCoinScheduled()
{
synchronized (coinScheduledSyncRoot)
{
return coinScheduled;
}
}
/**
* Sets the property which indicates whether a COIN is scheduled to be sent
* to this <tt>CallPeer</tt>.
* @param coinScheduled
*/
public void setCoinScheduled(boolean coinScheduled)
{
synchronized (coinScheduledSyncRoot)
{
this.coinScheduled = coinScheduled;
}
}
/**
* {@inheritDoc}
*/
public String getEntity()
{
return getAddress();
}
}

@ -15,7 +15,7 @@
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.xml.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
@ -27,6 +27,7 @@
*
* @author Lyubomir Marinov
* @author Sebastien Vincent
* @author Boris Grozev
*/
public class OperationSetTelephonyConferencingJabberImpl
extends AbstractOperationSetTelephonyConferencing<
@ -49,15 +50,15 @@ public class OperationSetTelephonyConferencingJabberImpl
= Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class);
/**
* Synchronization object.
* The minimum interval in milliseconds between COINs sent to a single
* <tt>CallPeer</tt>.
*/
private final Object lock = new Object();
private static final int COIN_MIN_INTERVAL = 200;
/**
* The value of the <tt>version</tt> attribute to be specified in the
* outgoing <tt>conference-info</tt> root XML elements.
* Synchronization object.
*/
private int version = 1;
private final Object lock = new Object();
/**
* Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt>
@ -97,8 +98,6 @@ protected void notifyCallPeers(Call call)
{
notify(i.next());
}
version++;
}
}
}
@ -114,10 +113,41 @@ private void notify(CallPeer callPeer)
if(!(callPeer instanceof CallPeerJabberImpl))
return;
final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer;
final long timeSinceLastCoin = System.currentTimeMillis()
- callPeerJabber.getLastConferenceInfoSentTimestamp();
if (timeSinceLastCoin < COIN_MIN_INTERVAL)
{
if (callPeerJabber.isCoinScheduled())
return;
logger.info("Scheduling to send a COIN to " + callPeerJabber);
callPeerJabber.setCoinScheduled(true);
new Thread(new Runnable(){
@Override
public void run()
{
try
{
Thread.sleep(1 + COIN_MIN_INTERVAL - timeSinceLastCoin);
}
catch (InterruptedException ie) {}
OperationSetTelephonyConferencingJabberImpl.this
.notify(callPeerJabber);
}
}).start();
return;
}
// check that callPeer supports COIN before sending him a
// conference-info
String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress());
// XXX if this generates actual disco#info requests we might want to
// cache it.
try
{
DiscoverInfo discoverInfo
@ -127,6 +157,7 @@ private void notify(CallPeer callPeer)
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN))
{
logger.info(callPeer.getAddress() + " does not support COIN");
callPeerJabber.setCoinScheduled(false);
return;
}
}
@ -135,135 +166,41 @@ private void notify(CallPeer callPeer)
logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
}
IQ iq = getConferenceInfo((CallPeerJabberImpl) callPeer, version);
ConferenceInfoDocument currentConfInfo
= getCurrentConferenceInfo(callPeerJabber);
ConferenceInfoDocument lastSentConfInfo
= callPeerJabber.getLastConferenceInfoSent();
if (iq != null)
parentProvider.getConnection().sendPacket(iq);
}
ConferenceInfoDocument diff;
/**
* Get media packet extension for the specified <tt>CallPeerJabberImpl</tt>.
*
* @param callPeer <tt>CallPeer</tt>
* @param remote if the callPeer is remote or local
* @return list of media packet extension
*/
private List<MediaPacketExtension> getMedia(
MediaAwareCallPeer<?,?,?> callPeer,
boolean remote)
{
CallPeerMediaHandler<?> mediaHandler = callPeer.getMediaHandler();
List<MediaPacketExtension> ret = new ArrayList<MediaPacketExtension>();
long i = 1;
if (lastSentConfInfo == null)
diff = currentConfInfo;
else
diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
for(MediaType mediaType : MediaType.values())
if (diff != null)
{
MediaStream stream = mediaHandler.getStream(mediaType);
if (stream != null)
{
MediaPacketExtension ext
= new MediaPacketExtension(Long.toString(i));
long srcId
= remote
? getRemoteSourceID(callPeer, mediaType)
: stream.getLocalSourceID();
int newVersion
= lastSentConfInfo == null
? 1
: lastSentConfInfo.getVersion() + 1;
diff.setVersion(newVersion);
if (srcId != -1)
ext.setSrcID(Long.toString(srcId));
IQ iq = getConferenceInfo(callPeerJabber, diff);
ext.setType(mediaType.toString());
MediaDirection direction
= remote
? getRemoteDirection(callPeer, mediaType)
: stream.getDirection();
if (direction == null)
direction = MediaDirection.INACTIVE;
ext.setStatus(direction.toString());
ret.add(ext);
i++;
}
}
return ret;
}
/**
* Get user packet extension for the specified <tt>CallPeerJabberImpl</tt>.
*
* @param callPeer <tt>CallPeer</tt>
* @return user packet extension
*/
private UserPacketExtension getUser(CallPeer callPeer)
{
UserPacketExtension ext
= new UserPacketExtension(callPeer.getAddress());
ext.setDisplayText(callPeer.getDisplayName());
EndpointPacketExtension endpoint
= new EndpointPacketExtension(callPeer.getURI());
endpoint.setStatus(getEndpointStatus(callPeer));
if (callPeer instanceof MediaAwareCallPeer<?,?,?>)
{
List<MediaPacketExtension> medias
= getMedia((MediaAwareCallPeer<?,?,?>) callPeer, true);
if(medias != null)
if (iq != null)
{
for(MediaPacketExtension media : medias)
endpoint.addChildExtension(media);
parentProvider.getConnection().sendPacket(iq);
// We save currentConfInfo, because it is of state "full", while
// diff could be a partial
currentConfInfo.setVersion(newVersion);
callPeerJabber.setLastConferenceInfoSent(currentConfInfo);
callPeerJabber.setLastConferenceInfoSentTimestamp(
System.currentTimeMillis());
}
}
ext.addChildExtension(endpoint);
return ext;
}
/**
* Generates the text content to be put in the <tt>status</tt> XML element
* of an <tt>endpoint</tt> XML element and which describes the state of a
* specific <tt>CallPeer</tt>.
*
* @param callPeer the <tt>CallPeer</tt> which is to get its state described
* in a <tt>status</tt> XML element of an <tt>endpoint</tt> XML element
* @return the text content to be put in the <tt>status</tt> XML element of
* an <tt>endpoint</tt> XML element and which describes the state of the
* specified <tt>callPeer</tt>
*/
private EndpointStatusType getEndpointStatus(CallPeer callPeer)
{
CallPeerState callPeerState = callPeer.getState();
if (CallPeerState.ALERTING_REMOTE_SIDE.equals(callPeerState))
return EndpointStatusType.alerting;
if (CallPeerState.CONNECTING.equals(callPeerState)
|| CallPeerState
.CONNECTING_WITH_EARLY_MEDIA.equals(callPeerState))
return EndpointStatusType.pending;
if (CallPeerState.DISCONNECTED.equals(callPeerState))
return EndpointStatusType.disconnected;
if (CallPeerState.INCOMING_CALL.equals(callPeerState))
return EndpointStatusType.dialing_in;
if (CallPeerState.INITIATING_CALL.equals(callPeerState))
return EndpointStatusType.dialing_out;
/*
* he/she is neither "hearing" the conference mix nor is his/her media
* being mixed in the conference
*/
if (CallPeerState.ON_HOLD_LOCALLY.equals(callPeerState)
|| CallPeerState.ON_HOLD_MUTUALLY.equals(callPeerState))
return EndpointStatusType.on_hold;
if (CallPeerState.CONNECTED.equals(callPeerState))
return EndpointStatusType.connected;
return null;
callPeerJabber.setCoinScheduled(false);
}
/**
@ -272,67 +209,34 @@ private EndpointStatusType getEndpointStatus(CallPeer callPeer)
* conference managed by the local peer.
*
* @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
* @param version the value of the version attribute of the
* <tt>conference-info</tt> root element of the conference-info XML to be
* generated
* @param confInfo the <tt>ConferenceInformationDocument</tt> which is to be
* included in the IQ
* @return the conference-info IQ to be sent to the specified
* <tt>callPeer</tt> in order to notify it of the current state of the
* conference managed by the local peer
*/
private IQ getConferenceInfo(CallPeerJabberImpl callPeer, int version)
private IQ getConferenceInfo(CallPeerJabberImpl callPeer,
final ConferenceInfoDocument confInfo)
{
String callPeerSID = callPeer.getSID();
if (callPeerSID == null)
return null;
CoinIQ iq = new CoinIQ();
IQ iq = new IQ(){
@Override
public String getChildElementXML()
{
return confInfo.toXml();
}
};
CallJabberImpl call = callPeer.getCall();
iq.setFrom(call.getProtocolProvider().getOurJID());
iq.setTo(callPeer.getAddress());
iq.setType(Type.SET);
iq.setEntity(getBasicTelephony().getProtocolProvider().getOurJID());
iq.setVersion(version);
iq.setState(StateType.full);
iq.setSID(callPeerSID);
// conference-description
iq.addExtension(new DescriptionPacketExtension());
// conference-state
StatePacketExtension state = new StatePacketExtension();
List<CallPeer> conferenceCallPeers = CallConference.getCallPeers(call);
state.setUserCount(
1 /* the local peer/user */ + conferenceCallPeers.size());
iq.addExtension(state);
// users
UsersPacketExtension users = new UsersPacketExtension();
// user
UserPacketExtension user
= new UserPacketExtension("xmpp:" + parentProvider.getOurJID());
// endpoint
EndpointPacketExtension endpoint = new EndpointPacketExtension(
"xmpp:" + parentProvider.getOurJID());
endpoint.setStatus(EndpointStatusType.connected);
// media
List<MediaPacketExtension> medias = getMedia(callPeer, false);
for(MediaPacketExtension media : medias)
endpoint.addChildExtension(media);
user.addChildExtension(endpoint);
users.addChildExtension(user);
// other users
for (CallPeer conferenceCallPeer : conferenceCallPeers)
users.addChildExtension(getUser(conferenceCallPeer));
iq.addExtension(users);
return iq;
}
@ -486,8 +390,8 @@ public void processPacket(Packet packet)
if (callPeer != null)
{
if (logger.isDebugEnabled())
logger.debug("Processing COIN from" + coinIQ.getFrom()
+ "(version=" + coinIQ.getVersion() + ")");
logger.debug("Processing COIN from " + coinIQ.getFrom()
+ " (version=" + coinIQ.getVersion() + ")");
handleCoin(callPeer, coinIQ);
}
}
@ -504,6 +408,53 @@ public void processPacket(Packet packet)
*/
private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ)
{
setConferenceInfoXML(callPeer, -1, coinIQ.getChildElementXML());
try
{
setConferenceInfoXML(callPeer, coinIQ.getChildElementXML());
}
catch (XMLException e)
{
logger.error("Could not handle received COIN from " + callPeer
+ ": " + coinIQ);
}
}
/**
* {@inheritDoc}
*
* For COINs (XEP-0298), we use the attributes of the
* <tt>conference-info</tt> element to piggyback a Jingle SID. This is
* temporary and should be removed once we choose a better way to pass the
* SID.
*/
protected ConferenceInfoDocument getCurrentConferenceInfo(
MediaAwareCallPeer<?,?,?> callPeer)
{
ConferenceInfoDocument confInfo
= super.getCurrentConferenceInfo(callPeer);
if (callPeer instanceof CallPeerJabberImpl)
{
confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID());
}
return confInfo;
}
/**
* {@inheritDoc}
*/
@Override
protected String getLocalEntity(CallPeer callPeer)
{
return "xmpp:" + parentProvider.getOurJID();
}
/**
* {@inheritDoc}
*/
@Override
protected String getLocalDisplayName()
{
return null;
}
}

@ -44,6 +44,7 @@ Import-Package: ch.imvs.sdes4j.srtp,
org.jitsi.service.resources,
org.jitsi.service.version,
org.jitsi.util,
org.jitsi.util.xml,
org.jivesoftware.smack,
org.jivesoftware.smack.filter,
org.jivesoftware.smack.packet,

@ -1681,4 +1681,14 @@ private void setDisconnectedState(boolean failed, String reason)
else
setState(CallPeerState.DISCONNECTED, reason);
}
/**
* {@inheritDoc}
*/
public String getEntity()
{
return AbstractOperationSetTelephonyConferencing
.stripParametersFromAddress(getURI());
}
}

@ -21,15 +21,13 @@
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.MediaType;
import org.jitsi.util.xml.*;
/**
* Implements <tt>OperationSetTelephonyConferencing</tt> for SIP.
*
* @author Lyubomir Marinov
* @author Boris Grozev
*/
public class OperationSetTelephonyConferencingSipImpl
extends AbstractOperationSetTelephonyConferencing<
@ -40,7 +38,6 @@ public class OperationSetTelephonyConferencingSipImpl
Address>
implements MethodProcessorListener
{
/**
* The <tt>Logger</tt> used by the
* <tt>OperationSetTelephonyConferencingSipImpl</tt> class and its instances
@ -55,28 +52,6 @@ public class OperationSetTelephonyConferencingSipImpl
*/
private static final String CONTENT_SUB_TYPE = "conference-info+xml";
/**
* The name of the conference-info XML element
* <tt>conference-description</tt>.
*/
private static final String ELEMENT_CONFERENCE_DESCRIPTION
= "conference-description";
/**
* The name of the conference-info XML element <tt>conference-info</tt>.
*/
private static final String ELEMENT_CONFERENCE_INFO = "conference-info";
/**
* The name of the conference-info XML element <tt>conference-state</tt>.
*/
private static final String ELEMENT_CONFERENCE_STATE = "conference-state";
/**
* The name of the conference-info XML element <tt>user-count</tt>.
*/
private static final String ELEMENT_USER_COUNT = "user-count";
/**
* The name of the event package supported by
* <tt>OperationSetTelephonyConferencingSipImpl</tt> in SUBSCRIBE and NOTIFY
@ -98,12 +73,6 @@ public class OperationSetTelephonyConferencingSipImpl
*/
private static final int SUBSCRIPTION_DURATION = 3600;
/**
* The utility which encodes text so that it's acceptable as the text of an
* XML element or attribute.
*/
private DOMElementWriter domElementWriter = new DOMElementWriter();
/**
* The <tt>EventPackageNotifier</tt> which implements conference
* event-package notifier support on behalf of this
@ -283,267 +252,44 @@ protected CallSipImpl createOutgoingCall()
/**
* Generates the conference-info XML to be sent to a specific
* <tt>CallPeer</tt> in order to notify it of the current state of the
* conference managed by the local peer.
* conference managed by the local peer. Return <tt>null</tt> if
* conference-info XML does not need to be sent to <tt>callPeer</tt>.
*
* @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
* @param version the value of the version attribute of the
* <tt>conference-info</tt> root element of the conference-info XML to be
* generated
* @return the conference-info XML to be sent to the specified
* <tt>callPeer</tt> in order to notify it of the current state of the
* conference managed by the local peer
*/
private String getConferenceInfoXML(CallPeerSipImpl callPeer, int version)
{
Dialog dialog = callPeer.getDialog();
String localParty = null;
if (dialog != null)
{
Address localPartyAddress = dialog.getLocalParty();
if (localPartyAddress != null)
localParty
= stripParametersFromAddress(
localPartyAddress.getURI().toString());
}
StringBuffer xml = new StringBuffer();
xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
// <conference-info>
append(xml, "<", ELEMENT_CONFERENCE_INFO);
// entity
append(xml, " entity=\"", domElementWriter.encode(localParty), "\"");
// state
xml.append(" state=\"full\"");
// version
append(xml, " version=\"", Integer.toString(version), "\">");
// <conference-description/>
append(xml, "<", ELEMENT_CONFERENCE_DESCRIPTION, "/>");
// <conference-state>
append(xml, "<", ELEMENT_CONFERENCE_STATE, ">");
// <user-count>
append(xml, "<", ELEMENT_USER_COUNT, ">");
CallSipImpl call = callPeer.getCall();
List<CallPeer> conferenceCallPeers = CallConference.getCallPeers(call);
xml.append(1 /* the local peer/user */ + conferenceCallPeers.size());
// </user-count>
append(xml, "</", ELEMENT_USER_COUNT, ">");
// </conference-state>
append(xml, "</", ELEMENT_CONFERENCE_STATE, ">");
// <users>
append(xml, "<", ELEMENT_USERS, ">");
// <user>
append(xml, "<", ELEMENT_USER);
// entity
append(xml, " entity=\"", domElementWriter.encode(localParty), "\"");
// state
xml.append(" state=\"full\">");
String ourDisplayName = parentProvider.getOurDisplayName();
if (ourDisplayName != null)
{
// <display-text>
append(xml, "<", ELEMENT_DISPLAY_TEXT, ">");
xml.append(domElementWriter.encode(ourDisplayName));
// </display-text>
append(xml, "</", ELEMENT_DISPLAY_TEXT, ">");
}
// <endpoint>
append(xml, "<", ELEMENT_ENDPOINT, ">");
// <status>
append(xml, "<", ELEMENT_STATUS, ">");
// We are the conference focus so we're connected to the conference.
xml.append(AbstractConferenceMember.CONNECTED);
// </status>
append(xml, "</", ELEMENT_STATUS, ">");
getMediaXML(callPeer, false, xml);
// </endpoint>
append(xml, "</", ELEMENT_ENDPOINT, ">");
// </user>
append(xml, "</", ELEMENT_USER, ">");
for (CallPeer conferenceCallPeer : conferenceCallPeers)
getUserXML(conferenceCallPeer, xml);
// </users>
append(xml, "</", ELEMENT_USERS, ">");
// </conference-info>
append(xml, "</", ELEMENT_CONFERENCE_INFO, ">");
return xml.toString();
}
/**
* Generates the text content to be put in the <tt>status</tt> XML element
* of an <tt>endpoint</tt> XML element and which describes the state of a
* specific <tt>CallPeer</tt>.
*
* @param callPeer the <tt>CallPeer</tt> which is to get its state described
* in a <tt>status</tt> XML element of an <tt>endpoint</tt> XML element
* @return the text content to be put in the <tt>status</tt> XML element of
* an <tt>endpoint</tt> XML element and which describes the state of the
* specified <tt>callPeer</tt>
*/
private String getEndpointStatusXML(CallPeer callPeer)
{
CallPeerState callPeerState = callPeer.getState();
if (CallPeerState.ALERTING_REMOTE_SIDE.equals(callPeerState))
return AbstractConferenceMember.ALERTING;
if (CallPeerState.CONNECTING.equals(callPeerState)
|| CallPeerState
.CONNECTING_WITH_EARLY_MEDIA.equals(callPeerState))
return AbstractConferenceMember.PENDING;
if (CallPeerState.DISCONNECTED.equals(callPeerState))
return AbstractConferenceMember.DISCONNECTED;
if (CallPeerState.INCOMING_CALL.equals(callPeerState))
return AbstractConferenceMember.DIALING_IN;
if (CallPeerState.INITIATING_CALL.equals(callPeerState))
return AbstractConferenceMember.DIALING_OUT;
/*
* he/she is neither "hearing" the conference mix nor is his/her media
* being mixed in the conference
*/
if (CallPeerState.ON_HOLD_LOCALLY.equals(callPeerState)
|| CallPeerState.ON_HOLD_MUTUALLY.equals(callPeerState))
return AbstractConferenceMember.ON_HOLD;
if (CallPeerState.CONNECTED.equals(callPeerState))
return AbstractConferenceMember.CONNECTED;
return null;
}
/**
* Appends to a specific <tt>StringBuffer</tt> <tt>media</tt> XML element
* trees which describe the state of the media streaming between a specific
* <tt>CallPeer</tt> and its local peer represented by an associated
* <tt>Call</tt>.
*
* @param callPeer the <tt>CallPeer</tt> which is to get its media streaming
* state described in <tt>media</tt> XML element trees appended to the
* specified <tt>StringBuffer</tt>
* @param remote <tt>true</tt> if the streaming from the <tt>callPeer</tt>
* to the local peer is to be described or <tt>false</tt> if the streaming
* from the local peer to the remote <tt>callPeer</tt> is to be described
* @param xml the <tt>StringBuffer</tt> to append the <tt>media</tt> XML
* trees describing the media streaming state of the specified
* <tt>callPeer</tt>
*/
private void getMediaXML(
MediaAwareCallPeer<?,?,?> callPeer,
boolean remote,
StringBuffer xml)
{
CallPeerMediaHandler<?> mediaHandler = callPeer.getMediaHandler();
for (MediaType mediaType : MediaType.values())
{
MediaStream stream = mediaHandler.getStream(mediaType);
if (stream != null)
{
// <media>
append(xml, "<", ELEMENT_MEDIA, ">");
// <type>
append(xml, "<", ELEMENT_TYPE, ">");
xml.append(mediaType.toString());
// </type>
append(xml, "</", ELEMENT_TYPE, ">");
long srcId
= remote
? getRemoteSourceID(callPeer, mediaType)
: stream.getLocalSourceID();
if (srcId != -1)
{
// <src-id>
append(xml, "<", ELEMENT_SRC_ID, ">");
xml.append(srcId);
// </src-id>
append(xml, "</", ELEMENT_SRC_ID, ">");
}
MediaDirection direction
= remote
? getRemoteDirection(callPeer, mediaType)
: stream.getDirection();
if (direction == null)
direction = MediaDirection.INACTIVE;
// <status>
append(xml, "<", ELEMENT_STATUS, ">");
xml.append(direction.toString());
// </status>
append(xml, "</", ELEMENT_STATUS, ">");
// </media>
append(xml, "</", ELEMENT_MEDIA, ">");
}
}
}
/**
* Appends to a specific <tt>StringBuffer</tt> a <tt>user</tt> XML element
* tree which describes the participation of a specific <tt>CallPeer</tt> in
* a conference managed by the local peer represented by its associated
* <tt>Call</tt>.
*
* @param callPeer the <tt>CallPeer</tt> which is to get its conference
* participation describes in a <tt>user</tt> XML element tree appended to
* the specified <tt>StringBuffer</tt>
* @param xml the <tt>StringBuffer</tt> to append the <tt>user</tt> XML
* tree describing the conference participation of the specified
* <tt>callPeer</tt> to
* conference managed by the local peer. Return <tt>null</tt> if
* conference-info XML does not need to be sent to <tt>callPeer</tt>.
*/
private void getUserXML(CallPeer callPeer, StringBuffer xml)
private String getConferenceInfoXML(CallPeerSipImpl callPeer)
{
// <user>
append(xml, "<", ELEMENT_USER);
// entity
append(
xml,
" entity=\"",
domElementWriter.encode(
stripParametersFromAddress(callPeer.getURI())),
"\"");
// state
xml.append(" state=\"full\">");
String displayName = callPeer.getDisplayName();
if (displayName != null)
{
// <display-text>
append(xml, "<", ELEMENT_DISPLAY_TEXT, ">");
xml.append(domElementWriter.encode(displayName));
// </display-text>
append(xml, "</", ELEMENT_DISPLAY_TEXT, ">");
}
// <endpoint>
append(xml, "<", ELEMENT_ENDPOINT, ">");
String status = getEndpointStatusXML(callPeer);
if (status != null)
ConferenceInfoDocument currentConfInfo
= getCurrentConferenceInfo(callPeer);
ConferenceInfoDocument lastSentConfInfo
= callPeer.getLastConferenceInfoSent();
ConferenceInfoDocument diff
= getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
if (diff == null)
return null;
else
{
// <status>
append(xml, "<", ELEMENT_STATUS, ">");
xml.append(status);
// </status>
append(xml, "</", ELEMENT_STATUS, ">");
int newVersion
= lastSentConfInfo == null
? 1
: lastSentConfInfo.getVersion() + 1;
diff.setVersion(newVersion);
currentConfInfo.setVersion(newVersion);
// We save currentConfInfo, because it is of state "full", while
// diff could be a partial
callPeer.setLastConferenceInfoSent(currentConfInfo);
callPeer.setLastConferenceInfoSentTimestamp(
System.currentTimeMillis());
return diff.toString();
}
if (callPeer instanceof MediaAwareCallPeer<?,?,?>)
getMediaXML((MediaAwareCallPeer<?,?,?>) callPeer, true, xml);
// </endpoint>
append(xml, "</", ELEMENT_ENDPOINT, ">");
// </user>
append(xml, "</", ELEMENT_USER, ">");
}
/**
@ -552,7 +298,7 @@ private void getUserXML(CallPeer callPeer, StringBuffer xml)
* Implements the protocol-dependent part of the logic of inviting a callee
* to a <tt>Call</tt>. The protocol-independent part of that logic is
* implemented by
* {@link AbstractOperationSetTelephonyConferencing#inviteCalleToCall(String,Call)}.
* {@link AbstractOperationSetTelephonyConferencing#inviteCalleeToCall(String,Call)}.
*/
@Override
protected CallPeerSipImpl doInviteCalleeToCall(
@ -778,6 +524,37 @@ public void responseProcessed(
}
}
/**
* {@inheritDoc}
*/
@Override
protected String getLocalEntity(CallPeer callPeer)
{
if (callPeer instanceof CallPeerSipImpl)
{
Dialog dialog = ((CallPeerSipImpl)callPeer).getDialog();
if (dialog != null)
{
Address localPartyAddress = dialog.getLocalParty();
if (localPartyAddress != null)
return stripParametersFromAddress(
localPartyAddress.getURI().toString());
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
protected String getLocalDisplayName()
{
return parentProvider.getOurDisplayName();
}
/**
* Implements <tt>EventPackageNotifier.Subscription</tt> in order to
* represent a conference subscription created by a remote <tt>CallPeer</tt>
@ -786,13 +563,6 @@ public void responseProcessed(
private class ConferenceNotifierSubscription
extends EventPackageNotifier.Subscription
{
/**
* The value of the <tt>version</tt> attribute to be specified in the
* outgoing <tt>conference-info</tt> root XML elements.
*/
private int version = 1;
/**
* Initializes a new <tt>ConferenceNotifierSubscription</tt> instance
* with a specific subscription <tt>Address</tt>/Request URI and a
@ -845,29 +615,52 @@ protected byte[] createNotifyContent(
return null;
}
String conferenceInfoXML = getConferenceInfoXML(callPeer, version);
byte[] notifyContent;
ConferenceInfoDocument currentConfInfo
= getCurrentConferenceInfo(callPeer);
ConferenceInfoDocument lastSentConfInfo
= callPeer.getLastConferenceInfoSent();
if (conferenceInfoXML == null)
notifyContent = null;
//Uncomment this when the rest of the code can handle a return value
//of null in case no NOTIFY needs to be sent.
/*
ConferenceInfoDocument diff
= lastSentConfInfo == null
? currentConfInfo
:getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
*/
ConferenceInfoDocument diff = currentConfInfo;
if (diff == null)
return null;
else
{
int newVersion
= lastSentConfInfo == null
? 1
: lastSentConfInfo.getVersion() + 1;
diff.setVersion(newVersion);
currentConfInfo.setVersion(newVersion);
// We save currentConfInfo, because it is of state "full", while
// diff could be a partial
callPeer.setLastConferenceInfoSent(currentConfInfo);
callPeer.setLastConferenceInfoSentTimestamp(
System.currentTimeMillis());
String xml = diff.toXml();
byte[] notifyContent;
try
{
notifyContent = conferenceInfoXML.getBytes("UTF-8");
notifyContent = xml.getBytes("UTF-8");
}
catch (UnsupportedEncodingException uee)
{
logger
.warn(
"Failed to gets bytes from String for the UTF-8 " +
"charset",
uee);
notifyContent = conferenceInfoXML.getBytes();
logger.warn("Failed to gets bytes from String for the "
+ "UTF-8 charset", uee);
notifyContent = xml.getBytes();
}
++ version;
return notifyContent;
}
return notifyContent;
}
/**
@ -998,14 +791,17 @@ protected void processActiveRequest(
{
if (rawContent != null)
{
int contentVersion
= setConferenceInfoXML(
try
{
setConferenceInfoXML(
callPeer,
version,
SdpUtils.getContentAsString(requestEvent.getRequest()));
if (contentVersion >= version)
version = contentVersion;
}
catch (XMLException e)
{
logger.error("Could not handle conference-info NOTIFY sent"
+ " to us by " + callPeer);
}
}
}

@ -7,18 +7,15 @@
package net.java.sip.communicator.service.protocol.media;
import java.beans.*;
import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.xml.*;
import org.w3c.dom.*;
import org.xml.sax.*;
/**
* Represents a default implementation of
@ -33,6 +30,7 @@
* @param <CalleeAddressT>
*
* @author Lyubomir Marinov
* @author Boris Grozev
*/
public abstract class AbstractOperationSetTelephonyConferencing<
ProtocolProviderServiceT extends ProtocolProviderService,
@ -748,21 +746,21 @@ else if (RegistrationState.UNREGISTERED.equals(newState))
/**
* Updates the conference-related properties of a specific <tt>CallPeer</tt>
* such as <tt>conferenceFocus</tt> and <tt>conferenceMembers</tt> with
* information received from it as a conference focus in the form of a
* conference-info XML document.
* the information described in <tt>confInfo</tt>.
* <tt>confInfo</tt> must be a document with "full" state.
*
* @param callPeer the <tt>CallPeer</tt> which is a conference focus and has
* sent the specified conference-info XML document
* @param conferenceInfoDocument the conference-info XML document sent by
* <tt>callPeer</tt> in order to update the conference-related information
* of the local peer represented by the associated <tt>Call</tt>
* @param confInfo the conference-info XML document to use to update
* the conference-related information of the local peer represented
* by the associated <tt>Call</tt>. It must have a "full" state.
*/
private void setConferenceInfoDocument(
private int setConferenceInfoDocument(
MediaAwareCallPeerT callPeer,
Document conferenceInfoDocument)
ConferenceInfoDocument confInfo)
{
NodeList usersList
= conferenceInfoDocument.getElementsByTagName(ELEMENT_USERS);
= confInfo.getDocument().getElementsByTagName(ELEMENT_USERS);
ConferenceMember[] toRemove
= callPeer.getConferenceMembers().toArray(
AbstractCallPeer.NO_CONFERENCE_MEMBERS);
@ -888,6 +886,9 @@ else if (ELEMENT_ENDPOINT.equals(userChildName))
if (changed)
notifyAll(callPeer.getCall());
callPeer.setLastConferenceInfoReceived(confInfo);
return confInfo.getVersion();
}
/**
@ -898,9 +899,6 @@ else if (ELEMENT_ENDPOINT.equals(userChildName))
*
* @param callPeer the <tt>CallPeer</tt> which is a conference focus and has
* sent the specified conference-info XML document
* @param version the value of the <tt>version</tt> attribute of the
* <tt>conference-info</tt> XML element currently represented in the
* specified <tt>callPeer</tt>
* @param conferenceInfoXML the conference-info XML document sent by
* <tt>callPeer</tt> in order to update the conference-related information
* of the local peer represented by the associated <tt>Call</tt>
@ -908,89 +906,566 @@ else if (ELEMENT_ENDPOINT.equals(userChildName))
* <tt>conference-info</tt> XML element of the specified
* <tt>conferenceInfoXML</tt> if it was successfully parsed and represented
* in the specified <tt>callPeer</tt>
*
* @throws XMLException If <tt>conferenceInfoXML</tt> couldn't be parsed as
* a <tt>ConferenceInfoDocument</tt>
*/
protected int setConferenceInfoXML(
MediaAwareCallPeerT callPeer,
int version,
String conferenceInfoXML)
throws XMLException
{
byte[] bytes;
ConferenceInfoDocument confInfo
= new ConferenceInfoDocument(conferenceInfoXML);
try
/*
* The CallPeer sent conference-info XML so we're sure it's a
* conference focus.
*/
callPeer.setConferenceFocus(true);
/*
* The following implements the procedure outlined in section 4.6 of
* RFC4575 - Constructing Coherent State
*/
int documentVersion = confInfo.getVersion();
int ourVersion = callPeer.getLastConferenceInfoReceivedVersion();
ConferenceInfoDocument.State documentState = confInfo.getState();
if (ourVersion == -1)
{
bytes = conferenceInfoXML.getBytes("UTF-8");
if (documentState == ConferenceInfoDocument.State.FULL)
{
return setConferenceInfoDocument(callPeer, confInfo);
}
else
{
logger.warn("Received a conference-info document with state '"
+ documentState + "'. Cannot apply it, because we haven't"
+ "initialized a local document yet. Sending peer: "
+ callPeer);
return -1;
}
}
catch (UnsupportedEncodingException uee)
else if (documentVersion <= ourVersion)
{
logger
.warn(
"Failed to gets bytes from String for the UTF-8 charset",
uee);
bytes = conferenceInfoXML.getBytes();
if (logger.isInfoEnabled())
{
logger.info("Received a stale conference-info document. Local "
+ "version " + ourVersion + ", document version "
+ documentVersion + ". Sending peer: " + callPeer);
}
return -1;
}
else //ourVersion != -1 && ourVersion < documentVersion
{
if (documentState == ConferenceInfoDocument.State.FULL)
return setConferenceInfoDocument(callPeer, confInfo);
else if (documentState == ConferenceInfoDocument.State.DELETED)
{
logger.warn("Received a conference-info document with state" +
"'deleted', can't handle. Sending peer: " + callPeer);
return -1;
}
else if (documentState == ConferenceInfoDocument.State.PARTIAL)
{
if (documentVersion == ourVersion+1)
return updateConferenceInfoDocument(callPeer, confInfo);
else
{
/*
* According to RFC4575 we "MUST generate a subscription
* refresh request to trigger a full state notification".
*/
logger.warn("Received a Conference Information document "
+ "with state '" + documentState + "' and version "
+ documentVersion + ". Cannon apply it, because local "
+ "version is " + ourVersion + ". Sending peer: "
+ callPeer);
return -1;
}
}
else
return -1; //unreachable
}
}
/**
* Removes the parameters (specified after a semicolon) from a specific
* address <tt>String</tt> if any are present in it.
*
* @param address the <tt>String</tt> value representing an address from
* which any parameters are to be removed
* @return a <tt>String</tt> representing the specified <tt>address</tt>
* without any parameters
*/
public static String stripParametersFromAddress(String address)
{
if (address != null)
{
int parametersBeginIndex = address.indexOf(';');
Document doc = null;
Throwable exception = null;
if (parametersBeginIndex > -1)
address = address.substring(0, parametersBeginIndex);
}
return address;
}
/**
* Creates a <tt>ConferenceInfoDocument</tt> which describes the current
* state of the conference in which <tt>callPeer</tt> participates. The
* created document contains a "full" description (as opposed to a partial
* description, see RFC4575).
*
* @return a <tt>ConferenceInfoDocument</tt> which describes the current
* state of the conference in which this <tt>CallPeer</tt> participates.
*/
protected ConferenceInfoDocument getCurrentConferenceInfo(
MediaAwareCallPeer<?,?,?> callPeer)
{
ConferenceInfoDocument confInfo;
try
{
doc
= DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new ByteArrayInputStream(bytes));
confInfo = new ConferenceInfoDocument();
}
catch (IOException ioe)
catch (XMLException e)
{
exception = ioe;
return null;
}
catch (ParserConfigurationException pce)
confInfo.setState(ConferenceInfoDocument.State.FULL);
confInfo.setEntity(getLocalEntity(callPeer));
Call call = callPeer.getCall();
List<CallPeer> conferenceCallPeers = CallConference.getCallPeers(call);
confInfo.setUserCount(
1 /* the local peer/user */ + conferenceCallPeers.size());
/* The local user */
addPeerToConferenceInfo(confInfo, callPeer, false);
/* Remote users */
for (CallPeer conferenceCallPeer : conferenceCallPeers)
{
exception = pce;
if (conferenceCallPeer instanceof MediaAwareCallPeer<?,?,?>)
addPeerToConferenceInfo(
confInfo,
(MediaAwareCallPeer<?,?,?>)conferenceCallPeer,
true);
}
catch (SAXException saxe)
return confInfo;
}
/**
* Adds a <tt>user</tt> element to <tt>confInfo</tt> which describes
* <tt>callPeer</tt>, or the local peer if <tt>remote</tt> is <tt>false</tt>.
*
* @param confInfo the <tt>ConferenceInformationDocument</tt> to which to
* add a <tt>user</tt> element
* @param callPeer the <tt>CallPeer</tt> which should be described
* @param remote <tt>true</tt> to describe <tt>callPeer</tt>, or
* <tt>false</tt> to describe the local peer.
*/
private void addPeerToConferenceInfo(
ConferenceInfoDocument confInfo,
MediaAwareCallPeer<?,?,?> callPeer,
boolean remote)
{
String entity
= remote
? callPeer.getEntity()
: getLocalEntity(callPeer);
ConferenceInfoDocument.User user = confInfo.addNewUser(entity);
String displayName
= remote
? callPeer.getDisplayName()
: getLocalDisplayName();
user.setDisplayText(displayName);
ConferenceInfoDocument.Endpoint endpoint
= user.addNewEndpoint(entity);
endpoint.setStatus(
remote
? getEndpointStatus(callPeer)
: ConferenceInfoDocument.EndpointStatusType.connected);
CallPeerMediaHandler<?> mediaHandler
= callPeer.getMediaHandler();
for (MediaType mediaType : MediaType.values())
{
exception = saxe;
MediaStream stream = mediaHandler.getStream(mediaType);
if (stream != null)
{
ConferenceInfoDocument.Media media
= endpoint.addNewMedia(mediaType.toString());
long srcId
= remote
? getRemoteSourceID(callPeer, mediaType)
: stream.getLocalSourceID();
if (srcId != -1)
media.setSrcId(Long.toString(srcId));
media.setType(mediaType.toString());
MediaDirection direction
= remote
? getRemoteDirection(callPeer, mediaType)
: stream.getDirection();
if (direction == null)
direction = MediaDirection.INACTIVE;
media.setStatus(direction.toString());
}
}
if (exception != null)
logger.error("Failed to parse conference-info XML", exception);
else
}
/**
* Returns a string to be used for the <tt>entity</tt> attribute of the
* <tt>user</tt> element for the local peer, in a Conference Information
* document to be sent to <tt>callPeer</tt>
*
* @param callPeer The <tt>CallPeer</tt> for which we are creating a
* Conference Information document.
* @return a string to be used for the <tt>entity</tt> attribute of the
* <tt>user</tt> element for the local peer, in a Conference Information
* document to be sent to <tt>callPeer</tt>
*/
protected abstract String getLocalEntity(CallPeer callPeer);
/**
* Returns the display name for the local peer, which is to be used when
* we send Conference Information.
* @return the display name for the local peer, which is to be used when
* we send Conference Information.
*/
protected abstract String getLocalDisplayName();
/**
* Gets the <tt>EndpointStatusType</tt> to use when describing
* <tt>callPeer</tt> in a Conference Information document.
*
* @param callPeer the <tt>CallPeer</tt> which is to get its state described
* in a <tt>status</tt> XML element of an <tt>endpoint</tt> XML element
* @return the <tt>EndpointStatusType</tt> to use when describing
* <tt>callPeer</tt> in a Conference Information document.
*/
private ConferenceInfoDocument.EndpointStatusType getEndpointStatus(
CallPeer callPeer)
{
CallPeerState callPeerState = callPeer.getState();
if (CallPeerState.ALERTING_REMOTE_SIDE.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.alerting;
if (CallPeerState.CONNECTING.equals(callPeerState)
|| CallPeerState
.CONNECTING_WITH_EARLY_MEDIA.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.pending;
if (CallPeerState.DISCONNECTED.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.disconnected;
if (CallPeerState.INCOMING_CALL.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.dialing_in;
if (CallPeerState.INITIATING_CALL.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.dialing_out;
/*
* he/she is neither "hearing" the conference mix nor is his/her
* media being mixed in the conference
*/
if (CallPeerState.ON_HOLD_LOCALLY.equals(callPeerState)
|| CallPeerState.ON_HOLD_MUTUALLY.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.on_hold;
if (CallPeerState.CONNECTED.equals(callPeerState))
return ConferenceInfoDocument.EndpointStatusType.connected;
return null;
}
/**
* @param from A document with state <tt>full</tt> from which to generate a
* "diff".
* @param to A document with state <tt>full</tt> to which to generate a
* "diff"
* @return a <tt>ConferenceInfoDocument</tt>, such that when it is applied
* to <tt>from</tt> using the procedure defined in section 4.6 of RFC4575,
* the result is <tt>to</tt>. May return <tt>null</tt> if <tt>from</tt> and
* <tt>to</tt> are not found to be different (that is, in case no document
* needs to be sent)
*/
protected ConferenceInfoDocument getConferenceInfoDiff(
ConferenceInfoDocument from,
ConferenceInfoDocument to)
throws IllegalArgumentException
{
if (from.getState() != ConferenceInfoDocument.State.FULL)
throw new IllegalArgumentException("The 'from' document needs to "
+ "have state=full");
if (to.getState() != ConferenceInfoDocument.State.FULL)
throw new IllegalArgumentException("The 'to' document needs to "
+ "have state=full");
if (conferenceInfoDocumentsMatch(from, to))
return null;
return to;
}
/**
* Updates the conference-related properties of a specific <tt>CallPeer</tt>
* such as <tt>conferenceFocus</tt> and <tt>conferenceMembers</tt> with
* information received from it as a conference focus in the form of a
* partial conference-info XML document.
*
* @param callPeer the <tt>CallPeer</tt> which is a conference focus and has
* sent the specified partial conference-info XML document
* @param diff the partial conference-info XML document sent by
* <tt>callPeer</tt> in order to update the conference-related information
* of the local peer represented by the associated <tt>Call</tt>
* @return the value of the <tt>version</tt> attribute of the
* <tt>conference-info</tt> XML element of the specified
* <tt>conferenceInfoXML</tt> if it was successfully parsed and represented
* in the specified <tt>callPeer</tt>
*/
private int updateConferenceInfoDocument(
MediaAwareCallPeerT callPeer,
ConferenceInfoDocument diff)
{
logger.warn("Received a conference-info partial notification, which we"
+ " can't handle. Sending peer: " + callPeer);
if (true)
return -1;
ConferenceInfoDocument ourDocument
= callPeer.getLastConferenceInfoReceived();
ConferenceInfoDocument newDocument;
ConferenceInfoDocument.State usersState = diff.getUsersState();
if (usersState == ConferenceInfoDocument.State.FULL)
{
/*
* The CallPeer sent conference-info XML so we're sure it's a
* conference focus.
*/
callPeer.setConferenceFocus(true);
//if users is 'full', all its children must be full
newDocument = diff;
newDocument.setState(ConferenceInfoDocument.State.FULL);
}
else if (usersState == ConferenceInfoDocument.State.DELETED)
{
try
{
newDocument = new ConferenceInfoDocument();
}
catch (XMLException e)
{
logger.warn("Could not create a new ConferenceInfoDocument", e);
return -1;
}
newDocument.setVersion(diff.getVersion());
newDocument.setEntity(diff.getEntity());
newDocument.setUserCount(diff.getUserCount());
}
else //'partial'
{
newDocument = ourDocument;
int documentVersion
= Integer.parseInt(
doc.getDocumentElement().getAttribute("version"));
newDocument.setVersion(diff.getVersion());
newDocument.setEntity(diff.getEntity());
newDocument.setUserCount(diff.getUserCount());
if ((version == -1) || (documentVersion >= version))
for (ConferenceInfoDocument.User user : diff.getUsers())
{
setConferenceInfoDocument(callPeer, doc);
return documentVersion;
ConferenceInfoDocument.State userState = user.getState();
if (userState == ConferenceInfoDocument.State.FULL)
{
//copy the whole thing from diff to newDocument
}
else if (userState == ConferenceInfoDocument.State.DELETED)
{
newDocument.removeUser(user.getEntity());
}
else
{
ConferenceInfoDocument.User ourUser
= newDocument.getUser(user.getEntity());
for (ConferenceInfoDocument.Endpoint endpoint
: user.getEndpoints())
{
ConferenceInfoDocument.State endpointState
= endpoint.getState();
if (endpointState == ConferenceInfoDocument.State.FULL)
{
//update the whole thing
}
else if (endpointState
== ConferenceInfoDocument.State.DELETED)
{
ourUser.removeEndpoint(endpoint.getEntity());
}
else //'partial'
{
for (ConferenceInfoDocument.Media media
: endpoint.getMedias())
{
//copy media with id media.getId()
}
}
}
}
}
}
return -1;
}
/**
* Removes the parameters (specified after a semicolon) from a specific
* address <tt>String</tt> if any are present in it.
* @param a A document with state <tt>full</tt> which to compare to
* <tt>b</tt>
* @param b A document with state <tt>full</tt> which to compare to
* <tt>a</tt>
* @return <tt>false</tt> if the two documents are found to be different,
* <tt>true</tt> otherwise (that is, it can return true for non-identical
* documents).
*/
private boolean conferenceInfoDocumentsMatch(
ConferenceInfoDocument a,
ConferenceInfoDocument b)
{
if (a.getState() != ConferenceInfoDocument.State.FULL)
throw new IllegalArgumentException("The 'a' document needs to"
+ "have state=full");
if (b.getState() != ConferenceInfoDocument.State.FULL)
throw new IllegalArgumentException("The 'b' document needs to"
+ "have state=full");
if (!stringsMatch(a.getEntity(), b.getEntity()))
return false;
else if (a.getUserCount() != b.getUserCount())
return false;
else if (a.getUsers().size() != b.getUsers().size())
return false;
for(ConferenceInfoDocument.User aUser : a.getUsers())
{
if (!usersMatch(aUser, b.getUser(aUser.getEntity())))
return false;
}
return true;
}
/**
* Checks whether two <tt>ConferenceInfoDocument.User</tt> instances
* match according to the needs of our implementation. Can return
* <tt>true</tt> for users which are not identical.
*
* @param address the <tt>String</tt> value representing an address from
* which any parameters are to be removed
* @return a <tt>String</tt> representing the specified <tt>address</tt>
* without any parameters
* @param a A <tt>ConferenceInfoDocument.User</tt> to compare
* @param b A <tt>ConferenceInfoDocument.User</tt> to compare
* @return <tt>false</tt> if <tt>a</tt> and <tt>b</tt> are found to be
* different in a way that is significant for our needs, <tt>true</tt>
* otherwise.
*/
protected static String stripParametersFromAddress(String address)
private boolean usersMatch(
ConferenceInfoDocument.User a,
ConferenceInfoDocument.User b)
{
if (address != null)
if (a == null && b == null)
return true;
else if (a == null || b == null)
return false;
else if (!stringsMatch(a.getEntity(), b.getEntity()))
return false;
else if (!stringsMatch(a.getDisplayText(), b.getDisplayText()))
return false;
else if (a.getEndpoints().size() != b.getEndpoints().size())
return false;
for (ConferenceInfoDocument.Endpoint aEndpoint : a.getEndpoints())
{
int parametersBeginIndex = address.indexOf(';');
if (!endpointsMatch(aEndpoint, b.getEndpoint(aEndpoint.getEntity())))
return false;
}
if (parametersBeginIndex > -1)
address = address.substring(0, parametersBeginIndex);
return true;
}
/**
* Checks whether two <tt>ConferenceInfoDocument.Endpoint</tt> instances
* match according to the needs of our implementation. Can return
* <tt>true</tt> for endpoints which are not identical.
*
* @param a A <tt>ConferenceInfoDocument.Endpoint</tt> to compare
* @param b A <tt>ConferenceInfoDocument.Endpoint</tt> to compare
* @return <tt>false</tt> if <tt>a</tt> and <tt>b</tt> are found to be
* different in a way that is significant for our needs, <tt>true</tt>
* otherwise.
*/
private boolean endpointsMatch(
ConferenceInfoDocument.Endpoint a,
ConferenceInfoDocument.Endpoint b)
{
if (a == null && b == null)
return true;
else if (a == null || b == null)
return false;
else if (!stringsMatch(a.getEntity(), b.getEntity()))
return false;
else if (a.getStatus() != b.getStatus())
return false;
else if (a.getMedias().size() != b.getMedias().size())
return false;
for (ConferenceInfoDocument.Media aMedia : a.getMedias())
{
if (!mediasMatch(aMedia, b.getMedia(aMedia.getId())))
return false;
}
return address;
return true;
}
/**
* Checks whether two <tt>ConferenceInfoDocument.Media</tt> instances
* match according to the needs of our implementation. Can return
* <tt>true</tt> for endpoints which are not identical.
*
* @param a A <tt>ConferenceInfoDocument.Media</tt> to compare
* @param b A <tt>ConferenceInfoDocument.Media</tt> to compare
* @return <tt>false</tt> if <tt>a</tt> and <tt>b</tt> are found to be
* different in a way that is significant for our needs, <tt>true</tt>
* otherwise.
*/
private boolean mediasMatch(
ConferenceInfoDocument.Media a,
ConferenceInfoDocument.Media b)
{
if (a == null && b == null)
return true;
else if (a == null || b == null)
return false;
else if (!stringsMatch(a.getId(), b.getId()))
return false;
else if (!stringsMatch(a.getSrcId(), b.getSrcId()))
return false;
else if (!stringsMatch(a.getType(), b.getType()))
return false;
else if (!stringsMatch(a.getStatus(), b.getStatus()))
return false;
return true;
}
/**
* @param a A <tt>String</tt> to compare to <tt>b</tt>
* @param b A <tt>String</tt> to compare to <tt>a</tt>
* @return <tt>true</tt> if and only if <tt>a</tt> and <tt>b</tt> are both
* <tt>null</tt>, or they are equal as <tt>String</tt>s
*/
private boolean stringsMatch(String a, String b)
{
if (a == null && b == null)
return true;
else if (a == null || b == null)
return false;
return a.equals(b);
}
}

@ -33,6 +33,7 @@
*
* @author Emil Ivov
* @author Lyubomir Marinov
* @author Boris Grozev
*/
public abstract class MediaAwareCallPeer
<T extends MediaAwareCall<?, ?, V>,
@ -121,11 +122,33 @@ public abstract class MediaAwareCallPeer
private final List<PropertyChangeListener> videoPropertyChangeListeners
= new LinkedList<PropertyChangeListener>();
/**
* Represents the last Conference Information (RFC4575) document sent to
* this <tt>CallPeer</tt>. This is always a document with state "full", even
* if the last document actually sent was a "partial"
*/
private ConferenceInfoDocument lastConferenceInfoSent = null;
/**
* The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
* a Conference Information (RFC4575) document was last sent to this
* <tt>CallPeer</tt>.
*/
private long lastConferenceInfoSentTimestamp = -1;
/**
* The last Conference Information (RFC4575) document sent to us by this
* <tt>CallPeer</tt>. This is always a document with state "full", which is
* only gets updated by "partial" or "deleted" documents.
*/
private ConferenceInfoDocument lastConferenceInfoReceived = null;
/**
* Creates a new call peer with address <tt>peerAddress</tt>.
*
* @param owningCall the call that contains this call peer.
*/
public MediaAwareCallPeer(T owningCall)
{
this.call = owningCall;
@ -1003,4 +1026,97 @@ public void setState(CallPeerState newState, String reason, int reasonCode)
}
}
}
/**
* Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
* <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
* @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
* <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
*/
public ConferenceInfoDocument getLastConferenceInfoSent()
{
return lastConferenceInfoSent;
}
/**
* Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
* <tt>CallPeer</tt>.
* @param confInfo the document to set.
*/
public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
{
lastConferenceInfoSent = confInfo;
}
/**
* Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
* at which we last sent a <tt>ConferenceInfoDocument</tt> to this
* <tt>CallPeer</tt>.
* @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
* at which we last sent a <tt>ConferenceInfoDocument</tt> to this
* <tt>CallPeer</tt>.
*/
public long getLastConferenceInfoSentTimestamp()
{
return lastConferenceInfoSentTimestamp;
}
/**
* Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
* at which we last sent a <tt>ConferenceInfoDocument</tt> to this
* <tt>CallPeer</tt>.
* @param newTimestamp the time to set
*/
public void setLastConferenceInfoSentTimestamp(long newTimestamp)
{
lastConferenceInfoSentTimestamp = newTimestamp;
}
/**
* Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
* <tt>CallPeer</tt>.
* @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
* <tt>CallPeer</tt>.
*/
public ConferenceInfoDocument getLastConferenceInfoReceived()
{
return lastConferenceInfoReceived;
}
/**
* Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
* <tt>CallPeer</tt>.
* @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
* <tt>CallPeer</tt>.
*/
public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
{
lastConferenceInfoReceived = confInfo;
}
/**
* Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
* sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
* a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
* @return
*/
public int getLastConferenceInfoReceivedVersion()
{
return (lastConferenceInfoReceived == null)
? -1
: lastConferenceInfoReceived.getVersion();
}
/**
* Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
* we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
* <tt>entity</tt> key attribute which to use for the <tt>user</tt>
* element corresponding to this <tt>CallPeer</tt>)
*
* @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
* we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
* <tt>entity</tt> key attribute which to use for the <tt>user</tt>
* element corresponding to this <tt>CallPeer</tt>)
*/
public abstract String getEntity();
}

@ -5,6 +5,9 @@ Bundle-Vendor: jitsi.org
Bundle-Version: 0.0.1
System-Bundle: yes
Import-Package: javax.xml.parsers,
javax.xml.transform,
javax.xml.transform.dom,
javax.xml.transform.stream,
net.java.sip.communicator.service.netaddr,
net.java.sip.communicator.service.protocol,
net.java.sip.communicator.service.protocol.event,
@ -20,6 +23,7 @@ Import-Package: javax.xml.parsers,
org.jitsi.service.protocol,
org.jitsi.util,
org.jitsi.util.event,
org.jitsi.util.xml,
org.osgi.framework,
org.w3c.dom,
org.xml.sax

Loading…
Cancel
Save