aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/protocol/jabber
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/sip/communicator/impl/protocol/jabber')
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java3287
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java44
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java21
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java141
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java4
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java21
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java8
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java13
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java7
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java223
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java10
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java4
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java17
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java1166
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java560
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java10
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java229
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java1054
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java13
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java91
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java1922
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java11
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java113
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java10
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java629
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java69
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java131
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java144
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java134
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQ.java (renamed from src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/GracefulShutdownIQ.java)21
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java94
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java413
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java112
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java121
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java126
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java93
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java878
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java12
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java21
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java25
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java196
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java135
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java15
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java70
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java9
52 files changed, 7359 insertions, 5082 deletions
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java
index c955100..b62d01a 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/AnonymousLoginStrategy.java
@@ -70,7 +70,7 @@ public class AnonymousLoginStrategy
}
@Override
- public boolean login(XMPPConnection connection, String userName,
+ public boolean login(Connection connection, String userName,
String resource)
throws XMPPException
{
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java
index e23481a..3987ea8 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java
@@ -311,7 +311,7 @@ public class CallJabberImpl
contentRequest.addChannel(remoteChannelRequest);
}
- XMPPConnection connection = protocolProvider.getConnection();
+ Connection connection = protocolProvider.getConnection();
PacketCollector packetCollector
= connection.createPacketCollector(
new PacketIDFilter(conferenceRequest.getPacketID()));
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java
index 64eb5fa..c257202 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,1647 +15,1644 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.SendersEnum;
-import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.util.*;
-import org.jivesoftware.smackx.packet.*;
-
-/**
- * Implements a Jabber <tt>CallPeer</tt>.
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public class CallPeerJabberImpl
- extends AbstractCallPeerJabberGTalkImpl
- <CallJabberImpl, CallPeerMediaHandlerJabberImpl, JingleIQ>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(CallPeerJabberImpl.class);
-
- /**
- * If the call is cancelled before session-initiate is sent.
- */
- private boolean cancelled = false;
-
- /**
- * Synchronization object for candidates available.
- */
- private final Object candSyncRoot = new Object();
-
- /**
- * If the content-add does not contains candidates.
- */
- private boolean contentAddWithNoCands = false;
-
- /**
- * If we have processed the session initiate.
- */
- private boolean sessionInitiateProcessed = false;
-
- /**
- * Synchronization object. Synchronization object? Wow, who would have
- * thought! ;) Would be great to have a word on what we are syncing with it
- */
- private final Object sessionInitiateSyncRoot = new Object();
-
- /**
- * Synchronization object for SID.
- */
- private final Object sidSyncRoot = new Object();
-
- /**
- * The current value of the 'senders' field of the audio content in the
- * Jingle session with this <tt>CallPeer</tt>.
- * <tt>null</tt> should be interpreted as 'both', which is the default in
- * Jingle if the XML attribute is missing.
- */
- private SendersEnum audioSenders = SendersEnum.none;
-
- /**
- * The current value of the 'senders' field of the video content in the
- * Jingle session with this <tt>CallPeer</tt>.
- * <tt>null</tt> should be interpreted as 'both', which is the default in
- * Jingle if the XML attribute is missing.
- */
- private SendersEnum videoSenders = SendersEnum.none;
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param peerAddress the Jabber address of the new call peer.
- * @param owningCall the call that contains this call peer.
- */
- public CallPeerJabberImpl(String peerAddress,
- CallJabberImpl owningCall)
- {
- super(peerAddress, owningCall);
-
- setMediaHandler(new CallPeerMediaHandlerJabberImpl(this));
- }
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param peerAddress the Jabber address of the new call peer.
- * @param owningCall the call that contains this call peer.
- * @param sessionIQ The session-initiate <tt>JingleIQ</tt> which was
- * received from <tt>peerAddress</tt> and caused the creation of this
- * <tt>CallPeerJabberImpl</tt>
- */
- public CallPeerJabberImpl(String peerAddress,
- CallJabberImpl owningCall,
- JingleIQ sessionIQ)
- {
- this(peerAddress, owningCall);
- this.sessionInitIQ = sessionIQ;
- }
-
- /**
- * Send a session-accept <tt>JingleIQ</tt> to this <tt>CallPeer</tt>
- * @throws OperationFailedException if we fail to create or send the
- * response.
- */
- public synchronized void answer()
- throws OperationFailedException
- {
- Iterable<ContentPacketExtension> answer;
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- try
- {
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- answer = mediaHandler.generateSessionAccept();
- for (ContentPacketExtension c : answer)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch(Exception exc)
- {
- logger.info("Failed to answer an incoming call", exc);
-
- //send an error response
- String reasonText = "Error: " + exc.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.FAILED_APPLICATION,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- JingleIQ response
- = JinglePacketFactory.createSessionAccept(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- getSID(),
- answer);
-
- //send the packet first and start the stream later in case the media
- //relay needs to see it before letting hole punching techniques through.
- getProtocolProvider().getConnection().sendPacket(response);
-
- try
- {
- mediaHandler.start();
- }
- catch(UndeclaredThrowableException e)
- {
- Throwable exc = e.getUndeclaredThrowable();
-
- logger.info("Failed to establish a connection", exc);
-
- //send an error response
- String reasonText = "Error: " + exc.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.GENERAL_ERROR,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- //tell everyone we are connected so that the audio notifications would
- //stop
- setState(CallPeerState.CONNECTED);
- }
-
- /**
- * Returns the session ID of the Jingle session associated with this call.
- *
- * @return the session ID of the Jingle session associated with this call.
- */
- @Override
- public String getSID()
- {
- return sessionInitIQ != null ? sessionInitIQ.getSID() : null;
- }
-
- /**
- * Returns the IQ ID of the Jingle session-initiate packet associated with
- * this call.
- *
- * @return the IQ ID of the Jingle session-initiate packet associated with
- * this call.
- */
- public JingleIQ getSessionIQ()
- {
- return sessionInitIQ;
- }
-
- /**
- * Ends the call with this <tt>CallPeer</tt>. Depending on the state
- * of the peer the method would send a CANCEL, BYE, or BUSY_HERE message
- * and set the new state to DISCONNECTED.
- *
- * @param failed indicates if the hangup is following to a call failure or
- * simply a disconnect
- * @param reasonText the text, if any, to be set on the
- * <tt>ReasonPacketExtension</tt> as the value of its
- * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be
- * set on the <tt>ReasonPacketExtension</tt> as the value of its
- * <tt>otherExtension</tt> property
- */
- public void hangup(boolean failed,
- String reasonText,
- PacketExtension reasonOtherExtension)
- {
- CallPeerState prevPeerState = getState();
-
- // do nothing if the call is already ended
- if (CallPeerState.DISCONNECTED.equals(prevPeerState)
- || CallPeerState.FAILED.equals(prevPeerState))
- {
- if (logger.isDebugEnabled())
- logger.debug("Ignoring a request to hangup a call peer "
- + "that is already DISCONNECTED");
- return;
- }
-
- setState(
- failed ? CallPeerState.FAILED : CallPeerState.DISCONNECTED,
- reasonText);
-
- JingleIQ responseIQ = null;
-
- if (prevPeerState.equals(CallPeerState.CONNECTED)
- || CallPeerState.isOnHold(prevPeerState))
- {
- responseIQ = JinglePacketFactory.createBye(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (CallPeerState.CONNECTING.equals(prevPeerState)
- || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
- || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState))
- {
- String jingleSID = getSID();
-
- if(jingleSID == null)
- {
- synchronized(sidSyncRoot)
- {
- // we cancelled the call too early because the jingleSID
- // is null (i.e. the session-initiate has not been created)
- // and no need to send the session-terminate
- cancelled = true;
- return;
- }
- }
-
- responseIQ = JinglePacketFactory.createCancel(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (prevPeerState.equals(CallPeerState.INCOMING_CALL))
- {
- responseIQ = JinglePacketFactory.createBusy(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (prevPeerState.equals(CallPeerState.BUSY)
- || prevPeerState.equals(CallPeerState.FAILED))
- {
- // For FAILED and BUSY we only need to update CALL_STATUS
- // as everything else has been done already.
- }
- else
- {
- logger.info("Could not determine call peer state!");
- }
-
- if (responseIQ != null)
- {
- if (reasonOtherExtension != null)
- {
- ReasonPacketExtension reason
- = (ReasonPacketExtension)
- responseIQ.getExtension(
- ReasonPacketExtension.ELEMENT_NAME,
- ReasonPacketExtension.NAMESPACE);
-
- if (reason != null)
- {
- reason.setOtherExtension(reasonOtherExtension);
- }
- else if(reasonOtherExtension instanceof ReasonPacketExtension)
- {
- responseIQ.setReason(
- (ReasonPacketExtension)reasonOtherExtension);
- }
- }
-
- getProtocolProvider().getConnection().sendPacket(responseIQ);
- }
- }
-
- /**
- * Creates and sends a session-initiate {@link JingleIQ}.
- *
- * @param sessionInitiateExtensions a collection of additional and optional
- * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
- * {@link JingleIQ} which is to initiate the session with this
- * <tt>CallPeerJabberImpl</tt>
- * @throws OperationFailedException exception
- */
- protected synchronized void initiateSession(
- Iterable<PacketExtension> sessionInitiateExtensions)
- throws OperationFailedException
- {
- initiator = false;
-
- //Create the media description that we'd like to send to the other side.
- List<ContentPacketExtension> offer
- = getMediaHandler().createContentList();
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- synchronized(sidSyncRoot)
- {
- sessionInitIQ
- = JinglePacketFactory.createSessionInitiate(
- protocolProvider.getOurJID(),
- this.peerJID,
- JingleIQ.generateSID(),
- offer);
-
- if(cancelled)
- {
- // we cancelled the call too early so no need to send the
- // session-initiate to peer
- getMediaHandler().getTransportManager().close();
- return;
- }
- }
-
- if (sessionInitiateExtensions != null)
- {
- for (PacketExtension sessionInitiateExtension
- : sessionInitiateExtensions)
- {
- sessionInitIQ.addExtension(sessionInitiateExtension);
- }
- }
-
- protocolProvider.getConnection().sendPacket(sessionInitIQ);
- }
-
- /**
- * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
- * been received. This <tt>CallPeerJabberImpl</tt> uses the part of the
- * information provided in the specified <tt>conferenceIQ</tt> which
- * concerns it only.
- *
- * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
- * received
- */
- void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * CallPeerJabberImpl does not itself/directly know the specifics
- * related to the channels allocated on the Jitsi Videobridge server.
- * The channels contain transport and media-related information so
- * forward the notification to CallPeerMediaHandlerJabberImpl.
- */
- getMediaHandler().processColibriConferenceIQ(conferenceIQ);
- }
-
- /**
- * Processes the content-accept {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer has accepted
- */
- public void processContentAccept(JingleIQ content)
- {
- List<ContentPacketExtension> contents = content.getContentList();
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- try
- {
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- mediaHandler.processAnswer(contents);
- for (ContentPacketExtension c : contents)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch (Exception e)
- {
- logger.warn("Failed to process a content-accept", e);
-
- // Send an error response.
- String reason = "Error: " + e.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reason);
-
- setState(CallPeerState.FAILED, reason);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- mediaHandler.start();
- }
-
- /**
- * Processes the content-add {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be added
- */
- public void processContentAdd(final JingleIQ content)
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- List<ContentPacketExtension> contents = content.getContentList();
- Iterable<ContentPacketExtension> answerContents;
- JingleIQ contentIQ;
- boolean noCands = false;
- MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO);
-
- if(logger.isInfoEnabled())
- logger.info("Looking for candidates in content-add.");
- try
- {
- if(!contentAddWithNoCands)
- {
- mediaHandler.processOffer(contents);
-
- /*
- * Gingle transport will not put candidate in session-initiate
- * and content-add.
- */
- for(ContentPacketExtension c : contents)
- {
- if(JingleUtils.getFirstCandidate(c, 1) == null)
- {
- contentAddWithNoCands = true;
- noCands = true;
- }
- }
- }
-
- // if no candidates are present, launch a new Thread which will
- // process and wait for the connectivity establishment (otherwise
- // the existing thread will be blocked and thus cannot receive
- // transport-info with candidates
- if(noCands)
- {
- new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- synchronized(candSyncRoot)
- {
- candSyncRoot.wait();
- }
- }
- catch(InterruptedException e)
- {
- }
-
- processContentAdd(content);
- contentAddWithNoCands = false;
- }
- }.start();
- if(logger.isInfoEnabled())
- logger.info("No candidates found in content-add, started "
- + "new thread.");
- return;
- }
-
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- if(logger.isInfoEnabled())
- logger.info("Wrapping up connectivity establishment");
- answerContents = mediaHandler.generateSessionAccept();
- contentIQ = null;
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred", e);
-
- answerContents = null;
- contentIQ
- = JinglePacketFactory.createContentReject(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID(),
- answerContents);
- }
-
- if(contentIQ == null)
- {
- /* send content-accept */
- contentIQ
- = JinglePacketFactory.createContentAccept(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID(),
- answerContents);
- for (ContentPacketExtension c : answerContents)
- setSenders(getMediaType(c), c.getSenders());
- }
-
- getProtocolProvider().getConnection().sendPacket(contentIQ);
- mediaHandler.start();
-
- /*
- * If a remote peer turns her video on in a conference which is hosted
- * by the local peer and the local peer is not streaming her local
- * video, reinvite the other remote peers to enable RTP translation.
- */
- if (oldVideoStream == null)
- {
- MediaStream newVideoStream
- = mediaHandler.getStream(MediaType.VIDEO);
-
- if ((newVideoStream != null)
- && mediaHandler.isRTPTranslationEnabled(MediaType.VIDEO))
- {
- try
- {
- getCall().modifyVideoContent();
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Failed to enable RTP translation", ofe);
- }
- }
- }
- }
-
- /**
- * Processes the content-modify {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be modified
- */
- public void processContentModify(JingleIQ content)
- {
- ContentPacketExtension ext = content.getContentList().get(0);
- MediaType mediaType = getMediaType(ext);
-
- try
- {
- boolean modify
- = (ext.getFirstChildOfType(RtpDescriptionPacketExtension.class)
- != null);
-
- getMediaHandler().reinitContent(ext.getName(), ext, modify);
-
- setSenders(mediaType, ext.getSenders());
-
- if (MediaType.VIDEO.equals(mediaType))
- getCall().modifyVideoContent();
- }
- catch(Exception e)
- {
- logger.info("Failed to process an incoming content-modify", e);
-
- // Send an error response.
- String reason = "Error: " + e.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reason);
-
- setState(CallPeerState.FAILED, reason);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
- }
-
- /**
- * Processes the content-reject {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ}
- */
- public void processContentReject(JingleIQ content)
- {
- if(content.getContentList().isEmpty())
- {
- //send an error response;
- JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
- "Error: content rejected");
-
- setState(CallPeerState.FAILED, "Error: content rejected");
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
- }
-
- /**
- * Processes the content-remove {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be removed
- */
- public void processContentRemove(JingleIQ content)
- {
- List<ContentPacketExtension> contents = content.getContentList();
- boolean videoContentRemoved = false;
-
- if (!contents.isEmpty())
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- for(ContentPacketExtension c : contents)
- {
- mediaHandler.removeContent(c.getName());
-
- MediaType mediaType = getMediaType(c);
- setSenders(mediaType, SendersEnum.none);
-
- if (MediaType.VIDEO.equals(mediaType))
- videoContentRemoved = true;
- }
-
- /*
- * TODO XEP-0166: Jingle says: If the content-remove results in zero
- * content definitions for the session, the entity that receives the
- * content-remove SHOULD send a session-terminate action to the
- * other party (since a session with no content definitions is
- * void).
- */
- }
-
- if (videoContentRemoved)
- {
- // removing of the video content might affect the other sessions
- // in the call
- try
- {
- getCall().modifyVideoContent();
- }
- catch (Exception e)
- {
- logger.warn("Failed to update Jingle sessions");
- }
- }
- }
-
- /**
- * Processes a session-accept {@link JingleIQ}.
- *
- * @param sessionInitIQ The session-accept {@link JingleIQ} to process.
- */
- public void processSessionAccept(JingleIQ sessionInitIQ)
- {
- this.sessionInitIQ = sessionInitIQ;
-
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- List<ContentPacketExtension> answer = sessionInitIQ.getContentList();
-
- try
- {
- mediaHandler
- .getTransportManager()
- .wrapupConnectivityEstablishment();
- mediaHandler.processAnswer(answer);
- for (ContentPacketExtension c : answer)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch(Exception exc)
- {
- if (logger.isInfoEnabled())
- logger.info("Failed to process a session-accept", exc);
-
- //send an error response;
- JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
- exc.getClass().getName() + ": " + exc.getMessage());
-
- setState(CallPeerState.FAILED, "Error: " + exc.getMessage());
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- //tell everyone we are connected so that the audio notifications would
- //stop
- setState(CallPeerState.CONNECTED);
-
- mediaHandler.start();
-
- /*
- * If video was added to the call after we sent the session-initiate
- * to this peer, it needs to be added to this peer's session with a
- * content-add.
- */
- sendModifyVideoContent();
- }
-
- /**
- * Handles the specified session <tt>info</tt> packet according to its
- * content.
- *
- * @param info the {@link SessionInfoPacketExtension} that we just received.
- */
- public void processSessionInfo(SessionInfoPacketExtension info)
- {
- switch (info.getType())
- {
- case ringing:
- setState(CallPeerState.ALERTING_REMOTE_SIDE);
- break;
- case hold:
- getMediaHandler().setRemotelyOnHold(true);
- reevalRemoteHoldStatus();
- break;
- case unhold:
- case active:
- getMediaHandler().setRemotelyOnHold(false);
- reevalRemoteHoldStatus();
- break;
- default:
- logger.warn("Received SessionInfoPacketExtension of unknown type");
- }
- }
-
- /**
- * Processes the session initiation {@link JingleIQ} that we were created
- * with, passing its content to the media handler and then sends either a
- * "session-info/ringing" or a "session-terminate" response.
- *
- * @param sessionInitIQ The {@link JingleIQ} that created the session that
- * we are handling here.
- */
- protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ)
- {
- // Do initiate the session.
- this.sessionInitIQ = sessionInitIQ;
- this.initiator = true;
-
- // This is the SDP offer that came from the initial session-initiate.
- // Contrary to SIP, we are guaranteed to have content because XEP-0166
- // says: "A session consists of at least one content type at a time."
- List<ContentPacketExtension> offer = sessionInitIQ.getContentList();
-
- try
- {
- getMediaHandler().processOffer(offer);
-
- CoinPacketExtension coin = null;
-
- for(PacketExtension ext : sessionInitIQ.getExtensions())
- {
- if(ext.getElementName().equals(
- CoinPacketExtension.ELEMENT_NAME))
- {
- coin = (CoinPacketExtension)ext;
- break;
- }
- }
-
- /* does the call peer acts as a conference focus ? */
- if(coin != null)
- {
- setConferenceFocus(Boolean.parseBoolean(
- (String)coin.getAttribute("isfocus")));
- }
- }
- catch(Exception ex)
- {
- logger.info("Failed to process an incoming session initiate", ex);
-
- //send an error response;
- String reasonText = "Error: " + ex.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- // If we do not get the info about the remote peer yet. Get it right
- // now.
- if(this.getDiscoveryInfo() == null)
- {
- String calleeURI = sessionInitIQ.getFrom();
- retrieveDiscoveryInfo(calleeURI);
- }
-
- //send a ringing response
- if (logger.isTraceEnabled())
- logger.trace("will send ringing response: ");
-
- getProtocolProvider().getConnection().sendPacket(
- JinglePacketFactory.createRinging(sessionInitIQ));
-
- synchronized(sessionInitiateSyncRoot)
- {
- sessionInitiateProcessed = true;
- sessionInitiateSyncRoot.notify();
- }
-
- //if this is a 3264 initiator, let's give them an early peek at our
- //answer so that they could start ICE (SIP-2-Jingle gateways won't
- //be able to send their candidates unless they have this)
- DiscoverInfo discoverInfo = getDiscoveryInfo();
- if ((discoverInfo != null)
- && discoverInfo.containsFeature(
- ProtocolProviderServiceJabberImpl.URN_IETF_RFC_3264))
- {
- getProtocolProvider().getConnection().sendPacket(
- JinglePacketFactory.createDescriptionInfo(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- getMediaHandler().getLocalContentList()));
- }
- }
-
- /**
- * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
- * reason to the user, if there is one.
- *
- * @param jingleIQ the {@link JingleIQ} that's terminating our session.
- */
- public void processSessionTerminate(JingleIQ jingleIQ)
- {
- String reasonStr = "Call ended by remote side.";
- ReasonPacketExtension reasonExt = jingleIQ.getReason();
-
- if(reasonExt != null)
- {
- Reason reason = reasonExt.getReason();
-
- if(reason != null)
- reasonStr += " Reason: " + reason.toString() + ".";
-
- String text = reasonExt.getText();
-
- if(text != null)
- reasonStr += " " + text;
- }
-
- setState(CallPeerState.DISCONNECTED, reasonStr);
- }
-
- /**
- * Processes a specific "XEP-0251: Jingle Session Transfer"
- * <tt>transfer</tt> packet (extension).
- *
- * @param transfer the "XEP-0251: Jingle Session Transfer" transfer packet
- * (extension) to process
- * @throws OperationFailedException if anything goes wrong while processing
- * the specified <tt>transfer</tt> packet (extension)
- */
- public void processTransfer(TransferPacketExtension transfer)
- throws OperationFailedException
- {
- String attendantAddress = transfer.getFrom();
-
- if (attendantAddress == null)
- {
- throw new OperationFailedException(
- "Session transfer must contain a \'from\' attribute value.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
-
- String calleeAddress = transfer.getTo();
-
- if (calleeAddress == null)
- {
- throw new OperationFailedException(
- "Session transfer must contain a \'to\' attribute value.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
-
- // Checks if the transfer remote peer is contained by the roster of this
- // account.
- Roster roster = getProtocolProvider().getConnection().getRoster();
- if(!roster.contains(StringUtils.parseBareAddress(calleeAddress)))
- {
- String failedMessage =
- "Transfer impossible:\n"
- + "Account roster does not contain transfer peer: "
- + StringUtils.parseBareAddress(calleeAddress);
- setState(CallPeerState.FAILED, failedMessage);
- logger.info(failedMessage);
- }
-
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = (OperationSetBasicTelephonyJabberImpl)
- getProtocolProvider()
- .getOperationSet(OperationSetBasicTelephony.class);
- CallJabberImpl calleeCall = new CallJabberImpl(basicTelephony);
- TransferPacketExtension calleeTransfer = new TransferPacketExtension();
- String sid = transfer.getSID();
-
- calleeTransfer.setFrom(attendantAddress);
- if (sid != null)
- {
- calleeTransfer.setSID(sid);
- calleeTransfer.setTo(calleeAddress);
- }
- basicTelephony.createOutgoingCall(
- calleeCall,
- calleeAddress,
- Arrays.asList(new PacketExtension[] { calleeTransfer }));
- }
-
- /**
- * Processes the <tt>transport-info</tt> {@link JingleIQ}.
- *
- * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process
- */
- public void processTransportInfo(JingleIQ jingleIQ)
- {
- /*
- * The transport-info action is used to exchange transport candidates so
- * it only concerns the mediaHandler.
- */
- try
- {
- if(isInitiator())
- {
- synchronized(sessionInitiateSyncRoot)
- {
- if(!sessionInitiateProcessed)
- {
- try
- {
- sessionInitiateSyncRoot.wait();
- }
- catch(InterruptedException e)
- {
- }
- }
- }
- }
-
- getMediaHandler().processTransportInfo(
- jingleIQ.getContentList());
- }
- catch (OperationFailedException ofe)
- {
- logger.warn("Failed to process an incoming transport-info", ofe);
-
- //send an error response
- String reasonText = "Error: " + ofe.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.GENERAL_ERROR,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
-
- return;
- }
-
- synchronized(candSyncRoot)
- {
- candSyncRoot.notify();
- }
- }
-
- /**
- * Puts the <tt>CallPeer</tt> represented by this instance on or off hold.
- *
- * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
- * <tt>false</tt>, otherwise
- *
- * @throws OperationFailedException if we fail to construct or send the
- * INVITE request putting the remote side on/off hold.
- */
- public void putOnHold(boolean onHold)
- throws OperationFailedException
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- mediaHandler.setLocallyOnHold(onHold);
-
- SessionInfoType type;
-
- if(onHold)
- type = SessionInfoType.hold;
- else
- {
- type = SessionInfoType.unhold;
- getMediaHandler().reinitAllContents();
- }
-
- //we are now on hold and need to realize this before potentially
- //spoiling it all with an exception while sending the packet :).
- reevalLocalHoldStatus();
-
- JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo(
- getProtocolProvider().getOurJID(),
- peerJID,
- getSID(),
- type);
-
- getProtocolProvider().getConnection().sendPacket(onHoldIQ);
- }
-
- /**
- * Send a <tt>content-add</tt> to add video setup.
- */
- private void sendAddVideoContent()
- {
- List<ContentPacketExtension> contents;
-
- try
- {
- contents = getMediaHandler().createContentList(MediaType.VIDEO);
- }
- catch(Exception exc)
- {
- logger.warn("Failed to gather content for video type", exc);
- return;
- }
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentAdd(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- contents);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- }
-
- /**
- * Sends a <tt>content</tt> message to reflect changes in the setup such as
- * the local peer/user becoming a conference focus.
- */
- public void sendCoinSessionInfo()
- {
- JingleIQ sessionInfoIQ
- = JinglePacketFactory.createSessionInfo(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID());
- CoinPacketExtension coinExt
- = new CoinPacketExtension(getCall().isConferenceFocus());
-
- sessionInfoIQ.addExtension(coinExt);
- getProtocolProvider().getConnection().sendPacket(sessionInfoIQ);
- }
-
- /**
- * Returns the <tt>MediaDirection</tt> that should be set for the content
- * of type <tt>mediaType</tt> in the Jingle session for this
- * <tt>CallPeer</tt>.
- * If we are the focus of a conference and are doing RTP translation,
- * takes into account the other <tt>CallPeer</tt>s in the <tt>Call</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> for which to return the
- * <tt>MediaDirection</tt>
- * @return the <tt>MediaDirection</tt> that should be used for the content
- * of type <tt>mediaType</tt> in the Jingle session for this
- * <tt>CallPeer</tt>.
- */
- private MediaDirection getDirectionForJingle(MediaType mediaType)
- {
- MediaDirection direction = MediaDirection.INACTIVE;
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- // If we are streaming media, the direction should allow sending
- if ( (MediaType.AUDIO == mediaType &&
- mediaHandler.isLocalAudioTransmissionEnabled()) ||
- (MediaType.VIDEO == mediaType &&
- isLocalVideoStreaming()))
- direction = direction.or(MediaDirection.SENDONLY);
-
- // If we are receiving media from this CallPeer, the direction should
- // allow receiving
- SendersEnum senders = getSenders(mediaType);
- if (senders == null || senders == SendersEnum.both ||
- (isInitiator() && senders == SendersEnum.initiator) ||
- (!isInitiator() && senders == SendersEnum.responder))
- direction = direction.or(MediaDirection.RECVONLY);
-
- // If we are the focus of a conference and we are receiving media from
- // another CallPeer in the same Call, the direction should allow sending
- CallJabberImpl call = getCall();
- if (call != null && call.isConferenceFocus())
- {
- for (CallPeerJabberImpl peer : call.getCallPeerList())
- {
- if (peer != this)
- {
- senders = peer.getSenders(mediaType);
- if (senders == null || senders == SendersEnum.both ||
- (peer.isInitiator()
- && senders == SendersEnum.initiator) ||
- (!peer.isInitiator()
- && senders == SendersEnum.responder))
- {
- direction = direction.or(MediaDirection.SENDONLY);
- break;
- }
- }
- }
- }
-
- return direction;
- }
-
- /**
- * Send, if necessary, a jingle <tt>content</tt> message to reflect change
- * in video setup. Whether the jingle session should have a video content,
- * and if so, the value of the <tt>senders</tt> field is determined
- * based on whether we are streaming local video and, if we are the focus
- * of a conference, on the other peers in the conference.
- * The message can be content-modify if video content exists (and the
- * <tt>senders</tt> field changes), content-add or content-remove.
- *
- * @return <tt>true</tt> if a jingle <tt>content</tt> message was sent.
- */
- public boolean sendModifyVideoContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- MediaDirection direction = getDirectionForJingle(MediaType.VIDEO);
-
- ContentPacketExtension remoteContent
- = mediaHandler.getLocalContent(MediaType.VIDEO.toString());
-
- if (remoteContent == null)
- {
- if (direction == MediaDirection.INACTIVE)
- {
- // no video content, none needed
- return false;
- }
- else
- {
- if (getState() == CallPeerState.CONNECTED)
- {
- if (logger.isInfoEnabled())
- logger.info("Adding video content for " + this);
- sendAddVideoContent();
- return true;
- }
- return false;
- }
- }
- else
- {
- if (direction == MediaDirection.INACTIVE)
- {
- sendRemoveVideoContent();
- return true;
- }
- }
-
- SendersEnum senders = getSenders(MediaType.VIDEO);
- if (senders == null)
- senders = SendersEnum.both;
-
- SendersEnum newSenders = SendersEnum.none;
- if (MediaDirection.SENDRECV == direction)
- newSenders = SendersEnum.both;
- else if (MediaDirection.RECVONLY == direction)
- newSenders = isInitiator()
- ? SendersEnum.initiator : SendersEnum.responder;
- else if (MediaDirection.SENDONLY == direction)
- newSenders = isInitiator()
- ? SendersEnum.responder : SendersEnum.initiator;
-
- /*
- * Send Content-Modify
- */
- ContentPacketExtension ext = new ContentPacketExtension();
- String remoteContentName = remoteContent.getName();
-
- ext.setSenders(newSenders);
- ext.setCreator(remoteContent.getCreator());
- ext.setName(remoteContentName);
-
- if (newSenders != senders)
- {
- if (logger.isInfoEnabled())
- logger.info("Sending content modify, senders: "
- + senders + "->" + newSenders);
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentModify(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- ext);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- }
-
- try
- {
- mediaHandler.reinitContent(remoteContentName, ext, false);
- mediaHandler.start();
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred during media reinitialization", e);
- }
-
- return (newSenders != senders);
- }
-
- /**
- * Send a <tt>content</tt> message to reflect change in video setup (start
- * or stop).
- */
- public void sendModifyVideoResolutionContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- ContentPacketExtension remoteContent
- = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
- ContentPacketExtension content;
-
- logger.info("send modify-content to change resolution");
-
- // send content-modify with RTP description
-
- // create content list with resolution
- try
- {
- content = mediaHandler.createContentForMedia(MediaType.VIDEO);
- }
- catch (Exception e)
- {
- logger.warn("Failed to gather content for video type", e);
- return;
- }
-
- // if we are only receiving video senders is null
- SendersEnum senders = remoteContent.getSenders();
-
- if (senders != null)
- content.setSenders(senders);
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentModify(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- content);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
-
- try
- {
- mediaHandler.reinitContent(remoteContent.getName(), content, false);
- mediaHandler.start();
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred when media reinitialization", e);
- }
- }
-
- /**
- * Send a <tt>content-remove</tt> to remove video setup.
- */
- private void sendRemoveVideoContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- ContentPacketExtension content = new ContentPacketExtension();
- ContentPacketExtension remoteContent
- = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
- if (remoteContent == null)
- return;
- String remoteContentName = remoteContent.getName();
-
- content.setName(remoteContentName);
- content.setCreator(remoteContent.getCreator());
- content.setSenders(remoteContent.getSenders());
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentRemove(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- Arrays.asList(content));
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- mediaHandler.removeContent(remoteContentName);
- setSenders(MediaType.VIDEO, SendersEnum.none);
- }
-
- /**
- * Sends local candidate addresses from the local peer to the remote peer
- * using the <tt>transport-info</tt> {@link JingleIQ}.
- *
- * @param contents the local candidate addresses to be sent from the local
- * peer to the remote peer using the <tt>transport-info</tt>
- * {@link JingleIQ}
- */
- protected void sendTransportInfo(Iterable<ContentPacketExtension> contents)
- {
- // if the call is canceled, do not start sending candidates in
- // transport-info
- if(cancelled)
- return;
-
- JingleIQ transportInfo = new JingleIQ();
-
- for (ContentPacketExtension content : contents)
- transportInfo.addContent(content);
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- transportInfo.setAction(JingleAction.TRANSPORT_INFO);
- transportInfo.setFrom(protocolProvider.getOurJID());
- transportInfo.setSID(getSID());
- transportInfo.setTo(getAddress());
- transportInfo.setType(IQ.Type.SET);
-
- PacketCollector collector
- = protocolProvider.getConnection().createPacketCollector(
- new PacketIDFilter(transportInfo.getPacketID()));
-
- protocolProvider.getConnection().sendPacket(transportInfo);
- collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
- collector.cancel();
- }
-
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
- {
- CallPeerState oldState = getState();
- try
- {
- /*
- * We need to dispose of the transport manager before the
- * 'call' field is set to null, because if Jitsi Videobridge is in
- * use, it (the call) is needed in order to expire the
- * Videobridge channels.
- */
- if (CallPeerState.DISCONNECTED.equals(newState)
- || CallPeerState.FAILED.equals(newState))
- getMediaHandler().getTransportManager().close();
- }
- finally
- {
- super.setState(newState, reason, reasonCode);
- }
-
- if (CallPeerState.isOnHold(oldState)
- && CallPeerState.CONNECTED.equals(newState))
- {
- try
- {
- getCall().modifyVideoContent();
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Failed to update call video state after " +
- "'hold' status removed for "+this);
- }
- }
- }
-
- /**
- * Transfer (in the sense of call transfer) this <tt>CallPeer</tt> to a
- * specific callee address which may optionally be participating in an
- * active <tt>Call</tt>.
- *
- * @param to the address of the callee to transfer this <tt>CallPeer</tt> to
- * @param sid the Jingle session ID of the active <tt>Call</tt> between the
- * local peer and the callee in the case of attended transfer; <tt>null</tt>
- * in the case of unattended transfer
- * @throws OperationFailedException if something goes wrong
- */
- protected void transfer(String to, String sid)
- throws OperationFailedException
- {
- JingleIQ transferSessionInfo = new JingleIQ();
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- transferSessionInfo.setAction(JingleAction.SESSION_INFO);
- transferSessionInfo.setFrom(protocolProvider.getOurJID());
- transferSessionInfo.setSID(getSID());
- transferSessionInfo.setTo(getAddress());
- transferSessionInfo.setType(IQ.Type.SET);
-
- TransferPacketExtension transfer = new TransferPacketExtension();
-
- // Attended transfer.
- if (sid != null)
- {
- /*
- * Not really sure what the value of the "from" attribute of the
- * "transfer" element should be but the examples in "XEP-0251:
- * Jingle Session Transfer" has it in the case of attended transfer.
- */
- transfer.setFrom(protocolProvider.getOurJID());
- transfer.setSID(sid);
-
- // Puts on hold the 2 calls before making the attended transfer.
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = (OperationSetBasicTelephonyJabberImpl)
- protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
- CallPeerJabberImpl callPeer = basicTelephony.getActiveCallPeer(sid);
- if(callPeer != null)
- {
- if(!CallPeerState.isOnHold(callPeer.getState()))
- {
- callPeer.putOnHold(true);
- }
- }
-
- if(!CallPeerState.isOnHold(this.getState()))
- {
- this.putOnHold(true);
- }
- }
- transfer.setTo(to);
-
- transferSessionInfo.addExtension(transfer);
-
- Connection connection = protocolProvider.getConnection();
- PacketCollector collector = connection.createPacketCollector(
- new PacketIDFilter(transferSessionInfo.getPacketID()));
- protocolProvider.getConnection().sendPacket(transferSessionInfo);
-
- Packet result
- = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
-
- if(result == null)
- {
- // Log the failed transfer call and notify the user.
- throw new OperationFailedException(
- "No response to the \"transfer\" request.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
- else if (((IQ) result).getType() != IQ.Type.RESULT)
- {
- // Log the failed transfer call and notify the user.
- throw new OperationFailedException(
- "Remote peer does not manage call \"transfer\"."
- + "Response to the \"transfer\" request is: "
- + ((IQ) result).getType(),
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
- else
- {
- String message = ((sid == null) ? "Unattended" : "Attended")
- + " transfer to: "
- + to;
- // Implements the SIP behavior: once the transfer is accepted, the
- // current call is closed.
- hangup(
- false,
- message,
- new ReasonPacketExtension(Reason.SUCCESS,
- message,
- new TransferredPacketExtension()));
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public String getEntity()
- {
- return getAddress();
- }
-
- /**
- * {@inheritDoc}
- *
- * In Jingle there isn't an actual "direction" parameter. We use the
- * <tt>senders</tt> field to calculate the direction.
- */
- @Override
- public MediaDirection getDirection(MediaType mediaType)
- {
- SendersEnum senders = getSenders(mediaType);
-
- if (senders == SendersEnum.none)
- {
- return MediaDirection.INACTIVE;
- }
- else if (senders == null || senders == SendersEnum.both)
- {
- return MediaDirection.SENDRECV;
- }
- else if (senders == SendersEnum.initiator)
- {
- return
- isInitiator()
- ? MediaDirection.RECVONLY
- : MediaDirection.SENDONLY;
- }
- else //senders == SendersEnum.responder
- {
- return
- isInitiator()
- ? MediaDirection.SENDONLY
- : MediaDirection.RECVONLY;
- }
- }
-
- /**
- * Gets the current value of the <tt>senders</tt> field of the content with
- * name <tt>mediaType</tt> in the Jingle session with this
- * <tt>CallPeer</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> for which to get the current
- * value of the <tt>senders</tt> field.
- * @return the current value of the <tt>senders</tt> field of the content
- * with name <tt>mediaType</tt> in the Jingle session with this
- * <tt>CallPeer</tt>.
- */
- public SendersEnum getSenders(MediaType mediaType)
- {
- switch (mediaType)
- {
- case AUDIO:
- return audioSenders;
- case DATA:
- /*
- * FIXME DATA has been introduced as a MediaType but explicit
- * support for DATA content has not been added yet.
- */
- return SendersEnum.none;
- case VIDEO:
- return videoSenders;
- default:
- throw new IllegalArgumentException("mediaType");
- }
- }
-
- /**
- * Set the current value of the <tt>senders</tt> field of the content with
- * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
- * @param mediaType the <tt>MediaType</tt> for which to get the current
- * value of the <tt>senders</tt> field.
- * @param senders the value to set
- */
- public void setSenders(MediaType mediaType, SendersEnum senders)
- {
- if (mediaType == null)
- return;
- else if (MediaType.AUDIO.equals(mediaType))
- this.audioSenders = senders;
- else if (MediaType.VIDEO.equals(mediaType))
- this.videoSenders = senders;
- else
- throw new IllegalArgumentException("mediaType");
- }
-
- /**
- * Gets the <tt>MediaType</tt> of <tt>content</tt>. If <tt>content</tt>
- * does not have a <tt>description</tt> child and therefore not
- * <tt>MediaType</tt> can be associated with it, tries to take the
- * <tt>MediaType</tt> from the session's already established contents with
- * the same name as <tt>content</tt>
- * @param content the <tt>ContentPacketExtention</tt> for which to get the
- * <tt>MediaType</tt>
- * @return the <tt>MediaType</tt> of <tt>content</tt>.
- */
- public MediaType getMediaType(ContentPacketExtension content)
- {
- String contentName = content.getName();
- if (contentName == null)
- return null;
-
- MediaType mediaType = JingleUtils.getMediaType(content);
- if (mediaType == null)
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- for (MediaType m : MediaType.values())
- {
- ContentPacketExtension sessionContent
- = mediaHandler.getRemoteContent(m.toString());
- if (sessionContent == null)
- sessionContent = mediaHandler.getLocalContent(m.toString());
-
- if (sessionContent != null
- && contentName.equals(sessionContent.getName()))
- {
- mediaType = m;
- break;
- }
- }
- }
-
- return mediaType;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.ContentPacketExtension.SendersEnum;
+import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Implements a Jabber <tt>CallPeer</tt>.
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Boris Grozev
+ */
+public class CallPeerJabberImpl
+ extends AbstractCallPeerJabberGTalkImpl
+ <CallJabberImpl, CallPeerMediaHandlerJabberImpl, JingleIQ>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(CallPeerJabberImpl.class);
+
+ /**
+ * If the call is cancelled before session-initiate is sent.
+ */
+ private boolean cancelled = false;
+
+ /**
+ * Synchronization object for candidates available.
+ */
+ private final Object candSyncRoot = new Object();
+
+ /**
+ * If the content-add does not contains candidates.
+ */
+ private boolean contentAddWithNoCands = false;
+
+ /**
+ * If we have processed the session initiate.
+ */
+ private boolean sessionInitiateProcessed = false;
+
+ /**
+ * Synchronization object. Synchronization object? Wow, who would have
+ * thought! ;) Would be great to have a word on what we are syncing with it
+ */
+ private final Object sessionInitiateSyncRoot = new Object();
+
+ /**
+ * Synchronization object for SID.
+ */
+ private final Object sidSyncRoot = new Object();
+
+ /**
+ * The current value of the 'senders' field of the audio content in the
+ * Jingle session with this <tt>CallPeer</tt>.
+ * <tt>null</tt> should be interpreted as 'both', which is the default in
+ * Jingle if the XML attribute is missing.
+ */
+ private SendersEnum audioSenders = SendersEnum.none;
+
+ /**
+ * The current value of the 'senders' field of the video content in the
+ * Jingle session with this <tt>CallPeer</tt>.
+ * <tt>null</tt> should be interpreted as 'both', which is the default in
+ * Jingle if the XML attribute is missing.
+ */
+ private SendersEnum videoSenders = SendersEnum.none;
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param peerAddress the Jabber address of the new call peer.
+ * @param owningCall the call that contains this call peer.
+ */
+ public CallPeerJabberImpl(String peerAddress,
+ CallJabberImpl owningCall)
+ {
+ super(peerAddress, owningCall);
+
+ setMediaHandler(new CallPeerMediaHandlerJabberImpl(this));
+ }
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param peerAddress the Jabber address of the new call peer.
+ * @param owningCall the call that contains this call peer.
+ * @param sessionIQ The session-initiate <tt>JingleIQ</tt> which was
+ * received from <tt>peerAddress</tt> and caused the creation of this
+ * <tt>CallPeerJabberImpl</tt>
+ */
+ public CallPeerJabberImpl(String peerAddress,
+ CallJabberImpl owningCall,
+ JingleIQ sessionIQ)
+ {
+ this(peerAddress, owningCall);
+ this.sessionInitIQ = sessionIQ;
+ }
+
+ /**
+ * Send a session-accept <tt>JingleIQ</tt> to this <tt>CallPeer</tt>
+ * @throws OperationFailedException if we fail to create or send the
+ * response.
+ */
+ public synchronized void answer()
+ throws OperationFailedException
+ {
+ Iterable<ContentPacketExtension> answer;
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ try
+ {
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ answer = mediaHandler.generateSessionAccept();
+ for (ContentPacketExtension c : answer)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+ catch(Exception exc)
+ {
+ logger.info("Failed to answer an incoming call", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ Reason.FAILED_APPLICATION,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ JingleIQ response
+ = JinglePacketFactory.createSessionAccept(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ getSID(),
+ answer);
+
+ //send the packet first and start the stream later in case the media
+ //relay needs to see it before letting hole punching techniques through.
+ getProtocolProvider().getConnection().sendPacket(response);
+
+ try
+ {
+ mediaHandler.start();
+ }
+ catch(UndeclaredThrowableException e)
+ {
+ Throwable exc = e.getUndeclaredThrowable();
+
+ logger.info("Failed to establish a connection", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ //tell everyone we are connected so that the audio notifications would
+ //stop
+ setState(CallPeerState.CONNECTED);
+ }
+
+ /**
+ * Returns the session ID of the Jingle session associated with this call.
+ *
+ * @return the session ID of the Jingle session associated with this call.
+ */
+ @Override
+ public String getSID()
+ {
+ return sessionInitIQ != null ? sessionInitIQ.getSID() : null;
+ }
+
+ /**
+ * Returns the IQ ID of the Jingle session-initiate packet associated with
+ * this call.
+ *
+ * @return the IQ ID of the Jingle session-initiate packet associated with
+ * this call.
+ */
+ public JingleIQ getSessionIQ()
+ {
+ return sessionInitIQ;
+ }
+
+ /**
+ * Ends the call with this <tt>CallPeer</tt>. Depending on the state
+ * of the peer the method would send a CANCEL, BYE, or BUSY_HERE message
+ * and set the new state to DISCONNECTED.
+ *
+ * @param failed indicates if the hangup is following to a call failure or
+ * simply a disconnect
+ * @param reasonText the text, if any, to be set on the
+ * <tt>ReasonPacketExtension</tt> as the value of its
+ * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be
+ * set on the <tt>ReasonPacketExtension</tt> as the value of its
+ * <tt>otherExtension</tt> property
+ */
+ public void hangup(boolean failed,
+ String reasonText,
+ PacketExtension reasonOtherExtension)
+ {
+ CallPeerState prevPeerState = getState();
+
+ // do nothing if the call is already ended
+ if (CallPeerState.DISCONNECTED.equals(prevPeerState)
+ || CallPeerState.FAILED.equals(prevPeerState))
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Ignoring a request to hangup a call peer "
+ + "that is already DISCONNECTED");
+ return;
+ }
+
+ setState(
+ failed ? CallPeerState.FAILED : CallPeerState.DISCONNECTED,
+ reasonText);
+
+ JingleIQ responseIQ = null;
+
+ if (prevPeerState.equals(CallPeerState.CONNECTED)
+ || CallPeerState.isOnHold(prevPeerState))
+ {
+ responseIQ = JinglePacketFactory.createBye(
+ getProtocolProvider().getOurJID(), peerJID, getSID());
+ }
+ else if (CallPeerState.CONNECTING.equals(prevPeerState)
+ || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
+ || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState))
+ {
+ String jingleSID = getSID();
+
+ if(jingleSID == null)
+ {
+ synchronized(sidSyncRoot)
+ {
+ // we cancelled the call too early because the jingleSID
+ // is null (i.e. the session-initiate has not been created)
+ // and no need to send the session-terminate
+ cancelled = true;
+ return;
+ }
+ }
+
+ responseIQ = JinglePacketFactory.createCancel(
+ getProtocolProvider().getOurJID(), peerJID, getSID());
+ }
+ else if (prevPeerState.equals(CallPeerState.INCOMING_CALL))
+ {
+ responseIQ = JinglePacketFactory.createBusy(
+ getProtocolProvider().getOurJID(), peerJID, getSID());
+ }
+ else if (prevPeerState.equals(CallPeerState.BUSY)
+ || prevPeerState.equals(CallPeerState.FAILED))
+ {
+ // For FAILED and BUSY we only need to update CALL_STATUS
+ // as everything else has been done already.
+ }
+ else
+ {
+ logger.info("Could not determine call peer state!");
+ }
+
+ if (responseIQ != null)
+ {
+ if (reasonOtherExtension != null)
+ {
+ ReasonPacketExtension reason
+ = (ReasonPacketExtension)
+ responseIQ.getExtension(
+ ReasonPacketExtension.ELEMENT_NAME,
+ ReasonPacketExtension.NAMESPACE);
+
+ if (reason != null)
+ {
+ reason.setOtherExtension(reasonOtherExtension);
+ }
+ else if(reasonOtherExtension instanceof ReasonPacketExtension)
+ {
+ responseIQ.setReason(
+ (ReasonPacketExtension)reasonOtherExtension);
+ }
+ }
+
+ getProtocolProvider().getConnection().sendPacket(responseIQ);
+ }
+ }
+
+ /**
+ * Creates and sends a session-initiate {@link JingleIQ}.
+ *
+ * @param sessionInitiateExtensions a collection of additional and optional
+ * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
+ * {@link JingleIQ} which is to initiate the session with this
+ * <tt>CallPeerJabberImpl</tt>
+ * @throws OperationFailedException exception
+ */
+ protected synchronized void initiateSession(
+ Iterable<PacketExtension> sessionInitiateExtensions)
+ throws OperationFailedException
+ {
+ initiator = false;
+
+ //Create the media description that we'd like to send to the other side.
+ List<ContentPacketExtension> offer
+ = getMediaHandler().createContentList();
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ synchronized(sidSyncRoot)
+ {
+ sessionInitIQ
+ = JinglePacketFactory.createSessionInitiate(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ JingleIQ.generateSID(),
+ offer);
+
+ if(cancelled)
+ {
+ // we cancelled the call too early so no need to send the
+ // session-initiate to peer
+ getMediaHandler().getTransportManager().close();
+ return;
+ }
+ }
+
+ if (sessionInitiateExtensions != null)
+ {
+ for (PacketExtension sessionInitiateExtension
+ : sessionInitiateExtensions)
+ {
+ sessionInitIQ.addExtension(sessionInitiateExtension);
+ }
+ }
+
+ protocolProvider.getConnection().sendPacket(sessionInitIQ);
+ }
+
+ /**
+ * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
+ * been received. This <tt>CallPeerJabberImpl</tt> uses the part of the
+ * information provided in the specified <tt>conferenceIQ</tt> which
+ * concerns it only.
+ *
+ * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
+ * received
+ */
+ void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
+ {
+ /*
+ * CallPeerJabberImpl does not itself/directly know the specifics
+ * related to the channels allocated on the Jitsi Videobridge server.
+ * The channels contain transport and media-related information so
+ * forward the notification to CallPeerMediaHandlerJabberImpl.
+ */
+ getMediaHandler().processColibriConferenceIQ(conferenceIQ);
+ }
+
+ /**
+ * Processes the content-accept {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer has accepted
+ */
+ public void processContentAccept(JingleIQ content)
+ {
+ List<ContentPacketExtension> contents = content.getContentList();
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ try
+ {
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ mediaHandler.processAnswer(contents);
+ for (ContentPacketExtension c : contents)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+ catch (Exception e)
+ {
+ logger.warn("Failed to process a content-accept", e);
+
+ // Send an error response.
+ String reason = "Error: " + e.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ sessionInitIQ.getSID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reason);
+
+ setState(CallPeerState.FAILED, reason);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ mediaHandler.start();
+ }
+
+ /**
+ * Processes the content-add {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer wants to be added
+ */
+ public void processContentAdd(final JingleIQ content)
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ List<ContentPacketExtension> contents = content.getContentList();
+ Iterable<ContentPacketExtension> answerContents;
+ JingleIQ contentIQ;
+ boolean noCands = false;
+ MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO);
+
+ if(logger.isInfoEnabled())
+ logger.info("Looking for candidates in content-add.");
+ try
+ {
+ if(!contentAddWithNoCands)
+ {
+ mediaHandler.processOffer(contents);
+
+ /*
+ * Gingle transport will not put candidate in session-initiate
+ * and content-add.
+ */
+ for(ContentPacketExtension c : contents)
+ {
+ if(JingleUtils.getFirstCandidate(c, 1) == null)
+ {
+ contentAddWithNoCands = true;
+ noCands = true;
+ }
+ }
+ }
+
+ // if no candidates are present, launch a new Thread which will
+ // process and wait for the connectivity establishment (otherwise
+ // the existing thread will be blocked and thus cannot receive
+ // transport-info with candidates
+ if(noCands)
+ {
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ synchronized(candSyncRoot)
+ {
+ candSyncRoot.wait();
+ }
+ }
+ catch(InterruptedException e)
+ {
+ }
+
+ processContentAdd(content);
+ contentAddWithNoCands = false;
+ }
+ }.start();
+ if(logger.isInfoEnabled())
+ logger.info("No candidates found in content-add, started "
+ + "new thread.");
+ return;
+ }
+
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ if(logger.isInfoEnabled())
+ logger.info("Wrapping up connectivity establishment");
+ answerContents = mediaHandler.generateSessionAccept();
+ contentIQ = null;
+ }
+ catch(Exception e)
+ {
+ logger.warn("Exception occurred", e);
+
+ answerContents = null;
+ contentIQ
+ = JinglePacketFactory.createContentReject(
+ getProtocolProvider().getOurJID(),
+ this.peerJID,
+ getSID(),
+ answerContents);
+ }
+
+ if(contentIQ == null)
+ {
+ /* send content-accept */
+ contentIQ
+ = JinglePacketFactory.createContentAccept(
+ getProtocolProvider().getOurJID(),
+ this.peerJID,
+ getSID(),
+ answerContents);
+ for (ContentPacketExtension c : answerContents)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+
+ getProtocolProvider().getConnection().sendPacket(contentIQ);
+ mediaHandler.start();
+
+ /*
+ * If a remote peer turns her video on in a conference which is hosted
+ * by the local peer and the local peer is not streaming her local
+ * video, reinvite the other remote peers to enable RTP translation.
+ */
+ if (oldVideoStream == null)
+ {
+ MediaStream newVideoStream
+ = mediaHandler.getStream(MediaType.VIDEO);
+
+ if ((newVideoStream != null)
+ && mediaHandler.isRTPTranslationEnabled(MediaType.VIDEO))
+ {
+ try
+ {
+ getCall().modifyVideoContent();
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error("Failed to enable RTP translation", ofe);
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes the content-modify {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer wants to be modified
+ */
+ public void processContentModify(JingleIQ content)
+ {
+ ContentPacketExtension ext = content.getContentList().get(0);
+ MediaType mediaType = getMediaType(ext);
+
+ try
+ {
+ boolean modify
+ = (ext.getFirstChildOfType(RtpDescriptionPacketExtension.class)
+ != null);
+
+ getMediaHandler().reinitContent(ext.getName(), ext, modify);
+
+ setSenders(mediaType, ext.getSenders());
+
+ if (MediaType.VIDEO.equals(mediaType))
+ getCall().modifyVideoContent();
+ }
+ catch(Exception e)
+ {
+ logger.info("Failed to process an incoming content-modify", e);
+
+ // Send an error response.
+ String reason = "Error: " + e.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ sessionInitIQ.getSID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reason);
+
+ setState(CallPeerState.FAILED, reason);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+ }
+
+ /**
+ * Processes the content-reject {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ}
+ */
+ public void processContentReject(JingleIQ content)
+ {
+ if(content.getContentList().isEmpty())
+ {
+ //send an error response;
+ JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
+ "Error: content rejected");
+
+ setState(CallPeerState.FAILED, "Error: content rejected");
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+ }
+
+ /**
+ * Processes the content-remove {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer wants to be removed
+ */
+ public void processContentRemove(JingleIQ content)
+ {
+ List<ContentPacketExtension> contents = content.getContentList();
+ boolean videoContentRemoved = false;
+
+ if (!contents.isEmpty())
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ for(ContentPacketExtension c : contents)
+ {
+ mediaHandler.removeContent(c.getName());
+
+ MediaType mediaType = getMediaType(c);
+ setSenders(mediaType, SendersEnum.none);
+
+ if (MediaType.VIDEO.equals(mediaType))
+ videoContentRemoved = true;
+ }
+
+ /*
+ * TODO XEP-0166: Jingle says: If the content-remove results in zero
+ * content definitions for the session, the entity that receives the
+ * content-remove SHOULD send a session-terminate action to the
+ * other party (since a session with no content definitions is
+ * void).
+ */
+ }
+
+ if (videoContentRemoved)
+ {
+ // removing of the video content might affect the other sessions
+ // in the call
+ try
+ {
+ getCall().modifyVideoContent();
+ }
+ catch (Exception e)
+ {
+ logger.warn("Failed to update Jingle sessions");
+ }
+ }
+ }
+
+ /**
+ * Processes a session-accept {@link JingleIQ}.
+ *
+ * @param sessionInitIQ The session-accept {@link JingleIQ} to process.
+ */
+ public void processSessionAccept(JingleIQ sessionInitIQ)
+ {
+ this.sessionInitIQ = sessionInitIQ;
+
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ List<ContentPacketExtension> answer = sessionInitIQ.getContentList();
+
+ try
+ {
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ mediaHandler.processAnswer(answer);
+ for (ContentPacketExtension c : answer)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+ catch(Exception exc)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Failed to process a session-accept", exc);
+
+ //send an error response;
+ JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
+ exc.getClass().getName() + ": " + exc.getMessage());
+
+ setState(CallPeerState.FAILED, "Error: " + exc.getMessage());
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ //tell everyone we are connected so that the audio notifications would
+ //stop
+ setState(CallPeerState.CONNECTED);
+
+ mediaHandler.start();
+
+ /*
+ * If video was added to the call after we sent the session-initiate
+ * to this peer, it needs to be added to this peer's session with a
+ * content-add.
+ */
+ sendModifyVideoContent();
+ }
+
+ /**
+ * Handles the specified session <tt>info</tt> packet according to its
+ * content.
+ *
+ * @param info the {@link SessionInfoPacketExtension} that we just received.
+ */
+ public void processSessionInfo(SessionInfoPacketExtension info)
+ {
+ switch (info.getType())
+ {
+ case ringing:
+ setState(CallPeerState.ALERTING_REMOTE_SIDE);
+ break;
+ case hold:
+ getMediaHandler().setRemotelyOnHold(true);
+ reevalRemoteHoldStatus();
+ break;
+ case unhold:
+ case active:
+ getMediaHandler().setRemotelyOnHold(false);
+ reevalRemoteHoldStatus();
+ break;
+ default:
+ logger.warn("Received SessionInfoPacketExtension of unknown type");
+ }
+ }
+
+ /**
+ * Processes the session initiation {@link JingleIQ} that we were created
+ * with, passing its content to the media handler and then sends either a
+ * "session-info/ringing" or a "session-terminate" response.
+ *
+ * @param sessionInitIQ The {@link JingleIQ} that created the session that
+ * we are handling here.
+ */
+ protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ)
+ {
+ // Do initiate the session.
+ this.sessionInitIQ = sessionInitIQ;
+ this.initiator = true;
+
+ // This is the SDP offer that came from the initial session-initiate.
+ // Contrary to SIP, we are guaranteed to have content because XEP-0166
+ // says: "A session consists of at least one content type at a time."
+ List<ContentPacketExtension> offer = sessionInitIQ.getContentList();
+
+ try
+ {
+ getMediaHandler().processOffer(offer);
+
+ CoinPacketExtension coin = null;
+
+ for(PacketExtension ext : sessionInitIQ.getExtensions())
+ {
+ if(ext.getElementName().equals(
+ CoinPacketExtension.ELEMENT_NAME))
+ {
+ coin = (CoinPacketExtension)ext;
+ break;
+ }
+ }
+
+ /* does the call peer acts as a conference focus ? */
+ if(coin != null)
+ {
+ setConferenceFocus(Boolean.parseBoolean(
+ (String)coin.getAttribute("isfocus")));
+ }
+ }
+ catch(Exception ex)
+ {
+ logger.info("Failed to process an incoming session initiate", ex);
+
+ //send an error response;
+ String reasonText = "Error: " + ex.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ // If we do not get the info about the remote peer yet. Get it right
+ // now.
+ if(this.getDiscoveryInfo() == null)
+ {
+ String calleeURI = sessionInitIQ.getFrom();
+ retrieveDiscoveryInfo(calleeURI);
+ }
+
+ //send a ringing response
+ if (logger.isTraceEnabled())
+ logger.trace("will send ringing response: ");
+
+ getProtocolProvider().getConnection().sendPacket(
+ JinglePacketFactory.createRinging(sessionInitIQ));
+
+ synchronized(sessionInitiateSyncRoot)
+ {
+ sessionInitiateProcessed = true;
+ sessionInitiateSyncRoot.notify();
+ }
+
+ //if this is a 3264 initiator, let's give them an early peek at our
+ //answer so that they could start ICE (SIP-2-Jingle gateways won't
+ //be able to send their candidates unless they have this)
+ DiscoverInfo discoverInfo = getDiscoveryInfo();
+ if ((discoverInfo != null)
+ && discoverInfo.containsFeature(
+ ProtocolProviderServiceJabberImpl.URN_IETF_RFC_3264))
+ {
+ getProtocolProvider().getConnection().sendPacket(
+ JinglePacketFactory.createDescriptionInfo(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ getMediaHandler().getLocalContentList()));
+ }
+ }
+
+ /**
+ * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
+ * reason to the user, if there is one.
+ *
+ * @param jingleIQ the {@link JingleIQ} that's terminating our session.
+ */
+ public void processSessionTerminate(JingleIQ jingleIQ)
+ {
+ String reasonStr = "Call ended by remote side.";
+ ReasonPacketExtension reasonExt = jingleIQ.getReason();
+
+ if(reasonExt != null)
+ {
+ Reason reason = reasonExt.getReason();
+
+ if(reason != null)
+ reasonStr += " Reason: " + reason.toString() + ".";
+
+ String text = reasonExt.getText();
+
+ if(text != null)
+ reasonStr += " " + text;
+ }
+
+ setState(CallPeerState.DISCONNECTED, reasonStr);
+ }
+
+ /**
+ * Processes a specific "XEP-0251: Jingle Session Transfer"
+ * <tt>transfer</tt> packet (extension).
+ *
+ * @param transfer the "XEP-0251: Jingle Session Transfer" transfer packet
+ * (extension) to process
+ * @throws OperationFailedException if anything goes wrong while processing
+ * the specified <tt>transfer</tt> packet (extension)
+ */
+ public void processTransfer(TransferPacketExtension transfer)
+ throws OperationFailedException
+ {
+ String attendantAddress = transfer.getFrom();
+
+ if (attendantAddress == null)
+ {
+ throw new OperationFailedException(
+ "Session transfer must contain a \'from\' attribute value.",
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+
+ String calleeAddress = transfer.getTo();
+
+ if (calleeAddress == null)
+ {
+ throw new OperationFailedException(
+ "Session transfer must contain a \'to\' attribute value.",
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+
+ // Checks if the transfer remote peer is contained by the roster of this
+ // account.
+ Roster roster = getProtocolProvider().getConnection().getRoster();
+ if(!roster.contains(StringUtils.parseBareAddress(calleeAddress)))
+ {
+ String failedMessage =
+ "Transfer impossible:\n"
+ + "Account roster does not contain transfer peer: "
+ + StringUtils.parseBareAddress(calleeAddress);
+ setState(CallPeerState.FAILED, failedMessage);
+ logger.info(failedMessage);
+ }
+
+ OperationSetBasicTelephonyJabberImpl basicTelephony
+ = (OperationSetBasicTelephonyJabberImpl)
+ getProtocolProvider()
+ .getOperationSet(OperationSetBasicTelephony.class);
+ CallJabberImpl calleeCall = new CallJabberImpl(basicTelephony);
+ TransferPacketExtension calleeTransfer = new TransferPacketExtension();
+ String sid = transfer.getSID();
+
+ calleeTransfer.setFrom(attendantAddress);
+ if (sid != null)
+ {
+ calleeTransfer.setSID(sid);
+ calleeTransfer.setTo(calleeAddress);
+ }
+ basicTelephony.createOutgoingCall(
+ calleeCall,
+ calleeAddress,
+ Arrays.asList(new PacketExtension[] { calleeTransfer }));
+ }
+
+ /**
+ * Processes the <tt>transport-info</tt> {@link JingleIQ}.
+ *
+ * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process
+ */
+ public void processTransportInfo(JingleIQ jingleIQ)
+ {
+ /*
+ * The transport-info action is used to exchange transport candidates so
+ * it only concerns the mediaHandler.
+ */
+ try
+ {
+ if(isInitiator())
+ {
+ synchronized(sessionInitiateSyncRoot)
+ {
+ if(!sessionInitiateProcessed)
+ {
+ try
+ {
+ sessionInitiateSyncRoot.wait();
+ }
+ catch(InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+
+ getMediaHandler().processTransportInfo(
+ jingleIQ.getContentList());
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.warn("Failed to process an incoming transport-info", ofe);
+
+ //send an error response
+ String reasonText = "Error: " + ofe.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ sessionInitIQ.getSID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+
+ return;
+ }
+
+ synchronized(candSyncRoot)
+ {
+ candSyncRoot.notify();
+ }
+ }
+
+ /**
+ * Puts the <tt>CallPeer</tt> represented by this instance on or off hold.
+ *
+ * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
+ * <tt>false</tt>, otherwise
+ *
+ * @throws OperationFailedException if we fail to construct or send the
+ * INVITE request putting the remote side on/off hold.
+ */
+ public void putOnHold(boolean onHold)
+ throws OperationFailedException
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ mediaHandler.setLocallyOnHold(onHold);
+
+ SessionInfoType type;
+
+ if(onHold)
+ type = SessionInfoType.hold;
+ else
+ {
+ type = SessionInfoType.unhold;
+ getMediaHandler().reinitAllContents();
+ }
+
+ //we are now on hold and need to realize this before potentially
+ //spoiling it all with an exception while sending the packet :).
+ reevalLocalHoldStatus();
+
+ JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ getSID(),
+ type);
+
+ getProtocolProvider().getConnection().sendPacket(onHoldIQ);
+ }
+
+ /**
+ * Send a <tt>content-add</tt> to add video setup.
+ */
+ private void sendAddVideoContent()
+ {
+ List<ContentPacketExtension> contents;
+
+ try
+ {
+ contents = getMediaHandler().createContentList(MediaType.VIDEO);
+ }
+ catch(Exception exc)
+ {
+ logger.warn("Failed to gather content for video type", exc);
+ return;
+ }
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentAdd(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ contents);
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+ }
+
+ /**
+ * Sends a <tt>content</tt> message to reflect changes in the setup such as
+ * the local peer/user becoming a conference focus.
+ */
+ public void sendCoinSessionInfo()
+ {
+ JingleIQ sessionInfoIQ
+ = JinglePacketFactory.createSessionInfo(
+ getProtocolProvider().getOurJID(),
+ this.peerJID,
+ getSID());
+ CoinPacketExtension coinExt
+ = new CoinPacketExtension(getCall().isConferenceFocus());
+
+ sessionInfoIQ.addExtension(coinExt);
+ getProtocolProvider().getConnection().sendPacket(sessionInfoIQ);
+ }
+
+ /**
+ * Returns the <tt>MediaDirection</tt> that should be set for the content
+ * of type <tt>mediaType</tt> in the Jingle session for this
+ * <tt>CallPeer</tt>.
+ * If we are the focus of a conference and are doing RTP translation,
+ * takes into account the other <tt>CallPeer</tt>s in the <tt>Call</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> for which to return the
+ * <tt>MediaDirection</tt>
+ * @return the <tt>MediaDirection</tt> that should be used for the content
+ * of type <tt>mediaType</tt> in the Jingle session for this
+ * <tt>CallPeer</tt>.
+ */
+ private MediaDirection getDirectionForJingle(MediaType mediaType)
+ {
+ MediaDirection direction = MediaDirection.INACTIVE;
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ // If we are streaming media, the direction should allow sending
+ if ( (MediaType.AUDIO == mediaType &&
+ mediaHandler.isLocalAudioTransmissionEnabled()) ||
+ (MediaType.VIDEO == mediaType &&
+ isLocalVideoStreaming()))
+ direction = direction.or(MediaDirection.SENDONLY);
+
+ // If we are receiving media from this CallPeer, the direction should
+ // allow receiving
+ SendersEnum senders = getSenders(mediaType);
+ if (senders == null || senders == SendersEnum.both ||
+ (isInitiator() && senders == SendersEnum.initiator) ||
+ (!isInitiator() && senders == SendersEnum.responder))
+ direction = direction.or(MediaDirection.RECVONLY);
+
+ // If we are the focus of a conference and we are receiving media from
+ // another CallPeer in the same Call, the direction should allow sending
+ CallJabberImpl call = getCall();
+ if (call != null && call.isConferenceFocus())
+ {
+ for (CallPeerJabberImpl peer : call.getCallPeerList())
+ {
+ if (peer != this)
+ {
+ senders = peer.getSenders(mediaType);
+ if (senders == null || senders == SendersEnum.both ||
+ (peer.isInitiator()
+ && senders == SendersEnum.initiator) ||
+ (!peer.isInitiator()
+ && senders == SendersEnum.responder))
+ {
+ direction = direction.or(MediaDirection.SENDONLY);
+ break;
+ }
+ }
+ }
+ }
+
+ return direction;
+ }
+
+ /**
+ * Send, if necessary, a jingle <tt>content</tt> message to reflect change
+ * in video setup. Whether the jingle session should have a video content,
+ * and if so, the value of the <tt>senders</tt> field is determined
+ * based on whether we are streaming local video and, if we are the focus
+ * of a conference, on the other peers in the conference.
+ * The message can be content-modify if video content exists (and the
+ * <tt>senders</tt> field changes), content-add or content-remove.
+ *
+ * @return <tt>true</tt> if a jingle <tt>content</tt> message was sent.
+ */
+ public boolean sendModifyVideoContent()
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ MediaDirection direction = getDirectionForJingle(MediaType.VIDEO);
+
+ ContentPacketExtension remoteContent
+ = mediaHandler.getLocalContent(MediaType.VIDEO.toString());
+
+ if (remoteContent == null)
+ {
+ if (direction == MediaDirection.INACTIVE)
+ {
+ // no video content, none needed
+ return false;
+ }
+ else
+ {
+ if (getState() == CallPeerState.CONNECTED)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Adding video content for " + this);
+ sendAddVideoContent();
+ return true;
+ }
+ return false;
+ }
+ }
+ else
+ {
+ if (direction == MediaDirection.INACTIVE)
+ {
+ sendRemoveVideoContent();
+ return true;
+ }
+ }
+
+ SendersEnum senders = getSenders(MediaType.VIDEO);
+ if (senders == null)
+ senders = SendersEnum.both;
+
+ SendersEnum newSenders = SendersEnum.none;
+ if (MediaDirection.SENDRECV == direction)
+ newSenders = SendersEnum.both;
+ else if (MediaDirection.RECVONLY == direction)
+ newSenders = isInitiator()
+ ? SendersEnum.initiator : SendersEnum.responder;
+ else if (MediaDirection.SENDONLY == direction)
+ newSenders = isInitiator()
+ ? SendersEnum.responder : SendersEnum.initiator;
+
+ /*
+ * Send Content-Modify
+ */
+ ContentPacketExtension ext = new ContentPacketExtension();
+ String remoteContentName = remoteContent.getName();
+
+ ext.setSenders(newSenders);
+ ext.setCreator(remoteContent.getCreator());
+ ext.setName(remoteContentName);
+
+ if (newSenders != senders)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Sending content modify, senders: "
+ + senders + "->" + newSenders);
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentModify(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ ext);
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+ }
+
+ try
+ {
+ mediaHandler.reinitContent(remoteContentName, ext, false);
+ mediaHandler.start();
+ }
+ catch(Exception e)
+ {
+ logger.warn("Exception occurred during media reinitialization", e);
+ }
+
+ return (newSenders != senders);
+ }
+
+ /**
+ * Send a <tt>content</tt> message to reflect change in video setup (start
+ * or stop).
+ */
+ public void sendModifyVideoResolutionContent()
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ ContentPacketExtension remoteContent
+ = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
+ ContentPacketExtension content;
+
+ logger.info("send modify-content to change resolution");
+
+ // send content-modify with RTP description
+
+ // create content list with resolution
+ try
+ {
+ content = mediaHandler.createContentForMedia(MediaType.VIDEO);
+ }
+ catch (Exception e)
+ {
+ logger.warn("Failed to gather content for video type", e);
+ return;
+ }
+
+ // if we are only receiving video senders is null
+ SendersEnum senders = remoteContent.getSenders();
+
+ if (senders != null)
+ content.setSenders(senders);
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentModify(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ content);
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+
+ try
+ {
+ mediaHandler.reinitContent(remoteContent.getName(), content, false);
+ mediaHandler.start();
+ }
+ catch(Exception e)
+ {
+ logger.warn("Exception occurred when media reinitialization", e);
+ }
+ }
+
+ /**
+ * Send a <tt>content-remove</tt> to remove video setup.
+ */
+ private void sendRemoveVideoContent()
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ ContentPacketExtension content = new ContentPacketExtension();
+ ContentPacketExtension remoteContent
+ = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
+ if (remoteContent == null)
+ return;
+ String remoteContentName = remoteContent.getName();
+
+ content.setName(remoteContentName);
+ content.setCreator(remoteContent.getCreator());
+ content.setSenders(remoteContent.getSenders());
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentRemove(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ Arrays.asList(content));
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+ mediaHandler.removeContent(remoteContentName);
+ setSenders(MediaType.VIDEO, SendersEnum.none);
+ }
+
+ /**
+ * Sends local candidate addresses from the local peer to the remote peer
+ * using the <tt>transport-info</tt> {@link JingleIQ}.
+ *
+ * @param contents the local candidate addresses to be sent from the local
+ * peer to the remote peer using the <tt>transport-info</tt>
+ * {@link JingleIQ}
+ */
+ protected void sendTransportInfo(Iterable<ContentPacketExtension> contents)
+ {
+ // if the call is canceled, do not start sending candidates in
+ // transport-info
+ if(cancelled)
+ return;
+
+ JingleIQ transportInfo = new JingleIQ();
+
+ for (ContentPacketExtension content : contents)
+ transportInfo.addContent(content);
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ transportInfo.setAction(JingleAction.TRANSPORT_INFO);
+ transportInfo.setFrom(protocolProvider.getOurJID());
+ transportInfo.setSID(getSID());
+ transportInfo.setTo(getAddress());
+ transportInfo.setType(IQ.Type.SET);
+
+ PacketCollector collector
+ = protocolProvider.getConnection().createPacketCollector(
+ new PacketIDFilter(transportInfo.getPacketID()));
+
+ protocolProvider.getConnection().sendPacket(transportInfo);
+ collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ collector.cancel();
+ }
+
+ @Override
+ public void setState(CallPeerState newState, String reason, int reasonCode)
+ {
+ CallPeerState oldState = getState();
+ try
+ {
+ /*
+ * We need to dispose of the transport manager before the
+ * 'call' field is set to null, because if Jitsi Videobridge is in
+ * use, it (the call) is needed in order to expire the
+ * Videobridge channels.
+ */
+ if (CallPeerState.DISCONNECTED.equals(newState)
+ || CallPeerState.FAILED.equals(newState))
+ getMediaHandler().getTransportManager().close();
+ }
+ finally
+ {
+ super.setState(newState, reason, reasonCode);
+ }
+
+ if (CallPeerState.isOnHold(oldState)
+ && CallPeerState.CONNECTED.equals(newState))
+ {
+ try
+ {
+ getCall().modifyVideoContent();
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error("Failed to update call video state after " +
+ "'hold' status removed for "+this);
+ }
+ }
+ }
+
+ /**
+ * Transfer (in the sense of call transfer) this <tt>CallPeer</tt> to a
+ * specific callee address which may optionally be participating in an
+ * active <tt>Call</tt>.
+ *
+ * @param to the address of the callee to transfer this <tt>CallPeer</tt> to
+ * @param sid the Jingle session ID of the active <tt>Call</tt> between the
+ * local peer and the callee in the case of attended transfer; <tt>null</tt>
+ * in the case of unattended transfer
+ * @throws OperationFailedException if something goes wrong
+ */
+ protected void transfer(String to, String sid)
+ throws OperationFailedException
+ {
+ JingleIQ transferSessionInfo = new JingleIQ();
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ transferSessionInfo.setAction(JingleAction.SESSION_INFO);
+ transferSessionInfo.setFrom(protocolProvider.getOurJID());
+ transferSessionInfo.setSID(getSID());
+ transferSessionInfo.setTo(getAddress());
+ transferSessionInfo.setType(IQ.Type.SET);
+
+ TransferPacketExtension transfer = new TransferPacketExtension();
+
+ // Attended transfer.
+ if (sid != null)
+ {
+ /*
+ * Not really sure what the value of the "from" attribute of the
+ * "transfer" element should be but the examples in "XEP-0251:
+ * Jingle Session Transfer" has it in the case of attended transfer.
+ */
+ transfer.setFrom(protocolProvider.getOurJID());
+ transfer.setSID(sid);
+
+ // Puts on hold the 2 calls before making the attended transfer.
+ OperationSetBasicTelephonyJabberImpl basicTelephony
+ = (OperationSetBasicTelephonyJabberImpl)
+ protocolProvider.getOperationSet(
+ OperationSetBasicTelephony.class);
+ CallPeerJabberImpl callPeer = basicTelephony.getActiveCallPeer(sid);
+ if(callPeer != null)
+ {
+ if(!CallPeerState.isOnHold(callPeer.getState()))
+ {
+ callPeer.putOnHold(true);
+ }
+ }
+
+ if(!CallPeerState.isOnHold(this.getState()))
+ {
+ this.putOnHold(true);
+ }
+ }
+ transfer.setTo(to);
+
+ transferSessionInfo.addExtension(transfer);
+
+ Connection connection = protocolProvider.getConnection();
+ PacketCollector collector = connection.createPacketCollector(
+ new PacketIDFilter(transferSessionInfo.getPacketID()));
+ protocolProvider.getConnection().sendPacket(transferSessionInfo);
+
+ Packet result
+ = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ if(result == null)
+ {
+ // Log the failed transfer call and notify the user.
+ throw new OperationFailedException(
+ "No response to the \"transfer\" request.",
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+ else if (((IQ) result).getType() != IQ.Type.RESULT)
+ {
+ // Log the failed transfer call and notify the user.
+ throw new OperationFailedException(
+ "Remote peer does not manage call \"transfer\"."
+ + "Response to the \"transfer\" request is: "
+ + ((IQ) result).getType(),
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+ else
+ {
+ String message = ((sid == null) ? "Unattended" : "Attended")
+ + " transfer to: "
+ + to;
+ // Implements the SIP behavior: once the transfer is accepted, the
+ // current call is closed.
+ hangup(
+ false,
+ message,
+ new ReasonPacketExtension(Reason.SUCCESS,
+ message,
+ new TransferredPacketExtension()));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getEntity()
+ {
+ return getAddress();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * In Jingle there isn't an actual "direction" parameter. We use the
+ * <tt>senders</tt> field to calculate the direction.
+ */
+ @Override
+ public MediaDirection getDirection(MediaType mediaType)
+ {
+ SendersEnum senders = getSenders(mediaType);
+
+ if (senders == SendersEnum.none)
+ {
+ return MediaDirection.INACTIVE;
+ }
+ else if (senders == null || senders == SendersEnum.both)
+ {
+ return MediaDirection.SENDRECV;
+ }
+ else if (senders == SendersEnum.initiator)
+ {
+ return
+ isInitiator()
+ ? MediaDirection.RECVONLY
+ : MediaDirection.SENDONLY;
+ }
+ else //senders == SendersEnum.responder
+ {
+ return
+ isInitiator()
+ ? MediaDirection.SENDONLY
+ : MediaDirection.RECVONLY;
+ }
+ }
+
+ /**
+ * Gets the current value of the <tt>senders</tt> field of the content with
+ * name <tt>mediaType</tt> in the Jingle session with this
+ * <tt>CallPeer</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> for which to get the current
+ * value of the <tt>senders</tt> field.
+ * @return the current value of the <tt>senders</tt> field of the content
+ * with name <tt>mediaType</tt> in the Jingle session with this
+ * <tt>CallPeer</tt>.
+ */
+ public SendersEnum getSenders(MediaType mediaType)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ return audioSenders;
+ case VIDEO:
+ return videoSenders;
+ default:
+ return SendersEnum.none;
+ }
+ }
+
+ /**
+ * Set the current value of the <tt>senders</tt> field of the content with
+ * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
+ * @param mediaType the <tt>MediaType</tt> for which to get the current
+ * value of the <tt>senders</tt> field.
+ * @param senders the value to set
+ */
+ public void setSenders(MediaType mediaType, SendersEnum senders)
+ {
+ switch(mediaType)
+ {
+ case AUDIO:
+ this.audioSenders = senders;
+ break;
+ case VIDEO:
+ this.videoSenders = senders;
+ break;
+ default:
+ throw new IllegalArgumentException("mediaType");
+ }
+ }
+
+ /**
+ * Gets the <tt>MediaType</tt> of <tt>content</tt>. If <tt>content</tt>
+ * does not have a <tt>description</tt> child and therefore not
+ * <tt>MediaType</tt> can be associated with it, tries to take the
+ * <tt>MediaType</tt> from the session's already established contents with
+ * the same name as <tt>content</tt>
+ * @param content the <tt>ContentPacketExtention</tt> for which to get the
+ * <tt>MediaType</tt>
+ * @return the <tt>MediaType</tt> of <tt>content</tt>.
+ */
+ public MediaType getMediaType(ContentPacketExtension content)
+ {
+ String contentName = content.getName();
+ if (contentName == null)
+ return null;
+
+ MediaType mediaType = JingleUtils.getMediaType(content);
+ if (mediaType == null)
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ for (MediaType m : MediaType.values())
+ {
+ ContentPacketExtension sessionContent
+ = mediaHandler.getRemoteContent(m.toString());
+ if (sessionContent == null)
+ sessionContent = mediaHandler.getLocalContent(m.toString());
+
+ if (sessionContent != null
+ && contentName.equals(sessionContent.getName()))
+ {
+ mediaType = m;
+ break;
+ }
+ }
+ }
+
+ return mediaType;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java
index 2c8845d..979d696 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java
@@ -937,19 +937,18 @@ public class CallPeerMediaHandlerJabberImpl
if (supportedTransports != null
&& supportedTransports.length > 0)
{
- for (int i = 0; i < supportedTransports.length; i++)
+ for(String supportedTransport : supportedTransports)
{
if (ProtocolProviderServiceJabberImpl.
URN_XMPP_JINGLE_ICE_UDP_1.
- equals(supportedTransports[i]))
+ equals(supportedTransport))
{
- transportManager
- = new IceUdpTransportManager(peer);
+ transportManager = new IceUdpTransportManager(peer);
break;
}
else if (ProtocolProviderServiceJabberImpl.
- URN_XMPP_JINGLE_RAW_UDP_0.
- equals(supportedTransports[i]))
+ URN_XMPP_JINGLE_RAW_UDP_0.
+ equals(supportedTransport))
{
transportManager
= new RawUdpTransportManager(peer);
@@ -1117,12 +1116,11 @@ public class CallPeerMediaHandlerJabberImpl
List<Component> visualComponents
= new LinkedList<Component>();
- for (int i = 0; i < remoteSSRCs.length; i++)
+ for(int remoteSSRC : remoteSSRCs)
{
- int remoteSSRC = remoteSSRCs[i];
Component visualComponent
- = videoStream.getVisualComponent(
- 0xFFFFFFFFL & remoteSSRC);
+ = videoStream.getVisualComponent(
+ 0xFFFFFFFFL & remoteSSRC);
if (visualComponent != null)
visualComponents.add(visualComponent);
@@ -1605,7 +1603,7 @@ public class CallPeerMediaHandlerJabberImpl
{
List<MediaFormat> fmts = supportedFormats;
- if(fmts.size() > 0)
+ if(!fmts.isEmpty())
{
MediaFormat fmt = fmts.get(0);
@@ -2108,21 +2106,17 @@ public class CallPeerMediaHandlerJabberImpl
* TODO The transportManager is going to be changed so it may need to be
* disposed of prior to the change.
*/
-
- if (xmlns.equals(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1))
+ switch (xmlns)
{
- transportManager = new IceUdpTransportManager(peer);
- }
- else if (xmlns.equals(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0))
- {
- transportManager = new RawUdpTransportManager(peer);
- }
- else
- {
- throw new IllegalArgumentException(
- "Unsupported Jingle transport " + xmlns);
+ case ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1:
+ transportManager = new IceUdpTransportManager(peer);
+ break;
+ case ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0:
+ transportManager = new RawUdpTransportManager(peer);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported Jingle " +
+ "transport " + xmlns);
}
synchronized(transportManagerSyncRoot)
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
index 8a6f3ed..6c4c358 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
@@ -609,7 +609,7 @@ public class ChatRoomJabberImpl
this.provider.getConnection().addPacketListener(
presenceListener,
new AndFilter(
- new FromMatchesFilter(multiUserChat.getRoom()),
+ FromMatchesFilter.create(multiUserChat.getRoom()),
new PacketTypeFilter(
org.jivesoftware.smack.packet.Presence.class)));
if(password == null)
@@ -868,7 +868,7 @@ public class ChatRoomJabberImpl
clearCachedConferenceDescriptionList();
- XMPPConnection connection = this.provider.getConnection();
+ Connection connection = this.provider.getConnection();
try
{
// if we are already disconnected
@@ -1896,6 +1896,23 @@ public class ChatRoomJabberImpl
}
/**
+ * Removes given <tt>PacketExtension</tt> from the MUC presence and
+ * publishes it immediately.
+ * @param extension the <tt>PacketExtension</tt> to be removed from the MUC
+ * presence.
+ */
+ public void removePresenceExtension(PacketExtension extension)
+ {
+ if (lastPresenceSent != null)
+ {
+ setPacketExtension(
+ lastPresenceSent, null, extension.getNamespace());
+
+ provider.getConnection().sendPacket(lastPresenceSent);
+ }
+ }
+
+ /**
* Returns the ids of the users that has the member role in the room.
* When the room is member only, this are the users allowed to join.
* @return the ids of the users that has the member role in the room.
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java
index 1f4e3d8..c3c852c 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java
@@ -58,11 +58,21 @@ public class IceUdpTransportManager
= Logger.getLogger(IceUdpTransportManager.class);
/**
+ * Default STUN server address.
+ */
+ protected static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.jitsi.net";
+
+ /**
+ * Default STUN server port.
+ */
+ protected static final int DEFAULT_STUN_SERVER_PORT = 3478;
+
+ /**
* The ICE <tt>Component</tt> IDs in their common order used, for example,
* by <tt>DefaultStreamConnector</tt>, <tt>MediaStreamTarget</tt>.
*/
private static final int[] COMPONENT_IDS
- = new int[] { Component.RTP, Component.RTCP };
+ = new int[] { Component.RTP, Component.RTCP };
/**
* This is where we keep our answer between the time we get the offer and
@@ -76,15 +86,6 @@ public class IceUdpTransportManager
*/
protected final Agent iceAgent;
- /**
- * Default STUN server address.
- */
- protected static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.jitsi.net";
-
- /**
- * Default STUN server port.
- */
- protected static final int DEFAULT_STUN_SERVER_PORT = 3478;
/**
* Creates a new instance of this transport manager, binding it to the
@@ -156,7 +157,10 @@ public class IceUdpTransportManager
// in case user has canceled the login window
if(credentials == null)
+ {
+ logger.info("Credentials were null. User has most likely canceled the login operation");
return null;
+ }
//extract the password the user passed us.
char[] pass = credentials.getPassword();
@@ -164,7 +168,10 @@ public class IceUdpTransportManager
// the user didn't provide us a password (i.e. canceled the
// operation)
if(pass == null)
+ {
+ logger.info("Password was null. User has most likely canceled the login operation");
return null;
+ }
password = new String(pass);
if (credentials.isPasswordPersistent())
@@ -390,28 +397,29 @@ public class IceUdpTransportManager
for (int i = 0; i < COMPONENT_IDS.length; i++)
{
Component component = stream.getComponent(COMPONENT_IDS[i]);
-
- if (component != null)
+ if (component == null)
{
- CandidatePair selectedPair = component.getSelectedPair();
-
- if (selectedPair != null)
- {
- DatagramSocket streamConnectorSocket
- = selectedPair.getLocalCandidate().
- getDatagramSocket();
+ continue;
+ }
- if (streamConnectorSocket != null)
- {
- streamConnectorSockets[i] = streamConnectorSocket;
- streamConnectorSocketCount++;
- }
- }
+ DatagramSocket streamConnectorSocket = component.getSocket();
+ if (streamConnectorSocket != null)
+ {
+ streamConnectorSockets[i] = streamConnectorSocket;
+ streamConnectorSocketCount++;
+ logger.trace("Added a streamConnectorSocket to the array " +
+ "StreamConnectorSocket and increased " +
+ "the count of streamConnectorSocketCount by one to " +
+ streamConnectorSocketCount);
}
}
+
if (streamConnectorSocketCount > 0)
+ {
return streamConnectorSockets;
+ }
}
+
return null;
}
@@ -742,20 +750,25 @@ public class IceUdpTransportManager
ex);
}
- //let's now update the next port var as best we can: we would assume
- //that all local candidates are bound on the same port and set it
- //to the one just above. if the assumption is wrong the next bind
- //would simply include one more bind retry.
+ // Attempt to minimize subsequent bind retries: see if we have allocated
+ // any ports from the dynamic range, and if so update the port tracker.
+ // Do NOT update the port tracker with non-dynamic ports (e.g. 4443
+ // coming from TCP) because this will force it to revert back it its
+ // configured min port. When maxPort is reached, allocation will begin
+ // from minPort again, so we don't have to worry about wraps.
try
{
- portTracker.setNextPort(
- 1
- + stream
- .getComponent(Component.RTCP)
- .getLocalCandidates()
- .get(0)
- .getTransportAddress()
- .getPort());
+ int maxAllocatedPort = getMaxAllocatedPort(
+ stream,
+ portTracker.getMinPort(),
+ portTracker.getMaxPort());
+
+ if(maxAllocatedPort > 0)
+ {
+ int nextPort = 1 + maxAllocatedPort;
+ portTracker.setNextPort(nextPort);
+ logger.debug("Updating the port tracker min port: " + nextPort);
+ }
}
catch(Throwable t)
{
@@ -768,6 +781,48 @@ public class IceUdpTransportManager
}
/**
+ * @return the highest local port used by any of the local candidates of
+ * {@code iceStream}, which falls in the range [{@code min}, {@code max}].
+ */
+ private int getMaxAllocatedPort(IceMediaStream iceStream, int min, int max)
+ {
+ return
+ Math.max(
+ getMaxAllocatedPort(
+ iceStream.getComponent(Component.RTP),
+ min, max),
+ getMaxAllocatedPort(
+ iceStream.getComponent(Component.RTCP),
+ min, max));
+ }
+
+ /**
+ * @return the highest local port used by any of the local candidates of
+ * {@code component}, which falls in the range [{@code min}, {@code max}].
+ */
+ private int getMaxAllocatedPort(Component component, int min, int max)
+ {
+ int maxAllocatedPort = -1;
+
+ if (component != null)
+ {
+ for (LocalCandidate candidate : component.getLocalCandidates())
+ {
+ int candidatePort = candidate.getTransportAddress().getPort();
+
+ if (min <= candidatePort
+ && candidatePort <= max
+ && maxAllocatedPort < candidatePort)
+ {
+ maxAllocatedPort = candidatePort;
+ }
+ }
+ }
+
+ return maxAllocatedPort;
+ }
+
+ /**
* Simply returns the list of local candidates that we gathered during the
* harvest.
*
@@ -898,8 +953,12 @@ public class IceUdpTransportManager
= transport.getChildExtensionsOfType(
CandidatePacketExtension.class);
- if (iceAgentStateIsRunning && (candidates.size() == 0))
+ if (iceAgentStateIsRunning && candidates.isEmpty())
+ {
+ logger.info("connectivity establishment has not been started " +
+ "because candidate list is empty");
return false;
+ }
String media = e.getKey();
IceMediaStream stream = iceAgent.getStream(media);
@@ -938,6 +997,12 @@ public class IceUdpTransportManager
if (candidate.getGeneration() != generation)
continue;
+ if (candidate.getIP() == null || "".equals(candidate.getIP()))
+ {
+ logger.warn("Skipped ICE candidate with empty IP");
+ continue;
+ }
+
Component component
= stream.getComponent(candidate.getComponent());
String relAddr;
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java b/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java
index a0387df..c3137ad 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java
@@ -151,7 +151,7 @@ public class InfoRetreiver
List<GenericDetail> result = new LinkedList<GenericDetail>();
try
{
- XMPPConnection connection = jabberProvider.getConnection();
+ Connection connection = jabberProvider.getConnection();
if(connection == null || !connection.isAuthenticated())
return null;
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java
index 438ea1b..de7578c 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberLoginStrategy.java
@@ -61,7 +61,7 @@ public interface JabberLoginStrategy
* @param resource the XMPP resource
* @return true to continue connecting, false to abort
*/
- public boolean login(XMPPConnection connection, String userName,
+ public boolean login(Connection connection, String userName,
String resource)
throws XMPPException;
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java
index 0ea9a54..f2473dd 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java
@@ -87,7 +87,7 @@ public class JingleNodesCandidate
* @return the <tt>RelayedCandidateDatagramSocket</tt> of this
* <tt>RelayedCandidate</tt>
*/
- public synchronized JingleNodesCandidateDatagramSocket
+ private synchronized JingleNodesCandidateDatagramSocket
getRelayedCandidateDatagramSocket()
{
if (jingleNodesCandidateDatagramSocket == null)
@@ -113,7 +113,7 @@ public class JingleNodesCandidate
* <tt>Candidate</tt>
*/
@Override
- public IceSocketWrapper getIceSocketWrapper()
+ protected IceSocketWrapper getCandidateIceSocketWrapper()
{
if (socket == null)
{
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java
index bb7d31b..d29be03 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java
@@ -36,7 +36,7 @@ import org.xmpp.jnodes.smack.*;
* @author Sebastien Vincent
*/
public class JingleNodesHarvester
- extends CandidateHarvester
+ extends AbstractCandidateHarvester
{
/**
* The <tt>Logger</tt> used by the <tt>JingleNodesHarvester</tt> class and
@@ -125,7 +125,7 @@ public class JingleNodesHarvester
}
}
- if (ciq != null && ciq.getRemoteport() > 0)
+ if (ciq != null)
{
ip = ciq.getHost();
port = ciq.getRemoteport();
@@ -136,6 +136,22 @@ public class JingleNodesHarvester
" local port: " + ciq.getLocalport());
}
+ if (ip == null || ciq.getRemoteport() == 0)
+ {
+ logger.warn("JN relay ignored because ip was null or port 0");
+ return candidates;
+ }
+
+ // Drop the scope or interface name if the relay sends it
+ // along in its IPv6 address. The scope/ifname is only valid on the
+ // host that owns the IP and we don't need it here.
+ int scopeIndex = ip.indexOf('%');
+ if (scopeIndex > 0)
+ {
+ logger.warn("Dropping scope from assumed IPv6 address " + ip);
+ ip = ip.substring(0, scopeIndex);
+ }
+
/* RTP */
TransportAddress relayedAddress = new TransportAddress(ip, port,
Transport.UDP);
@@ -160,6 +176,7 @@ public class JingleNodesHarvester
candidates.add(local);
}
}
+
return candidates;
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java
index 09c9462..4825e01 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByClientCertificateStrategy.java
@@ -116,7 +116,7 @@ class LoginByClientCertificateStrategy
* accepted.
* @throws XMPPException
*/
- public boolean login(XMPPConnection connection, String userName,
+ public boolean login(Connection connection, String userName,
String resource)
throws XMPPException
{
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java
index 43fc8a4..7034221 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/LoginByPasswordStrategy.java
@@ -115,7 +115,7 @@ public class LoginByPasswordStrategy
* @return always true.
* @throws XMPPException
*/
- public boolean login(XMPPConnection connection, String userName,
+ public boolean login(Connection connection, String userName,
String resource)
throws XMPPException
{
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java b/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java
index 926848c..8c801c6 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/MobileIndicator.java
@@ -222,11 +222,13 @@ public class MobileIndicator
/**
* Caps for user has been changed.
* @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user for which we're notified is online
*/
@Override
- public void userCapsNodeAdded(String user, String node, boolean online)
+ public void userCapsNodeAdded(String user, ArrayList<String> fullJids,
+ String node, boolean online)
{
updateMobileIndicatorUsingCaps(user);
}
@@ -234,11 +236,13 @@ public class MobileIndicator
/**
* Caps for user has been changed.
* @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user for which we're notified is online
*/
@Override
- public void userCapsNodeRemoved(String user, String node, boolean online)
+ public void userCapsNodeRemoved(String user, ArrayList<String> fullJids,
+ String node, boolean online)
{
updateMobileIndicatorUsingCaps(user);
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java
index 42a3916..2f35fef 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java
@@ -815,6 +815,17 @@ public class OperationSetBasicInstantMessagingJabberImpl
ForwardedPacketExtension.class);
if(extensions.isEmpty())
return;
+
+ // according to xep-0280 all carbons should come from
+ // our bare jid
+ if (!msg.getFrom().equals(
+ StringUtils.parseBareAddress(
+ jabberProvider.getOurJID())))
+ {
+ logger.info("Received a carbon copy with wrong from!");
+ return;
+ }
+
ForwardedPacketExtension forwardedExt = extensions.get(0);
msg = forwardedExt.getMessage();
if(msg == null || msg.getBody() == null)
@@ -1109,7 +1120,7 @@ public class OperationSetBasicInstantMessagingJabberImpl
NewMailNotificationIQ.NAMESPACE,
new NewMailNotificationProvider());
- XMPPConnection connection = jabberProvider.getConnection();
+ Connection connection = jabberProvider.getConnection();
connection.addPacketListener(
new MailboxIQListener(), new PacketTypeFilter(MailboxIQ.class));
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java
index cc36936..78027c0 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java
@@ -280,7 +280,10 @@ public class OperationSetBasicTelephonyJabberImpl
Iterable<PacketExtension> sessionInitiateExtensions)
throws OperationFailedException
{
- return createOutgoingCall(call, calleeAddress, null, null);
+ if (calleeAddress.contains("/"))
+ return createOutgoingCall(call, calleeAddress, calleeAddress, null);
+ else
+ return createOutgoingCall(call, calleeAddress, null, null);
}
/**
@@ -774,7 +777,7 @@ public class OperationSetBasicTelephonyJabberImpl
*/
private void unsubscribeForJinglePackets()
{
- XMPPConnection connection = protocolProvider.getConnection();
+ Connection connection = protocolProvider.getConnection();
if(connection != null)
connection.removePacketListener(this);
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java
index 0a6edfe..59cc17c 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetContactCapabilitiesJabberImpl.java
@@ -26,7 +26,7 @@ import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.util.*;
/**
* Represents an <tt>OperationSet</tt> to query the <tt>OperationSet</tt>s
@@ -52,6 +52,19 @@ public class OperationSetContactCapabilitiesJabberImpl
= Logger.getLogger(OperationSetContactCapabilitiesJabberImpl.class);
/**
+ * The name of the property used to control whether to use
+ * all resources to show capabilities
+ */
+ public static final String PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES =
+ "net.java.sip.communicator.XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES";
+
+ /**
+ * The default value for the capabilities setting
+ */
+ public static final boolean USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT =
+ true;
+
+ /**
* The list of <tt>OperationSet</tt> capabilities presumed to be supported
* by a <tt>Contact</tt> when it is offline.
*/
@@ -276,6 +289,42 @@ public class OperationSetContactCapabilitiesJabberImpl
}
/**
+ * Gets the largest set of <tt>OperationSet</tt>s supported from a
+ * list of full JIDs. The returned <tt>OperationSet</tt>s are considered
+ * by the associated protocol provider to capabilities possessed by the
+ * specified <tt>contact</tt>.
+ *
+ * @param fullJids a list of full JIDs in which to find the resource with
+ * the most capabilities.
+ * @return the <tt>Map</tt> listing the most <tt>OperationSet</tt>s
+ * considered by the associated protocol provider to be supported by the
+ * specified <tt>contact</tt> (i.e. to be possessed as capabilities).
+ * Each supported <tt>OperationSet</tt> capability is represented by a
+ * <tt>Map.Entry</tt> with key equal to the <tt>OperationSet</tt> class
+ * name and value equal to the respective <tt>OperationSet</tt> instance
+ */
+ protected Map<String, OperationSet> getLargestSupportedOperationSet(
+ ArrayList<String> fullJids)
+ {
+ Map<String, OperationSet> supportedOperationSets =
+ new HashMap<String, OperationSet>();
+ if (fullJids!=null)
+ {
+ for (String fullJid : fullJids)
+ {
+ Map<String, OperationSet> newSupportedOperationSets=
+ getSupportedOperationSets(fullJid, true);
+ if (newSupportedOperationSets.size()>
+ supportedOperationSets.size())
+ {
+ supportedOperationSets = newSupportedOperationSets;
+ }
+ }
+ }
+ return supportedOperationSets;
+ }
+
+ /**
* Gets the <tt>OperationSet</tt> corresponding to the specified
* <tt>Class</tt> and supported by the specified <tt>Contact</tt>. If the
* returned value is non-<tt>null</tt>, it indicates that the
@@ -387,17 +436,19 @@ public class OperationSetContactCapabilitiesJabberImpl
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user is currently online
* @see UserCapsNodeListener#userCapsNodeAdded(String, String, boolean)
*/
- public void userCapsNodeAdded(String user, String node, boolean online)
+ public void userCapsNodeAdded(String user, ArrayList<String> fullJids,
+ String node, boolean online)
{
/*
* It doesn't matter to us whether a caps node has been added or removed
* for the specified user because we report all changes.
*/
- userCapsNodeRemoved(user, node, online);
+ userCapsNodeChanged(user, fullJids, node, online);
}
/**
@@ -405,45 +456,86 @@ public class OperationSetContactCapabilitiesJabberImpl
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
+ * @param node the entity caps node#ver
+ * @param online indicates if the user is currently online
+ * @see UserCapsNodeListener#userCapsNodeAdded(String, String, boolean)
+ */
+ public void userCapsNodeRemoved(String user, ArrayList<String> fullJids,
+ String node, boolean online)
+ {
+ /*
+ * It doesn't matter to us whether a caps node has been added or removed
+ * for the specified user because we report all changes.
+ */
+ userCapsNodeChanged(user, fullJids, node, online);
+ }
+
+ /**
+ * Notifies this listener that an <tt>EntityCapsManager</tt> has changed a
+ * record for a specific user about the caps node the user has.
+ *
+ * @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the given user is online
- * @see UserCapsNodeListener#userCapsNodeRemoved(String, String, boolean)
*/
- public void userCapsNodeRemoved(String user, String node, boolean online)
+ public void userCapsNodeChanged(String user, ArrayList<String> fullJids,
+ String node, boolean online)
{
OperationSetPresence opsetPresence
- = parentProvider.getOperationSet(OperationSetPresence.class);
-
- if (opsetPresence != null)
- {
- String jid = StringUtils.parseBareAddress(user);
- Contact contact = opsetPresence.findContactByID(jid);
-
- // If the contact isn't null and is online we try to discover the
- // new set of operation sets and to notify interested parties.
- // Otherwise we ignore the event.
- if (contact != null)
+ = parentProvider.getOperationSet(OperationSetPresence.class);
+ if (opsetPresence != null) {
+ if(JabberActivator.getConfigurationService()
+ .getBoolean(
+ PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES,
+ USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT)
+ && !fullJids.isEmpty())
{
- if(online)
+ String bareJid = StringUtils.parseBareAddress(user);
+ Contact contact = opsetPresence.findContactByID(bareJid);
+ if (contact != null)
{
- // when going online we have received a presence
- // and make sure we discover this particular jid
- // for getSupportedOperationSets
fireContactCapabilitiesEvent(
contact,
- ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED,
- getSupportedOperationSets(user,
- online));
+ ContactCapabilitiesEvent.
+ SUPPORTED_OPERATION_SETS_CHANGED,
+ getLargestSupportedOperationSet(fullJids));
}
- else
+ }
+ else
+ {
+ String jid = StringUtils.parseBareAddress(user);
+ Contact contact = opsetPresence.findContactByID(jid);
+
+ // If the contact isn't null and is online we try to discover
+ // the new set of operation sets and to notify interested
+ // parties. Otherwise we ignore the event.
+ if (contact != null)
{
- // when offline, we use the contact, and selecting
- // the most connected jid
- // for getSupportedOperationSets
- fireContactCapabilitiesEvent(
- contact,
- ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED,
- getSupportedOperationSets(contact));
+ if(online)
+ {
+ // when going online we have received a presence
+ // and make sure we discover this particular jid
+ // for getSupportedOperationSets
+ fireContactCapabilitiesEvent(
+ contact,
+ ContactCapabilitiesEvent.
+ SUPPORTED_OPERATION_SETS_CHANGED,
+ getSupportedOperationSets(user,
+ online));
+ }
+ else
+ {
+ // when offline, we use the contact, and selecting
+ // the most connected jid
+ // for getSupportedOperationSets
+ fireContactCapabilitiesEvent(
+ contact,
+ ContactCapabilitiesEvent.
+ SUPPORTED_OPERATION_SETS_CHANGED,
+ getSupportedOperationSets(contact));
+ }
}
}
}
@@ -460,7 +552,8 @@ public class OperationSetContactCapabilitiesJabberImpl
{
// If the user goes offline we ensure to remove the caps node.
if (capsManager != null
- && evt.getNewStatus().getStatus() < PresenceStatus.ONLINE_THRESHOLD)
+ && evt.getNewStatus().getStatus() < PresenceStatus.ONLINE_THRESHOLD
+ && !evt.isResourceChanged())
{
capsManager.removeContactCapsNode(evt.getSourceContact());
}
@@ -469,31 +562,59 @@ public class OperationSetContactCapabilitiesJabberImpl
/**
* Fires event that contact capabilities has changed.
* @param user the user to search for its contact.
+ * @param fullJids a list of all resources of the user (full JIDs)
*/
- public void fireContactCapabilitiesChanged(String user)
+ public void fireContactCapabilitiesChanged(String user,
+ ArrayList<String> fullJids)
{
- OperationSetPresence opsetPresence
+ if(!JabberActivator.getConfigurationService()
+ .getBoolean(
+ PROP_XMPP_USE_ALL_RESOURCES_FOR_CAPABILITIES,
+ USE_ALL_RESOURCES_FOR_CAPABILITIES_DEFAULT)
+ || fullJids.isEmpty())
+ {
+ OperationSetPresence opsetPresence
= parentProvider.getOperationSet(OperationSetPresence.class);
- if (opsetPresence != null)
+ if (opsetPresence != null)
+ {
+ String userID = StringUtils.parseBareAddress(user);
+ Contact contact = opsetPresence.findContactByID(userID);
+
+ // this called by received discovery info for particular jid
+ // so we use its online and opsets for this particular jid
+ boolean online = false;
+ Presence presence = parentProvider.getConnection().getRoster()
+ .getPresence(user);
+ if(presence != null)
+ online = presence.isAvailable();
+
+ if(contact != null)
+ {
+ fireContactCapabilitiesEvent(
+ contact,
+ ContactCapabilitiesEvent.
+ SUPPORTED_OPERATION_SETS_CHANGED,
+ getSupportedOperationSets(user, online));
+ }
+ }
+ }
+ else
{
- String userID = StringUtils.parseBareAddress(user);
- Contact contact = opsetPresence.findContactByID(userID);
-
- // this called by received discovery info for particular jid
- // so we use its online and opsets for this particular jid
- boolean online = false;
- Presence presence = parentProvider.getConnection().getRoster()
- .getPresence(user);
- if(presence != null)
- online = presence.isAvailable();
-
- if(contact != null)
+ OperationSetPresence opsetPresence
+ = parentProvider.getOperationSet(OperationSetPresence.class);
+ if (opsetPresence != null)
{
- fireContactCapabilitiesEvent(
- contact,
- ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED,
- getSupportedOperationSets(user, online));
+ String bareJid = StringUtils.parseBareAddress(user);
+ Contact contact = opsetPresence.findContactByID(bareJid);
+ if(contact != null)
+ {
+ fireContactCapabilitiesEvent(
+ contact,
+ ContactCapabilitiesEvent.
+ SUPPORTED_OPERATION_SETS_CHANGED,
+ getLargestSupportedOperationSet(fullJids));
+ }
}
}
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java
index 0fb6979..7ea4453 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetJitsiMeetToolsJabberImpl.java
@@ -72,6 +72,16 @@ public class OperationSetJitsiMeetToolsJabberImpl
* {@inheritDoc}
*/
@Override
+ public void removePresenceExtension(ChatRoom chatRoom,
+ PacketExtension extension)
+ {
+ ((ChatRoomJabberImpl)chatRoom).removePresenceExtension(extension);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void setPresenceStatus(ChatRoom chatRoom, String statusMessage)
{
((ChatRoomJabberImpl)chatRoom).publishPresenceStatus(statusMessage);
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java
index 80bbb4e..cf53906 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java
@@ -471,10 +471,10 @@ public class OperationSetMultiUserChatJabberImpl
* Almost all <tt>MultiUserChat</tt> methods require an xmpp connection
* param so I added this method only for the sake of utility.
*
- * @return the XMPPConnection currently in use by the jabber provider or
+ * @return the XMPP connection currently in use by the jabber provider or
* null if jabber provider has yet to be initialized.
*/
- private XMPPConnection getXmppConnection()
+ private Connection getXmppConnection()
{
return (jabberProvider == null)
? null
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java
index 69c168c..c502824 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java
@@ -691,7 +691,7 @@ public class OperationSetPersistentPresenceJabberImpl
*/
assertConnected();
- XMPPConnection xmppConnection = parentProvider.getConnection();
+ Connection xmppConnection = parentProvider.getConnection();
if (xmppConnection == null)
{
@@ -1124,7 +1124,7 @@ public class OperationSetPersistentPresenceJabberImpl
ssContactList.cleanup();
- XMPPConnection connection = parentProvider.getConnection();
+ Connection connection = parentProvider.getConnection();
if(connection != null)
{
connection.removePacketListener(subscribtionPacketListener);
@@ -1528,6 +1528,19 @@ public class OperationSetPersistentPresenceJabberImpl
o2, parentProvider).getStatus()
- jabberStatusToPresenceStatus(
o1, parentProvider).getStatus();
+ // We have run out of "logical" ways to order
+ // the presences inside the TreeSet. We have
+ // make sure we are consinstent with equals.
+ // We do this by comparing the unique resource
+ // names. If this evaluates to 0 again, then we
+ // can safely assume this presence object
+ // represents the same resource and by that the
+ // same client.
+ if(res == 0)
+ {
+ res = o1.getFrom().compareTo(
+ o2.getFrom());
+ }
}
return res;
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java
index e81b43a..e5b83e7 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,585 +15,585 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.service.protocol.*;
-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.util.xml.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.packet.IQ.Type;
-import org.jivesoftware.smack.util.*;
-import org.jivesoftware.smackx.packet.*;
-
-/**
- * Implements <tt>OperationSetTelephonyConferencing</tt> for Jabber.
- *
- * @author Lyubomir Marinov
- * @author Sebastien Vincent
- * @author Boris Grozev
- * @author Pawel Domas
- */
-public class OperationSetTelephonyConferencingJabberImpl
- extends AbstractOperationSetTelephonyConferencing<
- ProtocolProviderServiceJabberImpl,
- OperationSetBasicTelephonyJabberImpl,
- CallJabberImpl,
- CallPeerJabberImpl,
- String>
- implements RegistrationStateChangeListener,
- PacketListener,
- PacketFilter
-
-{
- /**
- * The <tt>Logger</tt> used by the
- * <tt>OperationSetTelephonyConferencingJabberImpl</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class);
-
- /**
- * The minimum interval in milliseconds between COINs sent to a single
- * <tt>CallPeer</tt>.
- */
- private static final int COIN_MIN_INTERVAL = 200;
-
- /**
- * Property used to disable COIN notifications.
- */
- public static final String DISABLE_COIN_PROP_NAME
- = "net.java.sip.communicator.impl.protocol.jabber.DISABLE_COIN";
-
- /**
- * Synchronization object.
- */
- private final Object lock = new Object();
-
- /**
- * Field indicates whether COIN notification are disabled or not.
- */
- private boolean isCoinDisabled = false;
-
- /**
- * Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt>
- * instance which is to provide telephony conferencing services for the
- * specified Jabber <tt>ProtocolProviderService</tt> implementation.
- *
- * @param parentProvider the Jabber <tt>ProtocolProviderService</tt>
- * implementation which has requested the creation of the new instance and
- * for which the new instance is to provide telephony conferencing services
- */
- public OperationSetTelephonyConferencingJabberImpl(
- ProtocolProviderServiceJabberImpl parentProvider)
- {
- super(parentProvider);
-
- this.isCoinDisabled
- = JabberActivator.getConfigurationService()
- .getBoolean(DISABLE_COIN_PROP_NAME, false);
- }
-
- /**
- * Notifies all <tt>CallPeer</tt>s associated with a specific <tt>Call</tt>
- * about changes in the telephony conference-related information. In
- * contrast, {@link #notifyAll()} notifies all <tt>CallPeer</tt>s associated
- * with the telephony conference in which a specific <tt>Call</tt> is
- * participating.
- *
- * @param call the <tt>Call</tt> whose <tt>CallPeer</tt>s are to be notified
- * about changes in the telephony conference-related information
- */
- @Override
- protected void notifyCallPeers(Call call)
- {
- if (!isCoinDisabled && call.isConferenceFocus())
- {
- synchronized (lock)
- {
- // send conference-info to all CallPeers of the specified call.
- for (Iterator<? extends CallPeer> i = call.getCallPeers();
- i.hasNext();)
- {
- notify(i.next());
- }
- }
- }
- }
-
- /**
- * Notifies a specific <tt>CallPeer</tt> about changes in the telephony
- * conference-related information.
- *
- * @param callPeer the <tt>CallPeer</tt> to notify.
- */
- private void notify(CallPeer callPeer)
- {
- if(!(callPeer instanceof CallPeerJabberImpl))
- return;
-
- //Don't send COINs to peers with might not be ready to accept COINs yet
- CallPeerState peerState = callPeer.getState();
- if (peerState == CallPeerState.CONNECTING
- || peerState == CallPeerState.UNKNOWN
- || peerState == CallPeerState.INITIATING_CALL
- || peerState == CallPeerState.DISCONNECTED
- || peerState == CallPeerState.FAILED)
- return;
-
- final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer;
-
- final long timeSinceLastCoin = System.currentTimeMillis()
- - callPeerJabber.getLastConferenceInfoSentTimestamp();
- if (timeSinceLastCoin < COIN_MIN_INTERVAL)
- {
- if (callPeerJabber.isConfInfoScheduled())
- return;
-
- logger.info("Scheduling to send a COIN to " + callPeerJabber);
- callPeerJabber.setConfInfoScheduled(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
- = parentProvider.getDiscoveryManager().discoverInfo(to);
-
- if (!discoverInfo.containsFeature(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN))
- {
- logger.info(callPeer.getAddress() + " does not support COIN");
- callPeerJabber.setConfInfoScheduled(false);
- return;
- }
- }
- catch (XMPPException xmppe)
- {
- logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
- }
-
- ConferenceInfoDocument currentConfInfo
- = getCurrentConferenceInfo(callPeerJabber);
- ConferenceInfoDocument lastSentConfInfo
- = callPeerJabber.getLastConferenceInfoSent();
-
- ConferenceInfoDocument diff;
-
- if (lastSentConfInfo == null)
- diff = currentConfInfo;
- else
- diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
-
- if (diff != null)
- {
- int newVersion
- = lastSentConfInfo == null
- ? 1
- : lastSentConfInfo.getVersion() + 1;
- diff.setVersion(newVersion);
-
- IQ iq = getConferenceInfo(callPeerJabber, diff);
-
- if (iq != null)
- {
- 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());
- }
- }
- callPeerJabber.setConfInfoScheduled(false);
- }
-
- /**
- * Generates the conference-info IQ 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.
- *
- * @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
- * @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,
- final ConferenceInfoDocument confInfo)
- {
- String callPeerSID = callPeer.getSID();
-
- if (callPeerSID == null)
- return null;
-
- 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);
-
- return iq;
- }
-
- /**
- * Implementation of method <tt>registrationStateChange</tt> from
- * interface RegistrationStateChangeListener for setting up (or down)
- * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available
- *
- * @param evt the event received
- */
- @Override
- public void registrationStateChanged(RegistrationStateChangeEvent evt)
- {
- super.registrationStateChanged(evt);
-
- RegistrationState registrationState = evt.getNewState();
-
- if (RegistrationState.REGISTERED.equals(registrationState))
- {
- if(logger.isDebugEnabled())
- logger.debug("Subscribes to Coin packets");
- subscribeForCoinPackets();
- }
- else if (RegistrationState.UNREGISTERED.equals(registrationState))
- {
- if(logger.isDebugEnabled())
- logger.debug("Unsubscribes to Coin packets");
- unsubscribeForCoinPackets();
- }
- }
-
- /**
- * Creates a new outgoing <tt>Call</tt> into which conference callees are to
- * be invited by this <tt>OperationSetTelephonyConferencing</tt>.
- *
- * @return a new outgoing <tt>Call</tt> into which conference callees are to
- * be invited by this <tt>OperationSetTelephonyConferencing</tt>
- * @throws OperationFailedException if anything goes wrong
- */
- @Override
- protected CallJabberImpl createOutgoingCall()
- throws OperationFailedException
- {
- return new CallJabberImpl(getBasicTelephony());
- }
-
- /**
- * {@inheritDoc}
- *
- * 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#inviteCalleeToCall(String,Call)}.
- */
- @Override
- protected CallPeer doInviteCalleeToCall(
- String calleeAddress,
- CallJabberImpl call)
- throws OperationFailedException
- {
- return
- getBasicTelephony().createOutgoingCall(
- call,
- calleeAddress,
- Arrays.asList(
- new PacketExtension[]
- {
- new CoinPacketExtension(true)
- }));
- }
-
- /**
- * Parses a <tt>String</tt> value which represents a callee address
- * specified by the user into an object which is to actually represent the
- * callee during the invitation to a conference <tt>Call</tt>.
- *
- * @param calleeAddressString a <tt>String</tt> value which represents a
- * callee address to be parsed into an object which is to actually represent
- * the callee during the invitation to a conference <tt>Call</tt>
- * @return an object which is to actually represent the specified
- * <tt>calleeAddressString</tt> during the invitation to a conference
- * <tt>Call</tt>
- * @throws OperationFailedException if parsing the specified
- * <tt>calleeAddressString</tt> fails
- */
- @Override
- protected String parseAddressString(String calleeAddressString)
- throws OperationFailedException
- {
- return getBasicTelephony().getFullCalleeURI(calleeAddressString);
- }
-
- /**
- * Subscribes us to notifications about incoming Coin packets.
- */
- private void subscribeForCoinPackets()
- {
- parentProvider.getConnection().addPacketListener(this, this);
- }
-
- /**
- * Unsubscribes us from notifications about incoming Coin packets.
- */
- private void unsubscribeForCoinPackets()
- {
- XMPPConnection connection = parentProvider.getConnection();
-
- if (connection != null)
- connection.removePacketListener(this);
- }
-
- /**
- * Tests whether or not the specified packet should be handled by this
- * operation set. This method is called by smack prior to packet delivery
- * and it would only accept <tt>CoinIQ</tt>s.
- *
- * @param packet the packet to test.
- * @return true if and only if <tt>packet</tt> passes the filter.
- */
- public boolean accept(Packet packet)
- {
- return (packet instanceof CoinIQ);
- }
-
- /**
- * Handles incoming jingle packets and passes them to the corresponding
- * method based on their action.
- *
- * @param packet the packet to process.
- */
- public void processPacket(Packet packet)
- {
- CoinIQ coinIQ = (CoinIQ) packet;
- String errorMessage = null;
-
- //first ack all "set" requests.
- IQ.Type type = coinIQ.getType();
- if (type == IQ.Type.SET)
- {
- IQ ack = IQ.createResultIQ(coinIQ);
-
- parentProvider.getConnection().sendPacket(ack);
- }
- else if(type == IQ.Type.ERROR)
- {
- XMPPError error = coinIQ.getError();
- if(error != null)
- {
- String msg = error.getMessage();
- errorMessage = ((msg != null)? (msg + " ") : "")
- + "Error code: " + error.getCode();
- }
-
- logger.error("Received error in COIN packet. "+errorMessage);
- }
-
- String sid = coinIQ.getSID();
-
- if (sid != null)
- {
- CallPeerJabberImpl callPeer
- = getBasicTelephony().getActiveCallsRepository().findCallPeer(
- sid);
-
-
- if (callPeer != null)
- {
- if(type == IQ.Type.ERROR)
- {
- callPeer.fireConferenceMemberErrorEvent(errorMessage);
- return;
- }
-
- if (logger.isDebugEnabled())
- logger.debug("Processing COIN from " + coinIQ.getFrom()
- + " (version=" + coinIQ.getVersion() + ")");
-
- handleCoin(callPeer, coinIQ);
- }
- }
- }
-
- /**
- * Handles a specific <tt>CoinIQ</tt> sent from a specific
- * <tt>CallPeer</tt>.
- *
- * @param callPeer the <tt>CallPeer</tt> from which the specified
- * <tt>CoinIQ</tt> was sent
- * @param coinIQ the <tt>CoinIQ</tt> which was sent from the specified
- * <tt>callPeer</tt>
- */
- private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ)
- {
- 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.
- */
- @Override
- protected ConferenceInfoDocument getCurrentConferenceInfo(
- MediaAwareCallPeer<?,?,?> callPeer)
- {
- ConferenceInfoDocument confInfo
- = super.getCurrentConferenceInfo(callPeer);
-
- if (callPeer instanceof CallPeerJabberImpl
- && confInfo != null)
- {
- confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID());
- }
- return confInfo;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getLocalEntity(CallPeer callPeer)
- {
- JingleIQ sessionIQ = ((CallPeerJabberImpl)callPeer).getSessionIQ();
- String from = sessionIQ.getFrom();
- String chatRoomName = StringUtils.parseBareAddress(from);
- OperationSetMultiUserChatJabberImpl opSetMUC
- = (OperationSetMultiUserChatJabberImpl)
- parentProvider.getOperationSet(OperationSetMultiUserChat.class);
- ChatRoom room = null;
- if(opSetMUC != null)
- room = opSetMUC.getChatRoom(chatRoomName);
-
- if(room != null)
- return "xmpp:" + chatRoomName + "/" + room.getUserNickname();
-
- return "xmpp:" + parentProvider.getOurJID();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getLocalDisplayName()
- {
- return null;
- }
-
- /**
- * {@inheritDoc}
- *
- * The URI of the returned <tt>ConferenceDescription</tt> is the occupant
- * JID with which we have joined the room.
- *
- * If a Videobridge is available for our <tt>ProtocolProviderService</tt>
- * we use it. TODO: this should be relaxed when we refactor the Videobridge
- * implementation, so that any Videobridge (on any protocol provider) can
- * be used.
- */
- @Override
- public ConferenceDescription setupConference(final ChatRoom chatRoom)
- {
- OperationSetVideoBridge videoBridge
- = parentProvider.getOperationSet(OperationSetVideoBridge.class);
- boolean isVideobridge = (videoBridge != null) && videoBridge.isActive();
-
- CallJabberImpl call = new CallJabberImpl(getBasicTelephony());
- call.setAutoAnswer(true);
-
- String uri = "xmpp:" + chatRoom.getIdentifier() +
- "/" + chatRoom.getUserNickname();
-
- ConferenceDescription cd
- = new ConferenceDescription(uri, call.getCallID());
-
- call.addCallChangeListener(new CallChangeListener()
- {
- @Override
- public void callStateChanged(CallChangeEvent ev)
- {
- if(CallState.CALL_ENDED.equals(ev.getNewValue()))
- chatRoom.publishConference(null, null);
- }
-
- @Override
- public void callPeerRemoved(CallPeerEvent ev)
- {
- }
-
- @Override
- public void callPeerAdded(CallPeerEvent ev)
- {
- }
- });
- if (isVideobridge)
- {
- call.setConference(new MediaAwareCallConference(true));
-
- //For Jitsi Videobridge we set the transports to RAW-UDP, otherwise
- //we leave them empty (meaning both RAW-UDP and ICE could be used)
- cd.addTransport(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0);
- }
-
- if (logger.isInfoEnabled())
- {
- logger.info("Setup a conference with uri=" + uri + " and callid=" +
- call.getCallID() + ". Videobridge in use: " + isVideobridge);
- }
-
- return cd;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.service.protocol.*;
+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.util.xml.*;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Implements <tt>OperationSetTelephonyConferencing</tt> for Jabber.
+ *
+ * @author Lyubomir Marinov
+ * @author Sebastien Vincent
+ * @author Boris Grozev
+ * @author Pawel Domas
+ */
+public class OperationSetTelephonyConferencingJabberImpl
+ extends AbstractOperationSetTelephonyConferencing<
+ ProtocolProviderServiceJabberImpl,
+ OperationSetBasicTelephonyJabberImpl,
+ CallJabberImpl,
+ CallPeerJabberImpl,
+ String>
+ implements RegistrationStateChangeListener,
+ PacketListener,
+ PacketFilter
+
+{
+ /**
+ * The <tt>Logger</tt> used by the
+ * <tt>OperationSetTelephonyConferencingJabberImpl</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class);
+
+ /**
+ * The minimum interval in milliseconds between COINs sent to a single
+ * <tt>CallPeer</tt>.
+ */
+ private static final int COIN_MIN_INTERVAL = 200;
+
+ /**
+ * Property used to disable COIN notifications.
+ */
+ public static final String DISABLE_COIN_PROP_NAME
+ = "net.java.sip.communicator.impl.protocol.jabber.DISABLE_COIN";
+
+ /**
+ * Synchronization object.
+ */
+ private final Object lock = new Object();
+
+ /**
+ * Field indicates whether COIN notification are disabled or not.
+ */
+ private boolean isCoinDisabled = false;
+
+ /**
+ * Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt>
+ * instance which is to provide telephony conferencing services for the
+ * specified Jabber <tt>ProtocolProviderService</tt> implementation.
+ *
+ * @param parentProvider the Jabber <tt>ProtocolProviderService</tt>
+ * implementation which has requested the creation of the new instance and
+ * for which the new instance is to provide telephony conferencing services
+ */
+ public OperationSetTelephonyConferencingJabberImpl(
+ ProtocolProviderServiceJabberImpl parentProvider)
+ {
+ super(parentProvider);
+
+ this.isCoinDisabled
+ = JabberActivator.getConfigurationService()
+ .getBoolean(DISABLE_COIN_PROP_NAME, false);
+ }
+
+ /**
+ * Notifies all <tt>CallPeer</tt>s associated with a specific <tt>Call</tt>
+ * about changes in the telephony conference-related information. In
+ * contrast, {@link #notifyAll()} notifies all <tt>CallPeer</tt>s associated
+ * with the telephony conference in which a specific <tt>Call</tt> is
+ * participating.
+ *
+ * @param call the <tt>Call</tt> whose <tt>CallPeer</tt>s are to be notified
+ * about changes in the telephony conference-related information
+ */
+ @Override
+ protected void notifyCallPeers(Call call)
+ {
+ if (!isCoinDisabled && call.isConferenceFocus())
+ {
+ synchronized (lock)
+ {
+ // send conference-info to all CallPeers of the specified call.
+ for (Iterator<? extends CallPeer> i = call.getCallPeers();
+ i.hasNext();)
+ {
+ notify(i.next());
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies a specific <tt>CallPeer</tt> about changes in the telephony
+ * conference-related information.
+ *
+ * @param callPeer the <tt>CallPeer</tt> to notify.
+ */
+ private void notify(CallPeer callPeer)
+ {
+ if(!(callPeer instanceof CallPeerJabberImpl))
+ return;
+
+ //Don't send COINs to peers with might not be ready to accept COINs yet
+ CallPeerState peerState = callPeer.getState();
+ if (peerState == CallPeerState.CONNECTING
+ || peerState == CallPeerState.UNKNOWN
+ || peerState == CallPeerState.INITIATING_CALL
+ || peerState == CallPeerState.DISCONNECTED
+ || peerState == CallPeerState.FAILED)
+ return;
+
+ final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer;
+
+ final long timeSinceLastCoin = System.currentTimeMillis()
+ - callPeerJabber.getLastConferenceInfoSentTimestamp();
+ if (timeSinceLastCoin < COIN_MIN_INTERVAL)
+ {
+ if (callPeerJabber.isConfInfoScheduled())
+ return;
+
+ logger.info("Scheduling to send a COIN to " + callPeerJabber);
+ callPeerJabber.setConfInfoScheduled(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
+ = parentProvider.getDiscoveryManager().discoverInfo(to);
+
+ if (!discoverInfo.containsFeature(
+ ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN))
+ {
+ logger.info(callPeer.getAddress() + " does not support COIN");
+ callPeerJabber.setConfInfoScheduled(false);
+ return;
+ }
+ }
+ catch (XMPPException xmppe)
+ {
+ logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
+ }
+
+ ConferenceInfoDocument currentConfInfo
+ = getCurrentConferenceInfo(callPeerJabber);
+ ConferenceInfoDocument lastSentConfInfo
+ = callPeerJabber.getLastConferenceInfoSent();
+
+ ConferenceInfoDocument diff;
+
+ if (lastSentConfInfo == null)
+ diff = currentConfInfo;
+ else
+ diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
+
+ if (diff != null)
+ {
+ int newVersion
+ = lastSentConfInfo == null
+ ? 1
+ : lastSentConfInfo.getVersion() + 1;
+ diff.setVersion(newVersion);
+
+ IQ iq = getConferenceInfo(callPeerJabber, diff);
+
+ if (iq != null)
+ {
+ 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());
+ }
+ }
+ callPeerJabber.setConfInfoScheduled(false);
+ }
+
+ /**
+ * Generates the conference-info IQ 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.
+ *
+ * @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
+ * @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,
+ final ConferenceInfoDocument confInfo)
+ {
+ String callPeerSID = callPeer.getSID();
+
+ if (callPeerSID == null)
+ return null;
+
+ 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);
+
+ return iq;
+ }
+
+ /**
+ * Implementation of method <tt>registrationStateChange</tt> from
+ * interface RegistrationStateChangeListener for setting up (or down)
+ * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available
+ *
+ * @param evt the event received
+ */
+ @Override
+ public void registrationStateChanged(RegistrationStateChangeEvent evt)
+ {
+ super.registrationStateChanged(evt);
+
+ RegistrationState registrationState = evt.getNewState();
+
+ if (RegistrationState.REGISTERED.equals(registrationState))
+ {
+ if(logger.isDebugEnabled())
+ logger.debug("Subscribes to Coin packets");
+ subscribeForCoinPackets();
+ }
+ else if (RegistrationState.UNREGISTERED.equals(registrationState))
+ {
+ if(logger.isDebugEnabled())
+ logger.debug("Unsubscribes to Coin packets");
+ unsubscribeForCoinPackets();
+ }
+ }
+
+ /**
+ * Creates a new outgoing <tt>Call</tt> into which conference callees are to
+ * be invited by this <tt>OperationSetTelephonyConferencing</tt>.
+ *
+ * @return a new outgoing <tt>Call</tt> into which conference callees are to
+ * be invited by this <tt>OperationSetTelephonyConferencing</tt>
+ * @throws OperationFailedException if anything goes wrong
+ */
+ @Override
+ protected CallJabberImpl createOutgoingCall()
+ throws OperationFailedException
+ {
+ return new CallJabberImpl(getBasicTelephony());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * 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#inviteCalleeToCall(String,Call)}.
+ */
+ @Override
+ protected CallPeer doInviteCalleeToCall(
+ String calleeAddress,
+ CallJabberImpl call)
+ throws OperationFailedException
+ {
+ return
+ getBasicTelephony().createOutgoingCall(
+ call,
+ calleeAddress,
+ Arrays.asList(
+ new PacketExtension[]
+ {
+ new CoinPacketExtension(true)
+ }));
+ }
+
+ /**
+ * Parses a <tt>String</tt> value which represents a callee address
+ * specified by the user into an object which is to actually represent the
+ * callee during the invitation to a conference <tt>Call</tt>.
+ *
+ * @param calleeAddressString a <tt>String</tt> value which represents a
+ * callee address to be parsed into an object which is to actually represent
+ * the callee during the invitation to a conference <tt>Call</tt>
+ * @return an object which is to actually represent the specified
+ * <tt>calleeAddressString</tt> during the invitation to a conference
+ * <tt>Call</tt>
+ * @throws OperationFailedException if parsing the specified
+ * <tt>calleeAddressString</tt> fails
+ */
+ @Override
+ protected String parseAddressString(String calleeAddressString)
+ throws OperationFailedException
+ {
+ return getBasicTelephony().getFullCalleeURI(calleeAddressString);
+ }
+
+ /**
+ * Subscribes us to notifications about incoming Coin packets.
+ */
+ private void subscribeForCoinPackets()
+ {
+ parentProvider.getConnection().addPacketListener(this, this);
+ }
+
+ /**
+ * Unsubscribes us from notifications about incoming Coin packets.
+ */
+ private void unsubscribeForCoinPackets()
+ {
+ Connection connection = parentProvider.getConnection();
+
+ if (connection != null)
+ connection.removePacketListener(this);
+ }
+
+ /**
+ * Tests whether or not the specified packet should be handled by this
+ * operation set. This method is called by smack prior to packet delivery
+ * and it would only accept <tt>CoinIQ</tt>s.
+ *
+ * @param packet the packet to test.
+ * @return true if and only if <tt>packet</tt> passes the filter.
+ */
+ public boolean accept(Packet packet)
+ {
+ return (packet instanceof CoinIQ);
+ }
+
+ /**
+ * Handles incoming jingle packets and passes them to the corresponding
+ * method based on their action.
+ *
+ * @param packet the packet to process.
+ */
+ public void processPacket(Packet packet)
+ {
+ CoinIQ coinIQ = (CoinIQ) packet;
+ String errorMessage = null;
+
+ //first ack all "set" requests.
+ IQ.Type type = coinIQ.getType();
+ if (type == IQ.Type.SET)
+ {
+ IQ ack = IQ.createResultIQ(coinIQ);
+
+ parentProvider.getConnection().sendPacket(ack);
+ }
+ else if(type == IQ.Type.ERROR)
+ {
+ XMPPError error = coinIQ.getError();
+ if(error != null)
+ {
+ String msg = error.getMessage();
+ errorMessage = ((msg != null)? (msg + " ") : "")
+ + "Error code: " + error.getCode();
+ }
+
+ logger.error("Received error in COIN packet. "+errorMessage);
+ }
+
+ String sid = coinIQ.getSID();
+
+ if (sid != null)
+ {
+ CallPeerJabberImpl callPeer
+ = getBasicTelephony().getActiveCallsRepository().findCallPeer(
+ sid);
+
+
+ if (callPeer != null)
+ {
+ if(type == IQ.Type.ERROR)
+ {
+ callPeer.fireConferenceMemberErrorEvent(errorMessage);
+ return;
+ }
+
+ if (logger.isDebugEnabled())
+ logger.debug("Processing COIN from " + coinIQ.getFrom()
+ + " (version=" + coinIQ.getVersion() + ")");
+
+ handleCoin(callPeer, coinIQ);
+ }
+ }
+ }
+
+ /**
+ * Handles a specific <tt>CoinIQ</tt> sent from a specific
+ * <tt>CallPeer</tt>.
+ *
+ * @param callPeer the <tt>CallPeer</tt> from which the specified
+ * <tt>CoinIQ</tt> was sent
+ * @param coinIQ the <tt>CoinIQ</tt> which was sent from the specified
+ * <tt>callPeer</tt>
+ */
+ private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ)
+ {
+ 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.
+ */
+ @Override
+ protected ConferenceInfoDocument getCurrentConferenceInfo(
+ MediaAwareCallPeer<?,?,?> callPeer)
+ {
+ ConferenceInfoDocument confInfo
+ = super.getCurrentConferenceInfo(callPeer);
+
+ if (callPeer instanceof CallPeerJabberImpl
+ && confInfo != null)
+ {
+ confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID());
+ }
+ return confInfo;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getLocalEntity(CallPeer callPeer)
+ {
+ JingleIQ sessionIQ = ((CallPeerJabberImpl)callPeer).getSessionIQ();
+ String from = sessionIQ.getFrom();
+ String chatRoomName = StringUtils.parseBareAddress(from);
+ OperationSetMultiUserChatJabberImpl opSetMUC
+ = (OperationSetMultiUserChatJabberImpl)
+ parentProvider.getOperationSet(OperationSetMultiUserChat.class);
+ ChatRoom room = null;
+ if(opSetMUC != null)
+ room = opSetMUC.getChatRoom(chatRoomName);
+
+ if(room != null)
+ return "xmpp:" + chatRoomName + "/" + room.getUserNickname();
+
+ return "xmpp:" + parentProvider.getOurJID();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getLocalDisplayName()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * The URI of the returned <tt>ConferenceDescription</tt> is the occupant
+ * JID with which we have joined the room.
+ *
+ * If a Videobridge is available for our <tt>ProtocolProviderService</tt>
+ * we use it. TODO: this should be relaxed when we refactor the Videobridge
+ * implementation, so that any Videobridge (on any protocol provider) can
+ * be used.
+ */
+ @Override
+ public ConferenceDescription setupConference(final ChatRoom chatRoom)
+ {
+ OperationSetVideoBridge videoBridge
+ = parentProvider.getOperationSet(OperationSetVideoBridge.class);
+ boolean isVideobridge = (videoBridge != null) && videoBridge.isActive();
+
+ CallJabberImpl call = new CallJabberImpl(getBasicTelephony());
+ call.setAutoAnswer(true);
+
+ String uri = "xmpp:" + chatRoom.getIdentifier() +
+ "/" + chatRoom.getUserNickname();
+
+ ConferenceDescription cd
+ = new ConferenceDescription(uri, call.getCallID());
+
+ call.addCallChangeListener(new CallChangeListener()
+ {
+ @Override
+ public void callStateChanged(CallChangeEvent ev)
+ {
+ if(CallState.CALL_ENDED.equals(ev.getNewValue()))
+ chatRoom.publishConference(null, null);
+ }
+
+ @Override
+ public void callPeerRemoved(CallPeerEvent ev)
+ {
+ }
+
+ @Override
+ public void callPeerAdded(CallPeerEvent ev)
+ {
+ }
+ });
+ if (isVideobridge)
+ {
+ call.setConference(new MediaAwareCallConference(true));
+
+ //For Jitsi Videobridge we set the transports to RAW-UDP, otherwise
+ //we leave them empty (meaning both RAW-UDP and ICE could be used)
+ cd.addTransport(
+ ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0);
+ }
+
+ if (logger.isInfoEnabled())
+ {
+ logger.info("Setup a conference with uri=" + uri + " and callid=" +
+ call.getCallID() + ". Videobridge in use: " + isVideobridge);
+ }
+
+ return cd;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java
index b7e13ea..5d3dd8b 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,282 +15,282 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * Implements <tt>OperationSetVideoBridge</tt> for Jabber.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class OperationSetVideoBridgeImpl
- implements OperationSetVideoBridge,
- PacketFilter,
- PacketListener,
- RegistrationStateChangeListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>OperationSetVideoBridgeImpl</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(OperationSetVideoBridgeImpl.class);
-
- /**
- * The <tt>ProtocolProviderService</tt> implementation which initialized
- * this instance, owns it and is often referred to as its parent.
- */
- private final ProtocolProviderServiceJabberImpl protocolProvider;
-
- /**
- * Creates an instance of <tt>OperationSetVideoBridgeImpl</tt> by
- * specifying the parent <tt>ProtocolProviderService</tt> announcing this
- * operation set.
- *
- * @param protocolProvider the parent Jabber protocol provider
- */
- public OperationSetVideoBridgeImpl(
- ProtocolProviderServiceJabberImpl protocolProvider)
- {
- this.protocolProvider = protocolProvider;
- this.protocolProvider.addRegistrationStateChangeListener(this);
- }
-
- /**
- * Implements {@link PacketFilter}. Determines whether this instance is
- * interested in a specific {@link Packet}.
- * <tt>OperationSetVideoBridgeImpl</tt> returns <tt>true</tt> if the
- * specified <tt>packet</tt> is a {@link ColibriConferenceIQ}; otherwise,
- * <tt>false</tt>.
- *
- * @param packet the <tt>Packet</tt> to be determined whether this instance
- * is interested in it
- * @return <tt>true</tt> if the specified <tt>packet</tt> is a
- * <tt>ColibriConferenceIQ</tt>; otherwise, <tt>false</tt>
- */
- public boolean accept(Packet packet)
- {
- return (packet instanceof ColibriConferenceIQ);
- }
-
- /**
- * Creates a conference call with the specified callees as call peers via a
- * video bridge provided by the parent Jabber provider.
- *
- * @param callees the list of addresses that we should call
- * @return the newly created conference call containing all CallPeers
- * @throws OperationFailedException if establishing the conference call
- * fails
- * @throws OperationNotSupportedException if the provider does not have any
- * conferencing features.
- */
- public Call createConfCall(String[] callees)
- throws OperationFailedException,
- OperationNotSupportedException
- {
- return
- protocolProvider
- .getOperationSet(OperationSetTelephonyConferencing.class)
- .createConfCall(
- callees,
- new MediaAwareCallConference(true));
- }
-
- /**
- * Invites the callee represented by the specified uri to an already
- * existing call using a video bridge provided by the parent Jabber provider.
- * The difference between this method and createConfCall is that
- * inviteCalleeToCall allows a user to add new peers to an already
- * established conference.
- *
- * @param uri the callee to invite to an existing conf call.
- * @param call the call that we should invite the callee to.
- * @return the CallPeer object corresponding to the callee represented by
- * the specified uri.
- * @throws OperationFailedException if inviting the specified callee to the
- * specified call fails
- * @throws OperationNotSupportedException if allowing additional callees to
- * a pre-established call is not supported.
- */
- public CallPeer inviteCalleeToCall(String uri, Call call)
- throws OperationFailedException,
- OperationNotSupportedException
- {
- return
- protocolProvider
- .getOperationSet(OperationSetTelephonyConferencing.class)
- .inviteCalleeToCall(uri, call);
- }
-
- /**
- * Indicates if there's an active video bridge available at this moment. The
- * Jabber provider may announce support for video bridge, but it should not
- * be used for calling until it becomes actually active.
- *
- * @return <tt>true</tt> to indicate that there's currently an active
- * available video bridge, <tt>false</tt> - otherwise
- */
- public boolean isActive()
- {
- String jitsiVideobridge = protocolProvider.getJitsiVideobridge();
-
- return ((jitsiVideobridge != null) && (jitsiVideobridge.length() > 0));
- }
-
- /**
- * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
- * been received.
- *
- * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
- * received
- */
- private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * The application is not a Jitsi Videobridge server, it is a client.
- * Consequently, the specified ColibriConferenceIQ is sent to it in
- * relation to the part of the application's functionality which makes
- * requests to a Jitsi Videobridge server i.e. CallJabberImpl.
- *
- * Additionally, the method processColibriConferenceIQ is presently tasked
- * with processing ColibriConferenceIQ requests only. They are SET IQs
- * sent by the Jitsi Videobridge server to notify the application about
- * updates in the states of (colibri) conferences organized by the
- * application.
- */
- if (IQ.Type.SET.equals(conferenceIQ.getType())
- && conferenceIQ.getID() != null)
- {
- OperationSetBasicTelephony<?> basicTelephony
- = protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
-
- if (basicTelephony != null)
- {
- Iterator<? extends Call> i = basicTelephony.getActiveCalls();
-
- while (i.hasNext())
- {
- Call call = i.next();
-
- if (call instanceof CallJabberImpl)
- {
- CallJabberImpl callJabberImpl = (CallJabberImpl) call;
- MediaAwareCallConference conference
- = callJabberImpl.getConference();
-
- if ((conference != null)
- && conference.isJitsiVideobridge())
- {
- /*
- * TODO We may want to disallow rogue CallJabberImpl
- * instances which may throw an exception to prevent
- * the conferenceIQ from reaching the CallJabberImpl
- * instance which it was meant for.
- */
- if (callJabberImpl.processColibriConferenceIQ(
- conferenceIQ))
- break;
- }
- }
- }
- }
- }
- }
-
- /**
- * Implements {@link PacketListener}. Notifies this instance that a specific
- * {@link Packet} (which this instance has already expressed interest into
- * by returning <tt>true</tt> from {@link #accept(Packet)}) has been
- * received.
- *
- * @param packet the <tt>Packet</tt> which has been received and which this
- * instance is given a chance to process
- */
- public void processPacket(Packet packet)
- {
- /*
- * As we do elsewhere, acknowledge the receipt of the Packet first and
- * then go about our business with it.
- */
- IQ iq = (IQ) packet;
-
- if (iq.getType() == IQ.Type.SET)
- protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq));
-
- /*
- * Now that the acknowledging is out of the way, do go about our
- * business with the Packet.
- */
- ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq;
- boolean interrupted = false;
-
- try
- {
- processColibriConferenceIQ(conferenceIQ);
- }
- catch (Throwable t)
- {
- logger.error(
- "An error occurred during the processing of a "
- + packet.getClass().getName() + " packet",
- t);
-
- if (t instanceof InterruptedException)
- {
- /*
- * We cleared the interrupted state of the current Thread by
- * catching the InterruptedException. However, we do not really
- * care whether the current Thread has been interrupted - we
- * caught the InterruptedException because we want to swallow
- * any Throwable. Consequently, we should better restore the
- * interrupted state.
- */
- interrupted = true;
- }
- else if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- }
- if (interrupted)
- Thread.currentThread().interrupt();
- }
-
- /**
- * {@inheritDoc}
- *
- * Implements {@link RegistrationStateChangeListener}. Notifies this
- * instance that there has been a change in the <tt>RegistrationState</tt>
- * of {@link #protocolProvider}. Subscribes this instance to
- * {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is
- * registered and unsubscribes it as soon as <tt>protocolProvider</tt> is
- * unregistered.
- */
- public void registrationStateChanged(RegistrationStateChangeEvent ev)
- {
- RegistrationState registrationState = ev.getNewState();
-
- if (RegistrationState.REGISTERED.equals(registrationState))
- {
- protocolProvider.getConnection().addPacketListener(this, this);
- }
- else if (RegistrationState.UNREGISTERED.equals(registrationState))
- {
- XMPPConnection connection = protocolProvider.getConnection();
-
- if (connection != null)
- connection.removePacketListener(this);
- }
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * Implements <tt>OperationSetVideoBridge</tt> for Jabber.
+ *
+ * @author Yana Stamcheva
+ * @author Lyubomir Marinov
+ */
+public class OperationSetVideoBridgeImpl
+ implements OperationSetVideoBridge,
+ PacketFilter,
+ PacketListener,
+ RegistrationStateChangeListener
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>OperationSetVideoBridgeImpl</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(OperationSetVideoBridgeImpl.class);
+
+ /**
+ * The <tt>ProtocolProviderService</tt> implementation which initialized
+ * this instance, owns it and is often referred to as its parent.
+ */
+ private final ProtocolProviderServiceJabberImpl protocolProvider;
+
+ /**
+ * Creates an instance of <tt>OperationSetVideoBridgeImpl</tt> by
+ * specifying the parent <tt>ProtocolProviderService</tt> announcing this
+ * operation set.
+ *
+ * @param protocolProvider the parent Jabber protocol provider
+ */
+ public OperationSetVideoBridgeImpl(
+ ProtocolProviderServiceJabberImpl protocolProvider)
+ {
+ this.protocolProvider = protocolProvider;
+ this.protocolProvider.addRegistrationStateChangeListener(this);
+ }
+
+ /**
+ * Implements {@link PacketFilter}. Determines whether this instance is
+ * interested in a specific {@link Packet}.
+ * <tt>OperationSetVideoBridgeImpl</tt> returns <tt>true</tt> if the
+ * specified <tt>packet</tt> is a {@link ColibriConferenceIQ}; otherwise,
+ * <tt>false</tt>.
+ *
+ * @param packet the <tt>Packet</tt> to be determined whether this instance
+ * is interested in it
+ * @return <tt>true</tt> if the specified <tt>packet</tt> is a
+ * <tt>ColibriConferenceIQ</tt>; otherwise, <tt>false</tt>
+ */
+ public boolean accept(Packet packet)
+ {
+ return (packet instanceof ColibriConferenceIQ);
+ }
+
+ /**
+ * Creates a conference call with the specified callees as call peers via a
+ * video bridge provided by the parent Jabber provider.
+ *
+ * @param callees the list of addresses that we should call
+ * @return the newly created conference call containing all CallPeers
+ * @throws OperationFailedException if establishing the conference call
+ * fails
+ * @throws OperationNotSupportedException if the provider does not have any
+ * conferencing features.
+ */
+ public Call createConfCall(String[] callees)
+ throws OperationFailedException,
+ OperationNotSupportedException
+ {
+ return
+ protocolProvider
+ .getOperationSet(OperationSetTelephonyConferencing.class)
+ .createConfCall(
+ callees,
+ new MediaAwareCallConference(true));
+ }
+
+ /**
+ * Invites the callee represented by the specified uri to an already
+ * existing call using a video bridge provided by the parent Jabber provider.
+ * The difference between this method and createConfCall is that
+ * inviteCalleeToCall allows a user to add new peers to an already
+ * established conference.
+ *
+ * @param uri the callee to invite to an existing conf call.
+ * @param call the call that we should invite the callee to.
+ * @return the CallPeer object corresponding to the callee represented by
+ * the specified uri.
+ * @throws OperationFailedException if inviting the specified callee to the
+ * specified call fails
+ * @throws OperationNotSupportedException if allowing additional callees to
+ * a pre-established call is not supported.
+ */
+ public CallPeer inviteCalleeToCall(String uri, Call call)
+ throws OperationFailedException,
+ OperationNotSupportedException
+ {
+ return
+ protocolProvider
+ .getOperationSet(OperationSetTelephonyConferencing.class)
+ .inviteCalleeToCall(uri, call);
+ }
+
+ /**
+ * Indicates if there's an active video bridge available at this moment. The
+ * Jabber provider may announce support for video bridge, but it should not
+ * be used for calling until it becomes actually active.
+ *
+ * @return <tt>true</tt> to indicate that there's currently an active
+ * available video bridge, <tt>false</tt> - otherwise
+ */
+ public boolean isActive()
+ {
+ String jitsiVideobridge = protocolProvider.getJitsiVideobridge();
+
+ return ((jitsiVideobridge != null) && (jitsiVideobridge.length() > 0));
+ }
+
+ /**
+ * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
+ * been received.
+ *
+ * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
+ * received
+ */
+ private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
+ {
+ /*
+ * The application is not a Jitsi Videobridge server, it is a client.
+ * Consequently, the specified ColibriConferenceIQ is sent to it in
+ * relation to the part of the application's functionality which makes
+ * requests to a Jitsi Videobridge server i.e. CallJabberImpl.
+ *
+ * Additionally, the method processColibriConferenceIQ is presently tasked
+ * with processing ColibriConferenceIQ requests only. They are SET IQs
+ * sent by the Jitsi Videobridge server to notify the application about
+ * updates in the states of (colibri) conferences organized by the
+ * application.
+ */
+ if (IQ.Type.SET.equals(conferenceIQ.getType())
+ && conferenceIQ.getID() != null)
+ {
+ OperationSetBasicTelephony<?> basicTelephony
+ = protocolProvider.getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ if (basicTelephony != null)
+ {
+ Iterator<? extends Call> i = basicTelephony.getActiveCalls();
+
+ while (i.hasNext())
+ {
+ Call call = i.next();
+
+ if (call instanceof CallJabberImpl)
+ {
+ CallJabberImpl callJabberImpl = (CallJabberImpl) call;
+ MediaAwareCallConference conference
+ = callJabberImpl.getConference();
+
+ if ((conference != null)
+ && conference.isJitsiVideobridge())
+ {
+ /*
+ * TODO We may want to disallow rogue CallJabberImpl
+ * instances which may throw an exception to prevent
+ * the conferenceIQ from reaching the CallJabberImpl
+ * instance which it was meant for.
+ */
+ if (callJabberImpl.processColibriConferenceIQ(
+ conferenceIQ))
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Implements {@link PacketListener}. Notifies this instance that a specific
+ * {@link Packet} (which this instance has already expressed interest into
+ * by returning <tt>true</tt> from {@link #accept(Packet)}) has been
+ * received.
+ *
+ * @param packet the <tt>Packet</tt> which has been received and which this
+ * instance is given a chance to process
+ */
+ public void processPacket(Packet packet)
+ {
+ /*
+ * As we do elsewhere, acknowledge the receipt of the Packet first and
+ * then go about our business with it.
+ */
+ IQ iq = (IQ) packet;
+
+ if (iq.getType() == IQ.Type.SET)
+ protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq));
+
+ /*
+ * Now that the acknowledging is out of the way, do go about our
+ * business with the Packet.
+ */
+ ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq;
+ boolean interrupted = false;
+
+ try
+ {
+ processColibriConferenceIQ(conferenceIQ);
+ }
+ catch (Throwable t)
+ {
+ logger.error(
+ "An error occurred during the processing of a "
+ + packet.getClass().getName() + " packet",
+ t);
+
+ if (t instanceof InterruptedException)
+ {
+ /*
+ * We cleared the interrupted state of the current Thread by
+ * catching the InterruptedException. However, we do not really
+ * care whether the current Thread has been interrupted - we
+ * caught the InterruptedException because we want to swallow
+ * any Throwable. Consequently, we should better restore the
+ * interrupted state.
+ */
+ interrupted = true;
+ }
+ else if (t instanceof ThreadDeath)
+ throw (ThreadDeath) t;
+ }
+ if (interrupted)
+ Thread.currentThread().interrupt();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Implements {@link RegistrationStateChangeListener}. Notifies this
+ * instance that there has been a change in the <tt>RegistrationState</tt>
+ * of {@link #protocolProvider}. Subscribes this instance to
+ * {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is
+ * registered and unsubscribes it as soon as <tt>protocolProvider</tt> is
+ * unregistered.
+ */
+ public void registrationStateChanged(RegistrationStateChangeEvent ev)
+ {
+ RegistrationState registrationState = ev.getNewState();
+
+ if (RegistrationState.REGISTERED.equals(registrationState))
+ {
+ protocolProvider.getConnection().addPacketListener(this, this);
+ }
+ else if (RegistrationState.UNREGISTERED.equals(registrationState))
+ {
+ Connection connection = protocolProvider.getConnection();
+
+ if (connection != null)
+ connection.removePacketListener(this);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java
index f38d0bc..b219d68 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java
@@ -248,7 +248,7 @@ public class OutgoingFileTransferJabberImpl
ThumbnailIQ thumbnailIQ = (ThumbnailIQ) packet;
String thumbnailIQCid = thumbnailIQ.getCid();
- XMPPConnection connection = protocolProvider.getConnection();
+ Connection connection = protocolProvider.getConnection();
if ((thumbnailIQCid != null)
&& thumbnailIQCid.equals(thumbnailElement.getCid()))
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java
index 4f9fc5f..86666f3 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java
@@ -21,6 +21,7 @@ import java.util.*;
import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.jabber.*;
import org.jivesoftware.smack.provider.*;
import org.jivesoftware.smack.util.*;
import org.osgi.framework.*;
@@ -46,7 +47,14 @@ public class ProtocolProviderFactoryJabberImpl
{
try
{
+
+ // Set the extension provider manager for classes that use
+ // it directly
ProviderManager.setInstance(new ProviderManagerExt());
+ // Set the Smack interop implementation for the classes that need
+ // to support Smackv4 interoperation
+ AbstractSmackInteroperabilityLayer.setImplementationClass(
+ SmackV3InteroperabilityLayer.class);
}
catch(Throwable t)
{
@@ -179,7 +187,7 @@ public class ProtocolProviderFactoryJabberImpl
ProtocolProviderServiceJabberImpl service =
new ProtocolProviderServiceJabberImpl();
- service.initialize(userID, accountID);
+ service.initialize(userID, (JabberAccountID) accountID);
return service;
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
index 2ea5c9c..47b9b75 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
@@ -32,6 +32,7 @@ import net.java.sip.communicator.impl.protocol.jabber.extensions.carbon.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jibri.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingleinfo.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*;
@@ -41,6 +42,7 @@ import net.java.sip.communicator.service.certificate.*;
import net.java.sip.communicator.service.dns.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.jabber.*;
import net.java.sip.communicator.service.protocol.jabberconstants.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;
@@ -152,6 +154,14 @@ public class ProtocolProviderServiceJabberImpl
public static final String URN_IETF_RFC_3264 = "urn:ietf:rfc:3264";
/**
+ * http://xmpp.org/extensions/xep-0092.html Software Version.
+ *
+ */
+ // Used in JVB
+ @SuppressWarnings("unused")
+ public static final String URN_XMPP_IQ_VERSION = "jabber:iq:version";
+
+ /**
* Jingle's Discovery Info URN for "XEP-0294: Jingle RTP Header Extensions
* Negotiation" support.
*/
@@ -211,7 +221,7 @@ public class ProtocolProviderServiceJabberImpl
/**
* Used to connect to a XMPP server.
*/
- private XMPPConnection connection;
+ private Connection connection;
/**
* The socket address of the XMPP server.
@@ -231,7 +241,7 @@ public class ProtocolProviderServiceJabberImpl
/**
* The identifier of the account that this provider represents.
*/
- private AccountID accountID = null;
+ private JabberAccountID accountID = null;
/**
* Used when we need to re-register
@@ -598,7 +608,7 @@ public class ProtocolProviderServiceJabberImpl
*/
public boolean isSignalingTransportSecure()
{
- return connection != null && connection.isUsingTLS();
+ return connection.isSecureConnection();
}
/**
@@ -613,7 +623,7 @@ public class ProtocolProviderServiceJabberImpl
if(connection != null && connection.isConnected())
{
// Transport using a secure connection.
- if(connection.isUsingTLS())
+ if(isSignalingTransportSecure())
{
return TransportProtocol.TLS;
}
@@ -1112,11 +1122,24 @@ public class ProtocolProviderServiceJabberImpl
JabberLoginStrategy loginStrategy)
throws XMPPException
{
- ConnectionConfiguration confConn = new ConnectionConfiguration(
- address.getAddress().getHostAddress(),
- address.getPort(),
- serviceName, proxy
- );
+ // BOSH or TCP ?
+ ConnectionConfiguration confConn;
+ String boshURL = accountID.getBoshUrl();
+ boolean isBosh = !org.jitsi.util.StringUtils.isNullOrEmpty(boshURL);
+
+ if (isBosh)
+ {
+ confConn = new BOSHConfiguration(serviceName);
+ ((BOSHConfiguration)confConn).setBoshUrl(boshURL);
+ }
+ else
+ {
+ confConn
+ = new ConnectionConfiguration(
+ address.getAddress().getHostAddress(),
+ address.getPort(),
+ serviceName, proxy);
+ }
// if we have OperationSetPersistentPresence skip sending initial
// presence while login is executed, the OperationSet will take care
@@ -1144,7 +1167,11 @@ public class ProtocolProviderServiceJabberImpl
disconnectAndCleanConnection();
}
- connection = new XMPPConnection(confConn);
+ connection
+ = isBosh
+ ? new XMPPBOSHConnection((BOSHConfiguration)confConn)
+ : new XMPPConnection(confConn);
+
this.address = address;
try
@@ -1194,13 +1221,16 @@ public class ProtocolProviderServiceJabberImpl
throw new XMPPException("Error creating custom trust manager", e);
}
- if(debugger == null)
+ // FIXME rework debugger to work with Connection if possible
+ if(debugger == null && connection instanceof XMPPConnection)
+ {
debugger = new SmackPacketDebugger();
- // sets the debugger
- debugger.setConnection(connection);
- connection.addPacketListener(debugger, null);
- connection.addPacketInterceptor(debugger, null);
+ // sets the debugger
+ debugger.setConnection((XMPPConnection) connection);
+ connection.addPacketListener(debugger, null);
+ connection.addPacketInterceptor(debugger, null);
+ }
connection.connect();
@@ -1247,9 +1277,10 @@ public class ProtocolProviderServiceJabberImpl
}
else
{
- if (connection.getSocket() instanceof SSLSocket)
+ final SSLSocket sslSocket = getSSLSocket();
+
+ if (sslSocket != null)
{
- final SSLSocket sslSocket = (SSLSocket) connection.getSocket();
StringBuilder buff = new StringBuilder();
buff.append("Chosen TLS protocol and algorithm:\n")
.append("Protocol: ").append(sslSocket.getSession()
@@ -1538,7 +1569,7 @@ public class ProtocolProviderServiceJabberImpl
* @see net.java.sip.communicator.service.protocol.AccountID
*/
protected void initialize(String screenname,
- AccountID accountID)
+ JabberAccountID accountID)
{
synchronized(initializationLock)
{
@@ -1732,6 +1763,12 @@ public class ProtocolProviderServiceJabberImpl
ColibriConferenceIQ.NAMESPACE,
new ColibriIQProvider());
+ providerManager.addIQProvider(
+ JibriIq.ELEMENT_NAME,
+ JibriIq.NAMESPACE,
+ new JibriIqProvider()
+ );
+
providerManager.addExtensionProvider(
ConferenceDescriptionPacketExtension.ELEMENT_NAME,
ConferenceDescriptionPacketExtension.NAMESPACE,
@@ -2025,11 +2062,98 @@ public class ProtocolProviderServiceJabberImpl
}
/**
- * Returns the <tt>XMPPConnection</tt>opened by this provider
- * @return a reference to the <tt>XMPPConnection</tt> last opened by this
+ * Validates the node part of a JID and returns an error message if
+ * applicable and a suggested correction.
+ *
+ * @param contactId the contact identifier to validate
+ * @param result Must be supplied as an empty a list. Implementors add
+ * items:
+ * <ol>
+ * <li>is the error message if applicable
+ * <li>a suggested correction. Index 1 is optional and can only
+ * be present if there was a validation failure.
+ * </ol>
+ * @return true if the contact id is valid, false otherwise
+ */
+ @Override
+ public boolean validateContactAddress(String contactId, List<String> result)
+ {
+ if (result == null)
+ {
+ throw new IllegalArgumentException("result must be an empty list");
+ }
+
+ result.clear();
+ try
+ {
+ contactId = contactId.trim();
+ if (contactId.length() == 0)
+ {
+ result.add(JabberActivator.getResources().getI18NString(
+ "impl.protocol.jabber.INVALID_ADDRESS", new String[]
+ { contactId }));
+ // no suggestion for an empty id
+ return false;
+ }
+
+ String user = contactId;
+ String remainder = "";
+ int at = contactId.indexOf('@');
+ if (at > -1)
+ {
+ user = contactId.substring(0, at);
+ remainder = contactId.substring(at);
+ }
+
+ // <conforming-char> ::= #x21 | [#x23-#x25] | [#x28-#x2E] |
+ // [#x30-#x39] | #x3B | #x3D | #x3F |
+ // [#x41-#x7E] | [#x80-#xD7FF] |
+ // [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ boolean valid = true;
+ String suggestion = "";
+ for (char c : user.toCharArray())
+ {
+ if (!(c == 0x21 || (c >= 0x23 && c <= 0x25)
+ || (c >= 0x28 && c <= 0x2e) || (c >= 0x30 && c <= 0x39)
+ || c == 0x3b || c == 0x3d || c == 0x3f
+ || (c >= 0x41 && c <= 0x7e) || (c >= 0x80 && c <= 0xd7ff)
+ || (c >= 0xe000 && c <= 0xfffd)))
+ {
+ valid = false;
+ }
+ else
+ {
+ suggestion += c;
+ }
+ }
+
+ if (!valid)
+ {
+ result.add(JabberActivator.getResources().getI18NString(
+ "impl.protocol.jabber.INVALID_ADDRESS", new String[]
+ { contactId }));
+ result.add(suggestion + remainder);
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ result.add(JabberActivator.getResources().getI18NString(
+ "impl.protocol.jabber.INVALID_ADDRESS", new String[]
+ { contactId }));
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the <tt>Connection</tt>opened by this provider
+ * @return a reference to the <tt>Connection</tt> last opened by this
* provider.
*/
- public XMPPConnection getConnection()
+ public Connection getConnection()
{
return connection;
}
@@ -2315,18 +2439,16 @@ public class ProtocolProviderServiceJabberImpl
*/
public boolean isFeatureListSupported(String jid, String... features)
{
- boolean isFeatureListSupported = true;
-
try
{
if(discoveryManager == null)
- return isFeatureListSupported;
+ return false;
DiscoverInfo featureInfo =
discoveryManager.discoverInfoNonBlocking(jid);
if(featureInfo == null)
- return isFeatureListSupported;
+ return false;
for (String feature : features)
{
@@ -2334,17 +2456,19 @@ public class ProtocolProviderServiceJabberImpl
{
// If one is not supported we return false and don't check
// the others.
- isFeatureListSupported = false;
- break;
+ return false;
}
}
+
+ return true;
}
catch (XMPPException e)
{
if (logger.isDebugEnabled())
logger.debug("Failed to retrive discovery info.", e);
}
- return isFeatureListSupported;
+
+ return false;
}
/**
@@ -2386,7 +2510,7 @@ public class ProtocolProviderServiceJabberImpl
*/
public String getFullJid(String bareJid)
{
- XMPPConnection connection = getConnection();
+ Connection connection = getConnection();
// when we are not connected there is no full jid
if (connection != null && connection.isConnected())
@@ -2590,12 +2714,21 @@ public class ProtocolProviderServiceJabberImpl
*/
public void startJingleNodesDiscovery()
{
+ if (!(connection instanceof XMPPConnection))
+ {
+ logger.warn(
+ "Jingle node discovery currently will work only with " +
+ "TCP XMPP connection");
+ return;
+ }
+
// Jingle Nodes Service Initialization
+ final XMPPConnection xmppConnection = (XMPPConnection) connection;
final JabberAccountIDImpl accID = (JabberAccountIDImpl)getAccountID();
- final SmackServiceNode service = new SmackServiceNode(connection,
- 60000);
+ final SmackServiceNode service
+ = new SmackServiceNode(xmppConnection, 60000);
// make sure SmackServiceNode will clean up when connection is closed
- connection.addConnectionListener(service);
+ xmppConnection.addConnectionListener(service);
for(JingleNodeDescriptor desc : accID.getJingleNodes())
{
@@ -2611,7 +2744,7 @@ public class ProtocolProviderServiceJabberImpl
new Thread(new JingleNodesServiceDiscovery(
service,
- connection,
+ xmppConnection,
accID,
jingleNodesSyncRoot))
.start();
@@ -2739,7 +2872,7 @@ public class ProtocolProviderServiceJabberImpl
*/
private void setTrafficClass()
{
- Socket s = connection.getSocket();
+ Socket s = getSocket();
if(s != null)
{
@@ -2774,7 +2907,7 @@ public class ProtocolProviderServiceJabberImpl
*/
public String getJitsiVideobridge()
{
- XMPPConnection connection = getConnection();
+ Connection connection = getConnection();
if (connection != null)
{
@@ -2865,21 +2998,23 @@ public class ProtocolProviderServiceJabberImpl
}
/**
+ * Obtains XMPP connection's socket.
+ * @return <tt>Socket</tt> instance used by the underlying XMPP connection
+ * or <tt>null</tt> if "non socket" type of transport is currently used.
+ */
+ private Socket getSocket()
+ {
+ return connection != null ? connection.getSocket() : null;
+ }
+
+ /**
* Return the SSL socket (if TLS used).
* @return The SSL socket or null if not used
*/
- public SSLSocket getSSLSocket()
+ SSLSocket getSSLSocket()
{
- final SSLSocket result;
- final Socket socket = connection.getSocket();
- if (socket instanceof SSLSocket)
- {
- result = (SSLSocket) socket;
- }
- else
- {
- result = null;
- }
- return result;
+ final Socket socket = getSocket();
+
+ return (socket instanceof SSLSocket) ? (SSLSocket) socket : null;
}
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java
index 0aa4fb6..4bd49a9 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,529 +15,529 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.net.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
-import net.java.sip.communicator.service.protocol.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * A {@link TransportManagerJabberImpl} implementation that would only gather a
- * single candidate pair (i.e. RTP and RTCP).
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Hristo Terezov
- */
-public class RawUdpTransportManager
- extends TransportManagerJabberImpl
-{
- /**
- * The list of <tt>ContentPacketExtension</tt>s which represents the local
- * counterpart of the negotiation between the local and the remote peers.
- */
- private List<ContentPacketExtension> local;
-
- /**
- * The collection of <tt>ContentPacketExtension</tt>s which represents the
- * remote counterpart of the negotiation between the local and the remote
- * peers.
- */
- private final List<Iterable<ContentPacketExtension>> remotes
- = new LinkedList<Iterable<ContentPacketExtension>>();
-
- /**
- * Creates a new instance of this transport manager, binding it to the
- * specified peer.
- *
- * @param callPeer the {@link CallPeer} whose traffic we will be taking
- * care of.
- */
- public RawUdpTransportManager(CallPeerJabberImpl callPeer)
- {
- super(callPeer);
- }
-
- /**
- * {@inheritDoc}
- */
- protected PacketExtension createTransport(String media)
- throws OperationFailedException
- {
- MediaType mediaType = MediaType.parseString(media);
-
- return createTransport(mediaType, getStreamConnector(mediaType));
- }
-
- /**
- * Creates a raw UDP transport element according to a specific
- * <tt>StreamConnector</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * uses the specified <tt>connector</tt> or <tt>channel</tt>
- * @param connector the <tt>StreamConnector</tt> to be described within the
- * transport element
- * @return a {@link RawUdpTransportPacketExtension} containing the RTP and
- * RTCP candidates of the specified <tt>connector</tt>
- */
- private RawUdpTransportPacketExtension createTransport(
- MediaType mediaType,
- StreamConnector connector)
- {
- RawUdpTransportPacketExtension ourTransport
- = new RawUdpTransportPacketExtension();
- int generation = getCurrentGeneration();
-
- // create and add candidates that correspond to the stream connector
- // RTP
- CandidatePacketExtension rtpCand = new CandidatePacketExtension();
-
- rtpCand.setComponent(CandidatePacketExtension.RTP_COMPONENT_ID);
- rtpCand.setGeneration(generation);
- rtpCand.setID(getNextID());
- rtpCand.setType(CandidateType.host);
-
- DatagramSocket dataSocket = connector.getDataSocket();
-
- rtpCand.setIP(dataSocket.getLocalAddress().getHostAddress());
- rtpCand.setPort(dataSocket.getLocalPort());
-
- ourTransport.addCandidate(rtpCand);
-
- // RTCP
- CandidatePacketExtension rtcpCand = new CandidatePacketExtension();
-
- rtcpCand.setComponent(CandidatePacketExtension.RTCP_COMPONENT_ID);
- rtcpCand.setGeneration(generation);
- rtcpCand.setID(getNextID());
- rtcpCand.setType(CandidateType.host);
-
- DatagramSocket controlSocket = connector.getControlSocket();
-
- rtcpCand.setIP(controlSocket.getLocalAddress().getHostAddress());
- rtcpCand.setPort(controlSocket.getLocalPort());
-
- ourTransport.addCandidate(rtcpCand);
-
- return ourTransport;
- }
-
- /**
- * {@inheritDoc}
- */
- protected PacketExtension createTransportPacketExtension()
- {
- return new RawUdpTransportPacketExtension();
- }
-
- /**
- * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}.
- * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
- * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * is to have its <tt>target</tt> set to the returned
- * <tt>MediaStreamTarget</tt>
- * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
- * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
- * @see TransportManagerJabberImpl#getStreamTarget(MediaType)
- */
- @Override
- public MediaStreamTarget getStreamTarget(MediaType mediaType)
- {
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, true /* local */);
- MediaStreamTarget streamTarget = null;
-
- if (channel == null)
- {
- String media = mediaType.toString();
-
- for (Iterable<ContentPacketExtension> remote : remotes)
- {
- for (ContentPacketExtension content : remote)
- {
- RtpDescriptionPacketExtension rtpDescription
- = content.getFirstChildOfType(
- RtpDescriptionPacketExtension.class);
-
- if (media.equals(rtpDescription.getMedia()))
- {
- streamTarget
- = JingleUtils.extractDefaultTarget(content);
- break;
- }
- }
- }
- }
- else
- {
- IceUdpTransportPacketExtension transport = channel.getTransport();
-
- if (transport != null)
- streamTarget = JingleUtils.extractDefaultTarget(transport);
- if (streamTarget == null)
- {
- /*
- * For the purposes of compatibility with legacy Jitsi
- * Videobridge, support the channel attributes host, rtpPort and
- * rtcpPort.
- */
- @SuppressWarnings("deprecation")
- String host = channel.getHost();
-
- if (host != null)
- {
- @SuppressWarnings("deprecation")
- int rtpPort = channel.getRTPPort();
- @SuppressWarnings("deprecation")
- int rtcpPort = channel.getRTCPPort();
-
- streamTarget
- = new MediaStreamTarget(
- new InetSocketAddress(host, rtpPort),
- new InetSocketAddress(host, rtcpPort));
- }
- }
- }
- return streamTarget;
- }
-
- /**
- * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the
- * XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>.
- *
- * @return the XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>
- * @see TransportManagerJabberImpl#getXmlNamespace()
- */
- @Override
- public String getXmlNamespace()
- {
- return ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0;
- }
-
- /**
- * Removes a content with a specific name from the transport-related part of
- * the session represented by this <tt>TransportManagerJabberImpl</tt> which
- * may have been reported through previous calls to the
- * <tt>startCandidateHarvest</tt> and
- * <tt>startConnectivityEstablishment</tt> methods.
- *
- * @param name the name of the content to be removed from the
- * transport-related part of the session represented by this
- * <tt>TransportManagerJabberImpl</tt>
- * @see TransportManagerJabberImpl#removeContent(String)
- */
- @Override
- public void removeContent(String name)
- {
- if (local != null)
- removeContent(local, name);
-
- removeRemoteContent(name);
- }
-
- /**
- * Removes a content with a specific name from the remote counterpart of the
- * negotiation between the local and the remote peers.
- *
- * @param name the name of the content to be removed from the remote
- * counterpart of the negotiation between the local and the remote peers
- */
- private void removeRemoteContent(String name)
- {
- for (Iterator<Iterable<ContentPacketExtension>> remoteIter
- = remotes.iterator();
- remoteIter.hasNext();)
- {
- Iterable<ContentPacketExtension> remote = remoteIter.next();
-
- /*
- * Once the remote content is removed, make sure that we are not
- * retaining sets which do not have any contents.
- */
- if ((removeContent(remote, name) != null)
- && !remote.iterator().hasNext())
- {
- remoteIter.remove();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- protected PacketExtension startCandidateHarvest(
- ContentPacketExtension theirContent,
- ContentPacketExtension ourContent,
- TransportInfoSender transportInfoSender,
- String media)
- throws OperationFailedException
- {
- return createTransportForStartCandidateHarvest(media);
- }
-
- /**
- * Starts transport candidate harvest. This method should complete rapidly
- * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
- * are necessary, they should be executed in a separate thread. Candidate
- * harvest would then need to be concluded in the
- * {@link #wrapupCandidateHarvest()} method which would be called once we
- * absolutely need the candidates.
- *
- * @param theirOffer a media description offer that we've received from the
- * remote party and that we should use in case we need to know what
- * transports our peer is using.
- * @param ourAnswer the content descriptions that we should be adding our
- * transport lists to (although not necessarily in this very instance).
- * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
- * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
- * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
- * <tt>TransportManagerJabberImpl</tt> wishes to utilize
- * <tt>transport-info</tt>. Local candidate addresses sent by this
- * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
- * expected to not be included in the result of
- * {@link #wrapupCandidateHarvest()}.
- *
- * @throws OperationFailedException if we fail to allocate a port number.
- * @see TransportManagerJabberImpl#startCandidateHarvest(List, List,
- * TransportInfoSender)
- */
- @Override
- public void startCandidateHarvest(
- List<ContentPacketExtension> theirOffer,
- List<ContentPacketExtension> ourAnswer,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- this.local = ourAnswer;
-
- super.startCandidateHarvest(theirOffer, ourAnswer, transportInfoSender);
- }
-
- /**
- * Overrides the super implementation in order to remember the remote
- * counterpart of the negotiation between the local and the remote peer for
- * subsequent calls to {@link #getStreamTarget(MediaType)}.
- *
- * @param remote the collection of <tt>ContentPacketExtension</tt>s which
- * represents the remote counterpart of the negotiation between the local
- * and the remote peer
- * @return <tt>true</tt> because <tt>RawUdpTransportManager</tt> does not
- * perform connectivity checks
- * @see TransportManagerJabberImpl#startConnectivityEstablishment(Iterable)
- */
- @Override
- public boolean startConnectivityEstablishment(
- Iterable<ContentPacketExtension> remote)
- {
- if ((remote != null) && !remotes.contains(remote))
- {
- /*
- * The state of the session in Jingle is maintained by each peer and
- * is modified by content-add and content-remove. The remotes field
- * of this RawUdpTransportManager represents the state of the
- * session with respect to the remote peer. When the remote peer
- * tells us about a specific set of contents, make sure that it is
- * the only record we will have with respect to the specified set of
- * contents.
- */
- for (ContentPacketExtension content : remote)
- removeRemoteContent(content.getName());
-
- remotes.add(remote);
- }
-
- return super.startConnectivityEstablishment(remote);
- }
-
- /**
- * Simply returns the list of local candidates that we gathered during the
- * harvest. This is a raw UDP transport manager so there's no real wrapping
- * up to do.
- *
- * @return the list of local candidates that we gathered during the harvest
- * @see TransportManagerJabberImpl#wrapupCandidateHarvest()
- */
- @Override
- public List<ContentPacketExtension> wrapupCandidateHarvest()
- {
- return local;
- }
-
- /**
- * Returns the extended type of the candidate selected if this transport
- * manager is using ICE.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return The extended type of the candidate selected if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- @Override
- public String getICECandidateExtendedType(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the current state of ICE processing.
- *
- * @return the current state of ICE processing.
- */
- @Override
- public String getICEState()
- {
- return null;
- }
-
- /**
- * Returns the ICE local host address.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local host address if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- @Override
- public InetSocketAddress getICELocalHostAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE remote host address.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote host address if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- @Override
- public InetSocketAddress getICERemoteHostAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE local reflexive address (server or peer reflexive).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local reflexive address. May be null if this transport
- * manager is not using ICE or if there is no reflexive address for the
- * local candidate used.
- */
- @Override
- public InetSocketAddress getICELocalReflexiveAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE remote reflexive address (server or peer reflexive).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote reflexive address. May be null if this transport
- * manager is not using ICE or if there is no reflexive address for the
- * remote candidate used.
- */
- @Override
- public InetSocketAddress getICERemoteReflexiveAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE local relayed address (server or peer relayed).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local relayed address. May be null if this transport
- * manager is not using ICE or if there is no relayed address for the
- * local candidate used.
- */
- @Override
- public InetSocketAddress getICELocalRelayedAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the ICE remote relayed address (server or peer relayed).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote relayed address. May be null if this transport
- * manager is not using ICE or if there is no relayed address for the
- * remote candidate used.
- */
- @Override
- public InetSocketAddress getICERemoteRelayedAddress(String streamName)
- {
- return null;
- }
-
- /**
- * Returns the total harvesting time (in ms) for all harvesters.
- *
- * @return The total harvesting time (in ms) for all the harvesters. 0 if
- * the ICE agent is null, or if the agent has nevers harvested.
- */
- @Override
- public long getTotalHarvestingTime()
- {
- return 0;
- }
-
- /**
- * Returns the harvesting time (in ms) for the harvester given in parameter.
- *
- * @param harvesterName The class name if the harvester.
- *
- * @return The harvesting time (in ms) for the harvester given in parameter.
- * 0 if this harvester does not exists, if the ICE agent is null, or if the
- * agent has never harvested with this harvester.
- */
- @Override
- public long getHarvestingTime(String harvesterName)
- {
- return 0;
- }
-
- /**
- * Returns the number of harvesting for this agent.
- *
- * @return The number of harvesting for this agent.
- */
- @Override
- public int getNbHarvesting()
- {
- return 0;
- }
-
- /**
- * Returns the number of harvesting time for the harvester given in
- * parameter.
- *
- * @param harvesterName The class name if the harvester.
- *
- * @return The number of harvesting time for the harvester given in
- * parameter.
- */
- @Override
- public int getNbHarvesting(String harvesterName)
- {
- return 0;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.net.*;
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
+import net.java.sip.communicator.service.protocol.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A {@link TransportManagerJabberImpl} implementation that would only gather a
+ * single candidate pair (i.e. RTP and RTCP).
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Hristo Terezov
+ */
+public class RawUdpTransportManager
+ extends TransportManagerJabberImpl
+{
+ /**
+ * The list of <tt>ContentPacketExtension</tt>s which represents the local
+ * counterpart of the negotiation between the local and the remote peers.
+ */
+ private List<ContentPacketExtension> local;
+
+ /**
+ * The collection of <tt>ContentPacketExtension</tt>s which represents the
+ * remote counterpart of the negotiation between the local and the remote
+ * peers.
+ */
+ private final List<Iterable<ContentPacketExtension>> remotes
+ = new LinkedList<Iterable<ContentPacketExtension>>();
+
+ /**
+ * Creates a new instance of this transport manager, binding it to the
+ * specified peer.
+ *
+ * @param callPeer the {@link CallPeer} whose traffic we will be taking
+ * care of.
+ */
+ public RawUdpTransportManager(CallPeerJabberImpl callPeer)
+ {
+ super(callPeer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected PacketExtension createTransport(String media)
+ throws OperationFailedException
+ {
+ MediaType mediaType = MediaType.parseString(media);
+
+ return createTransport(mediaType, getStreamConnector(mediaType));
+ }
+
+ /**
+ * Creates a raw UDP transport element according to a specific
+ * <tt>StreamConnector</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * uses the specified <tt>connector</tt> or <tt>channel</tt>
+ * @param connector the <tt>StreamConnector</tt> to be described within the
+ * transport element
+ * @return a {@link RawUdpTransportPacketExtension} containing the RTP and
+ * RTCP candidates of the specified <tt>connector</tt>
+ */
+ private RawUdpTransportPacketExtension createTransport(
+ MediaType mediaType,
+ StreamConnector connector)
+ {
+ RawUdpTransportPacketExtension ourTransport
+ = new RawUdpTransportPacketExtension();
+ int generation = getCurrentGeneration();
+
+ // create and add candidates that correspond to the stream connector
+ // RTP
+ CandidatePacketExtension rtpCand = new CandidatePacketExtension();
+
+ rtpCand.setComponent(CandidatePacketExtension.RTP_COMPONENT_ID);
+ rtpCand.setGeneration(generation);
+ rtpCand.setID(getNextID());
+ rtpCand.setType(CandidateType.host);
+
+ DatagramSocket dataSocket = connector.getDataSocket();
+
+ rtpCand.setIP(dataSocket.getLocalAddress().getHostAddress());
+ rtpCand.setPort(dataSocket.getLocalPort());
+
+ ourTransport.addCandidate(rtpCand);
+
+ // RTCP
+ CandidatePacketExtension rtcpCand = new CandidatePacketExtension();
+
+ rtcpCand.setComponent(CandidatePacketExtension.RTCP_COMPONENT_ID);
+ rtcpCand.setGeneration(generation);
+ rtcpCand.setID(getNextID());
+ rtcpCand.setType(CandidateType.host);
+
+ DatagramSocket controlSocket = connector.getControlSocket();
+
+ rtcpCand.setIP(controlSocket.getLocalAddress().getHostAddress());
+ rtcpCand.setPort(controlSocket.getLocalPort());
+
+ ourTransport.addCandidate(rtcpCand);
+
+ return ourTransport;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected PacketExtension createTransportPacketExtension()
+ {
+ return new RawUdpTransportPacketExtension();
+ }
+
+ /**
+ * Implements {@link TransportManagerJabberImpl#getStreamTarget(MediaType)}.
+ * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
+ * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * is to have its <tt>target</tt> set to the returned
+ * <tt>MediaStreamTarget</tt>
+ * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
+ * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
+ * @see TransportManagerJabberImpl#getStreamTarget(MediaType)
+ */
+ @Override
+ public MediaStreamTarget getStreamTarget(MediaType mediaType)
+ {
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, true /* local */);
+ MediaStreamTarget streamTarget = null;
+
+ if (channel == null)
+ {
+ String media = mediaType.toString();
+
+ for (Iterable<ContentPacketExtension> remote : remotes)
+ {
+ for (ContentPacketExtension content : remote)
+ {
+ RtpDescriptionPacketExtension rtpDescription
+ = content.getFirstChildOfType(
+ RtpDescriptionPacketExtension.class);
+
+ if (media.equals(rtpDescription.getMedia()))
+ {
+ streamTarget
+ = JingleUtils.extractDefaultTarget(content);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ IceUdpTransportPacketExtension transport = channel.getTransport();
+
+ if (transport != null)
+ streamTarget = JingleUtils.extractDefaultTarget(transport);
+ if (streamTarget == null)
+ {
+ /*
+ * For the purposes of compatibility with legacy Jitsi
+ * Videobridge, support the channel attributes host, rtpPort and
+ * rtcpPort.
+ */
+ @SuppressWarnings("deprecation")
+ String host = channel.getHost();
+
+ if (host != null)
+ {
+ @SuppressWarnings("deprecation")
+ int rtpPort = channel.getRTPPort();
+ @SuppressWarnings("deprecation")
+ int rtcpPort = channel.getRTCPPort();
+
+ streamTarget
+ = new MediaStreamTarget(
+ new InetSocketAddress(host, rtpPort),
+ new InetSocketAddress(host, rtcpPort));
+ }
+ }
+ }
+ return streamTarget;
+ }
+
+ /**
+ * Implements {@link TransportManagerJabberImpl#getXmlNamespace()}. Gets the
+ * XML namespace of the Jingle transport implemented by this
+ * <tt>TransportManagerJabberImpl</tt>.
+ *
+ * @return the XML namespace of the Jingle transport implemented by this
+ * <tt>TransportManagerJabberImpl</tt>
+ * @see TransportManagerJabberImpl#getXmlNamespace()
+ */
+ @Override
+ public String getXmlNamespace()
+ {
+ return ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0;
+ }
+
+ /**
+ * Removes a content with a specific name from the transport-related part of
+ * the session represented by this <tt>TransportManagerJabberImpl</tt> which
+ * may have been reported through previous calls to the
+ * <tt>startCandidateHarvest</tt> and
+ * <tt>startConnectivityEstablishment</tt> methods.
+ *
+ * @param name the name of the content to be removed from the
+ * transport-related part of the session represented by this
+ * <tt>TransportManagerJabberImpl</tt>
+ * @see TransportManagerJabberImpl#removeContent(String)
+ */
+ @Override
+ public void removeContent(String name)
+ {
+ if (local != null)
+ removeContent(local, name);
+
+ removeRemoteContent(name);
+ }
+
+ /**
+ * Removes a content with a specific name from the remote counterpart of the
+ * negotiation between the local and the remote peers.
+ *
+ * @param name the name of the content to be removed from the remote
+ * counterpart of the negotiation between the local and the remote peers
+ */
+ private void removeRemoteContent(String name)
+ {
+ for (Iterator<Iterable<ContentPacketExtension>> remoteIter
+ = remotes.iterator();
+ remoteIter.hasNext();)
+ {
+ Iterable<ContentPacketExtension> remote = remoteIter.next();
+
+ /*
+ * Once the remote content is removed, make sure that we are not
+ * retaining sets which do not have any contents.
+ */
+ if ((removeContent(remote, name) != null)
+ && !remote.iterator().hasNext())
+ {
+ remoteIter.remove();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected PacketExtension startCandidateHarvest(
+ ContentPacketExtension theirContent,
+ ContentPacketExtension ourContent,
+ TransportInfoSender transportInfoSender,
+ String media)
+ throws OperationFailedException
+ {
+ return createTransportForStartCandidateHarvest(media);
+ }
+
+ /**
+ * Starts transport candidate harvest. This method should complete rapidly
+ * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
+ * are necessary, they should be executed in a separate thread. Candidate
+ * harvest would then need to be concluded in the
+ * {@link #wrapupCandidateHarvest()} method which would be called once we
+ * absolutely need the candidates.
+ *
+ * @param theirOffer a media description offer that we've received from the
+ * remote party and that we should use in case we need to know what
+ * transports our peer is using.
+ * @param ourAnswer the content descriptions that we should be adding our
+ * transport lists to (although not necessarily in this very instance).
+ * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
+ * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
+ * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
+ * <tt>TransportManagerJabberImpl</tt> wishes to utilize
+ * <tt>transport-info</tt>. Local candidate addresses sent by this
+ * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
+ * expected to not be included in the result of
+ * {@link #wrapupCandidateHarvest()}.
+ *
+ * @throws OperationFailedException if we fail to allocate a port number.
+ * @see TransportManagerJabberImpl#startCandidateHarvest(List, List,
+ * TransportInfoSender)
+ */
+ @Override
+ public void startCandidateHarvest(
+ List<ContentPacketExtension> theirOffer,
+ List<ContentPacketExtension> ourAnswer,
+ TransportInfoSender transportInfoSender)
+ throws OperationFailedException
+ {
+ this.local = ourAnswer;
+
+ super.startCandidateHarvest(theirOffer, ourAnswer, transportInfoSender);
+ }
+
+ /**
+ * Overrides the super implementation in order to remember the remote
+ * counterpart of the negotiation between the local and the remote peer for
+ * subsequent calls to {@link #getStreamTarget(MediaType)}.
+ *
+ * @param remote the collection of <tt>ContentPacketExtension</tt>s which
+ * represents the remote counterpart of the negotiation between the local
+ * and the remote peer
+ * @return <tt>true</tt> because <tt>RawUdpTransportManager</tt> does not
+ * perform connectivity checks
+ * @see TransportManagerJabberImpl#startConnectivityEstablishment(Iterable)
+ */
+ @Override
+ public boolean startConnectivityEstablishment(
+ Iterable<ContentPacketExtension> remote)
+ {
+ if ((remote != null) && !remotes.contains(remote))
+ {
+ /*
+ * The state of the session in Jingle is maintained by each peer and
+ * is modified by content-add and content-remove. The remotes field
+ * of this RawUdpTransportManager represents the state of the
+ * session with respect to the remote peer. When the remote peer
+ * tells us about a specific set of contents, make sure that it is
+ * the only record we will have with respect to the specified set of
+ * contents.
+ */
+ for (ContentPacketExtension content : remote)
+ removeRemoteContent(content.getName());
+
+ remotes.add(remote);
+ }
+
+ return super.startConnectivityEstablishment(remote);
+ }
+
+ /**
+ * Simply returns the list of local candidates that we gathered during the
+ * harvest. This is a raw UDP transport manager so there's no real wrapping
+ * up to do.
+ *
+ * @return the list of local candidates that we gathered during the harvest
+ * @see TransportManagerJabberImpl#wrapupCandidateHarvest()
+ */
+ @Override
+ public List<ContentPacketExtension> wrapupCandidateHarvest()
+ {
+ return local;
+ }
+
+ /**
+ * Returns the extended type of the candidate selected if this transport
+ * manager is using ICE.
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return The extended type of the candidate selected if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ @Override
+ public String getICECandidateExtendedType(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the current state of ICE processing.
+ *
+ * @return the current state of ICE processing.
+ */
+ @Override
+ public String getICEState()
+ {
+ return null;
+ }
+
+ /**
+ * Returns the ICE local host address.
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE local host address if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ @Override
+ public InetSocketAddress getICELocalHostAddress(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the ICE remote host address.
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE remote host address if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ @Override
+ public InetSocketAddress getICERemoteHostAddress(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the ICE local reflexive address (server or peer reflexive).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE local reflexive address. May be null if this transport
+ * manager is not using ICE or if there is no reflexive address for the
+ * local candidate used.
+ */
+ @Override
+ public InetSocketAddress getICELocalReflexiveAddress(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the ICE remote reflexive address (server or peer reflexive).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE remote reflexive address. May be null if this transport
+ * manager is not using ICE or if there is no reflexive address for the
+ * remote candidate used.
+ */
+ @Override
+ public InetSocketAddress getICERemoteReflexiveAddress(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the ICE local relayed address (server or peer relayed).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE local relayed address. May be null if this transport
+ * manager is not using ICE or if there is no relayed address for the
+ * local candidate used.
+ */
+ @Override
+ public InetSocketAddress getICELocalRelayedAddress(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the ICE remote relayed address (server or peer relayed).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE remote relayed address. May be null if this transport
+ * manager is not using ICE or if there is no relayed address for the
+ * remote candidate used.
+ */
+ @Override
+ public InetSocketAddress getICERemoteRelayedAddress(String streamName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the total harvesting time (in ms) for all harvesters.
+ *
+ * @return The total harvesting time (in ms) for all the harvesters. 0 if
+ * the ICE agent is null, or if the agent has nevers harvested.
+ */
+ @Override
+ public long getTotalHarvestingTime()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns the harvesting time (in ms) for the harvester given in parameter.
+ *
+ * @param harvesterName The class name if the harvester.
+ *
+ * @return The harvesting time (in ms) for the harvester given in parameter.
+ * 0 if this harvester does not exists, if the ICE agent is null, or if the
+ * agent has never harvested with this harvester.
+ */
+ @Override
+ public long getHarvestingTime(String harvesterName)
+ {
+ return 0;
+ }
+
+ /**
+ * Returns the number of harvesting for this agent.
+ *
+ * @return The number of harvesting for this agent.
+ */
+ @Override
+ public int getNbHarvesting()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns the number of harvesting time for the harvester given in
+ * parameter.
+ *
+ * @param harvesterName The class name if the harvester.
+ *
+ * @return The number of harvesting time for the harvester given in
+ * parameter.
+ */
+ @Override
+ public int getNbHarvesting(String harvesterName)
+ {
+ return 0;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java
index 9fdbea5..de2ce3e 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java
@@ -27,6 +27,7 @@ import net.java.sip.communicator.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.*;
import org.jivesoftware.smackx.*;
import org.jivesoftware.smackx.packet.*;
@@ -79,9 +80,9 @@ public class ScServiceDiscoveryManager
private final ProtocolProviderService parentProvider;
/**
- * The {@link XMPPConnection} that this manager is responsible for.
+ * The {@link Connection} that this manager is responsible for.
*/
- private final XMPPConnection connection;
+ private final Connection connection;
/**
* A local copy that we keep in sync with {@link ServiceDiscoveryManager}'s
@@ -129,7 +130,7 @@ public class ScServiceDiscoveryManager
*/
public ScServiceDiscoveryManager(
ProtocolProviderService parentProvider,
- XMPPConnection connection,
+ Connection connection,
String[] featuresToRemove,
String[] featuresToAdd,
boolean cacheNonCaps)
@@ -791,7 +792,11 @@ public class ScServiceDiscoveryManager
// fire event
if(fireEvent && capabilitiesOpSet != null)
{
- capabilitiesOpSet.fireContactCapabilitiesChanged(entityID);
+ capabilitiesOpSet.fireContactCapabilitiesChanged(
+ entityID,
+ capsManager.getFullJidsByBareJid(
+ StringUtils.parseBareAddress(entityID))
+ );
}
}
catch(XMPPException ex)
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java b/src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java
new file mode 100644
index 0000000..d5af26f
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/SmackV3InteroperabilityLayer.java
@@ -0,0 +1,91 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import net.java.sip.communicator.service.protocol.jabber.*;
+import org.jivesoftware.smack.provider.*;
+
+/**
+ * Smack v3 interoperation layer
+ *
+ * @author Maksym Kulish
+ */
+public class SmackV3InteroperabilityLayer
+ extends AbstractSmackInteroperabilityLayer
+{
+
+ /**
+ * A SmackV3 ProviderManager instance
+ */
+ private ProviderManager providerManager = ProviderManager.getInstance();
+
+ /**
+ * A default constructor
+ */
+ public SmackV3InteroperabilityLayer() {}
+
+ /**
+ * Add <tt>PacketExtensionProvider</tt> to the list of known
+ * providers
+ *
+ * @param elementName The element name where the matching is happening
+ * @param namespace The XML namespace used in that element
+ * @param provider <tt>PacketExtensionProvider</tt> implementation to be
+ * used
+ */
+ @Override
+ public void addExtensionProvider(
+ String elementName, String namespace, Object provider)
+ {
+ providerManager.addExtensionProvider(elementName, namespace, provider);
+ }
+
+ /**
+ * Add <tt>IQProvider</tt> to the list of known
+ * providers
+ *
+ * @param elementName The element name where the matching is happening
+ * @param namespace The XML namespace used in that element
+ * @param provider <tt>IQProvider</tt> implementation to be
+ * used
+ */
+ @Override
+ public void addIQProvider(
+ String elementName, String namespace, Object provider)
+ {
+ providerManager.addIQProvider(elementName, namespace, provider);
+ }
+
+ /**
+ * Get the <tt>PacketExtensionProvider</tt> for given element name and XML
+ * namespace
+ *
+ * @param elementName The element name where the matching is happening
+ * @param namespace The XML namespace used in that element
+ * @return <tt>PacketExtensionProvider</tt> implementation to be
+ * used
+ */
+ @Override
+ public PacketExtensionProvider getExtensionProvider(
+ String elementName, String namespace)
+ {
+ return (PacketExtensionProvider)providerManager
+ .getExtensionProvider(elementName, namespace);
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java
index f7f47c6..41e8c05 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerJabberImpl.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,963 +15,963 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.net.*;
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * <tt>TransportManager</tt>s gather local candidates for incoming and outgoing
- * calls. Their work starts by calling a start method which, using the remote
- * peer's session description, would start the harvest. Calling a second wrapup
- * method would deliver the candidate harvest, possibly after blocking if it has
- * not yet completed.
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- */
-public abstract class TransportManagerJabberImpl
- extends TransportManager<CallPeerJabberImpl>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>TransportManagerJabberImpl</tt> class
- * and its instances to print debug messages.
- */
- private static final Logger logger
- = Logger.getLogger(TransportManagerJabberImpl.class);
-
- /**
- * The ID that we will be assigning to our next candidate. We use
- * <tt>int</tt>s for interoperability reasons (Emil: I believe that GTalk
- * uses <tt>int</tt>s. If that turns out not to be the case we can stop
- * using <tt>int</tt>s here if that's an issue).
- */
- private static int nextID = 1;
-
- /**
- * The information pertaining to the Jisti Videobridge conference which the
- * local peer represented by this instance is a focus of. It gives a view of
- * the whole Jitsi Videobridge conference managed by the associated
- * <tt>CallJabberImpl</tt> which provides information specific to this
- * <tt>TransportManager</tt> only.
- */
- private ColibriConferenceIQ colibri;
-
- /**
- * The generation of the candidates we are currently generating
- */
- private int currentGeneration = 0;
-
- /**
- * The indicator which determines whether this <tt>TransportManager</tt>
- * instance is responsible to establish the connectivity with the associated
- * Jitsi Videobridge (in case it is being employed at all).
- */
- boolean isEstablishingConnectivityWithJitsiVideobridge = false;
-
- /**
- * The indicator which determines whether this <tt>TransportManager</tt>
- * instance is yet to start establishing the connectivity with the
- * associated Jitsi Videobridge (in case it is being employed at all).
- */
- boolean startConnectivityEstablishmentWithJitsiVideobridge = false;
-
- /**
- * Creates a new instance of this transport manager, binding it to the
- * specified peer.
- *
- * @param callPeer the {@link CallPeer} whose traffic we will be taking
- * care of.
- */
- protected TransportManagerJabberImpl(CallPeerJabberImpl callPeer)
- {
- super(callPeer);
- }
-
- /**
- * Returns the <tt>InetAddress</tt> that is most likely to be to be used
- * as a next hop when contacting the specified <tt>destination</tt>. This is
- * an utility method that is used whenever we have to choose one of our
- * local addresses to put in the Via, Contact or (in the case of no
- * registrar accounts) From headers.
- *
- * @param peer the CallPeer that we would contact.
- *
- * @return the <tt>InetAddress</tt> that is most likely to be to be used
- * as a next hop when contacting the specified <tt>destination</tt>.
- *
- * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
- * host/IP/FQDN
- */
- @Override
- protected InetAddress getIntendedDestination(CallPeerJabberImpl peer)
- {
- return peer.getProtocolProvider().getNextHop();
- }
-
- /**
- * Returns the ID that we will be assigning to the next candidate we create.
- *
- * @return the next ID to use with a candidate.
- */
- protected String getNextID()
- {
- int nextID;
-
- synchronized (TransportManagerJabberImpl.class)
- {
- nextID = TransportManagerJabberImpl.nextID++;
- }
- return Integer.toString(nextID);
- }
-
- /**
- * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
- * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * is to have its <tt>target</tt> set to the returned
- * <tt>MediaStreamTarget</tt>
- * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
- * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
- */
- public abstract MediaStreamTarget getStreamTarget(MediaType mediaType);
-
- /**
- * Gets the XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>.
- *
- * @return the XML namespace of the Jingle transport implemented by this
- * <tt>TransportManagerJabberImpl</tt>
- */
- public abstract String getXmlNamespace();
-
- /**
- * Returns the generation that our current candidates belong to.
- *
- * @return the generation that we should assign to candidates that we are
- * currently advertising.
- */
- protected int getCurrentGeneration()
- {
- return currentGeneration;
- }
-
- /**
- * Increments the generation that we are assigning candidates.
- */
- protected void incrementGeneration()
- {
- currentGeneration++;
- }
-
- /**
- * Sends transport-related information received from the remote peer to the
- * associated Jiitsi Videobridge in order to update the (remote)
- * <tt>ColibriConferenceIQ.Channel</tt> associated with this
- * <tt>TransportManager</tt> instance.
- *
- * @param map a <tt>Map</tt> of media-IceUdpTransportPacketExtension pairs
- * which represents the transport-related information which has been
- * received from the remote peer and which is to be sent to the associated
- * Jitsi Videobridge
- */
- protected void sendTransportInfoToJitsiVideobridge(
- Map<String,IceUdpTransportPacketExtension> map)
- {
- CallPeerJabberImpl peer = getCallPeer();
- boolean initiator = !peer.isInitiator();
- ColibriConferenceIQ conferenceRequest = null;
-
- for (Map.Entry<String,IceUdpTransportPacketExtension> e
- : map.entrySet())
- {
- String media = e.getKey();
- MediaType mediaType = MediaType.parseString(media);
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, false /* remote */);
-
- if (channel != null)
- {
- IceUdpTransportPacketExtension transport;
-
- try
- {
- transport = cloneTransportAndCandidates(e.getValue());
- }
- catch (OperationFailedException ofe)
- {
- transport = null;
- }
- if (transport == null)
- continue;
-
- ColibriConferenceIQ.Channel channelRequest
- = new ColibriConferenceIQ.Channel();
-
- channelRequest.setID(channel.getID());
- channelRequest.setInitiator(initiator);
- channelRequest.setTransport(transport);
-
- if (conferenceRequest == null)
- {
- if (colibri == null)
- break;
- else
- {
- String id = colibri.getID();
-
- if ((id == null) || (id.length() == 0))
- break;
- else
- {
- conferenceRequest = new ColibriConferenceIQ();
- conferenceRequest.setID(id);
- conferenceRequest.setTo(colibri.getFrom());
- conferenceRequest.setType(IQ.Type.SET);
- }
- }
- }
- conferenceRequest.getOrCreateContent(media).addChannel(
- channelRequest);
- }
- }
- if (conferenceRequest != null)
- {
- peer.getProtocolProvider().getConnection().sendPacket(
- conferenceRequest);
- }
- }
-
- /**
- * Starts transport candidate harvest for a specific
- * <tt>ContentPacketExtension</tt> that we are going to offer or answer
- * with.
- *
- * @param theirContent the <tt>ContentPacketExtension</tt> offered by the
- * remote peer to which we are going to answer with <tt>ourContent</tt> or
- * <tt>null</tt> if <tt>ourContent</tt> will be an offer to the remote peer
- * @param ourContent the <tt>ContentPacketExtension</tt> for which transport
- * candidate harvest is to be started
- * @param transportInfoSender a <tt>TransportInfoSender</tt> if the
- * harvested transport candidates are to be sent in a
- * <tt>transport-info</tt> rather than in <tt>ourContent</tt>; otherwise,
- * <tt>null</tt>
- * @param media the media of the <tt>RtpDescriptionPacketExtension</tt>
- * child of <tt>ourContent</tt>
- * @return a <tt>PacketExtension</tt> to be added as a child to
- * <tt>ourContent</tt>; otherwise, <tt>null</tt>
- * @throws OperationFailedException if anything goes wrong while starting
- * transport candidate harvest for the specified <tt>ourContent</tt>
- */
- protected abstract PacketExtension startCandidateHarvest(
- ContentPacketExtension theirContent,
- ContentPacketExtension ourContent,
- TransportInfoSender transportInfoSender,
- String media)
- throws OperationFailedException;
-
- /**
- * Starts transport candidate harvest. This method should complete rapidly
- * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
- * are necessary, they should be executed in a separate thread. Candidate
- * harvest would then need to be concluded in the
- * {@link #wrapupCandidateHarvest()} method which would be called once we
- * absolutely need the candidates.
- *
- * @param theirOffer a media description offer that we've received from the
- * remote party and that we should use in case we need to know what
- * transports our peer is using.
- * @param ourAnswer the content descriptions that we should be adding our
- * transport lists to (although not necessarily in this very instance).
- * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
- * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
- * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
- * <tt>TransportManagerJabberImpl</tt> wishes to utilize
- * <tt>transport-info</tt>. Local candidate addresses sent by this
- * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
- * expected to not be included in the result of
- * {@link #wrapupCandidateHarvest()}.
- *
- * @throws OperationFailedException if we fail to allocate a port number.
- */
- public void startCandidateHarvest(
- List<ContentPacketExtension> theirOffer,
- List<ContentPacketExtension> ourAnswer,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- CallPeerJabberImpl peer = getCallPeer();
- CallJabberImpl call = peer.getCall();
- boolean isJitsiVideobridge = call.getConference().isJitsiVideobridge();
- List<ContentPacketExtension> cpes
- = (theirOffer == null) ? ourAnswer : theirOffer;
-
- /*
- * If Jitsi Videobridge is to be used, determine which channels are to
- * be allocated and attempt to allocate them now.
- */
- if (isJitsiVideobridge)
- {
- Map<ContentPacketExtension,ContentPacketExtension> contentMap
- = new LinkedHashMap
- <ContentPacketExtension,ContentPacketExtension>();
-
- for (ContentPacketExtension cpe : cpes)
- {
- MediaType mediaType = JingleUtils.getMediaType(cpe);
-
- /*
- * The existence of a content for the mediaType and regardless
- * of the existence of channels in it signals that a channel
- * allocation request has already been sent for that mediaType.
- */
- if ((colibri == null)
- || (colibri.getContent(mediaType.toString()) == null))
- {
- ContentPacketExtension local, remote;
-
- if (cpes == ourAnswer)
- {
- local = cpe;
- remote
- = (theirOffer == null)
- ? null
- : findContentByName(theirOffer, cpe.getName());
- }
- else
- {
- local = findContentByName(ourAnswer, cpe.getName());
- remote = cpe;
- }
- contentMap.put(local, remote);
- }
- }
- if (!contentMap.isEmpty())
- {
- /*
- * We are about to request the channel allocations for the media
- * types found in contentMap. Regardless of the response, we do
- * not want to repeat these requests.
- */
- if (colibri == null)
- colibri = new ColibriConferenceIQ();
- for (Map.Entry<ContentPacketExtension,ContentPacketExtension> e
- : contentMap.entrySet())
- {
- ContentPacketExtension cpe = e.getValue();
-
- if (cpe == null)
- cpe = e.getKey();
-
- colibri.getOrCreateContent(
- JingleUtils.getMediaType(cpe).toString());
- }
-
- ColibriConferenceIQ conferenceResult
- = call.createColibriChannels(peer, contentMap);
-
- if (conferenceResult != null)
- {
- String videobridgeID = colibri.getID();
- String conferenceResultID = conferenceResult.getID();
-
- if (videobridgeID == null)
- colibri.setID(conferenceResultID);
- else if (!videobridgeID.equals(conferenceResultID))
- throw new IllegalStateException("conference.id");
-
- String videobridgeFrom = conferenceResult.getFrom();
-
- if ((videobridgeFrom != null)
- && (videobridgeFrom.length() != 0))
- {
- colibri.setFrom(videobridgeFrom);
- }
-
- for (ColibriConferenceIQ.Content contentResult
- : conferenceResult.getContents())
- {
- ColibriConferenceIQ.Content content
- = colibri.getOrCreateContent(
- contentResult.getName());
-
- for (ColibriConferenceIQ.Channel channelResult
- : contentResult.getChannels())
- {
- if (content.getChannel(channelResult.getID())
- == null)
- {
- content.addChannel(channelResult);
- }
- }
- }
- }
- else
- {
- /*
- * The call fails if the createColibriChannels method fails
- * which may happen if the conference packet times out or it
- * can't be built.
- */
- ProtocolProviderServiceJabberImpl
- .throwOperationFailedException(
- "Failed to allocate colibri channel.",
- OperationFailedException.GENERAL_ERROR,
- null,
- logger);
- }
- }
- }
-
- for (ContentPacketExtension cpe : cpes)
- {
- String contentName = cpe.getName();
- ContentPacketExtension ourContent
- = findContentByName(ourAnswer, contentName);
-
- //it might be that we decided not to reply to this content
- if (ourContent != null)
- {
- ContentPacketExtension theirContent
- = (theirOffer == null)
- ? null
- : findContentByName(theirOffer, contentName);
- RtpDescriptionPacketExtension rtpDesc
- = ourContent.getFirstChildOfType(
- RtpDescriptionPacketExtension.class);
- String media = rtpDesc.getMedia();
- PacketExtension pe
- = startCandidateHarvest(
- theirContent,
- ourContent,
- transportInfoSender,
- media);
-
- if (pe != null)
- ourContent.addChildExtension(pe);
- }
- }
- }
-
- /**
- * Starts transport candidate harvest. This method should complete rapidly
- * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
- * are necessary, they should be executed in a separate thread. Candidate
- * harvest would then need to be concluded in the
- * {@link #wrapupCandidateHarvest()} method which would be called once we
- * absolutely need the candidates.
- *
- * @param ourOffer the content descriptions that we should be adding our
- * transport lists to (although not necessarily in this very instance).
- * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
- * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
- * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
- * <tt>TransportManagerJabberImpl</tt> wishes to utilize
- * <tt>transport-info</tt>. Local candidate addresses sent by this
- * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
- * expected to not be included in the result of
- * {@link #wrapupCandidateHarvest()}.
- * @throws OperationFailedException if we fail to allocate a port number.
- */
- public void startCandidateHarvest(
- List<ContentPacketExtension> ourOffer,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- startCandidateHarvest(
- /* theirOffer */ null,
- ourOffer,
- transportInfoSender);
- }
-
- /**
- * Notifies the transport manager that it should conclude candidate
- * harvesting as soon as possible and return the lists of candidates
- * gathered so far.
- *
- * @return the content list that we received earlier (possibly cloned into
- * a new instance) and that we have updated with transport lists.
- */
- public abstract List<ContentPacketExtension> wrapupCandidateHarvest();
-
- /**
- * Looks through the <tt>cpExtList</tt> and returns the {@link
- * ContentPacketExtension} with the specified name.
- *
- * @param cpExtList the list that we will be searching for a specific
- * content.
- * @param name the name of the content element we are looking for.
- * @return the {@link ContentPacketExtension} with the specified name or
- * <tt>null</tt> if no such content element exists.
- */
- public static ContentPacketExtension findContentByName(
- Iterable<ContentPacketExtension> cpExtList,
- String name)
- {
- for(ContentPacketExtension cpExt : cpExtList)
- {
- if(cpExt.getName().equals(name))
- return cpExt;
- }
- return null;
- }
-
- /**
- * Starts the connectivity establishment of this
- * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between
- * the local and the remote peers given the remote counterpart of the
- * negotiation between them.
- *
- * @param remote the collection of <tt>ContentPacketExtension</tt>s which
- * represents the remote counterpart of the negotiation between the local
- * and the remote peer
- * @return <tt>true</tt> if connectivity establishment has been started in
- * response to the call; otherwise, <tt>false</tt>.
- * <tt>TransportManagerJabberImpl</tt> implementations which do not perform
- * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The
- * default implementation does not perform connectivity checks and always
- * returns <tt>true</tt>.
- */
- public boolean startConnectivityEstablishment(
- Iterable<ContentPacketExtension> remote)
- {
- return true;
- }
-
- /**
- * Starts the connectivity establishment of this
- * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between
- * the local and the remote peers given the remote counterpart of the
- * negotiation between them.
- *
- * @param remote a <tt>Map</tt> of
- * media-<tt>IceUdpTransportPacketExtension</tt> pairs which represents the
- * remote counterpart of the negotiation between the local and the remote
- * peers
- * @return <tt>true</tt> if connectivity establishment has been started in
- * response to the call; otherwise, <tt>false</tt>.
- * <tt>TransportManagerJabberImpl</tt> implementations which do not perform
- * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The
- * default implementation does not perform connectivity checks and always
- * returns <tt>true</tt>.
- */
- protected boolean startConnectivityEstablishment(
- Map<String,IceUdpTransportPacketExtension> remote)
- {
- return true;
- }
-
- /**
- * Notifies this <tt>TransportManagerJabberImpl</tt> that it should conclude
- * any started connectivity establishment.
- *
- * @throws OperationFailedException if anything goes wrong with connectivity
- * establishment (i.e. ICE failed, ...)
- */
- public void wrapupConnectivityEstablishment()
- throws OperationFailedException
- {
- }
-
- /**
- * Removes a content with a specific name from the transport-related part of
- * the session represented by this <tt>TransportManagerJabberImpl</tt> which
- * may have been reported through previous calls to the
- * <tt>startCandidateHarvest</tt> and
- * <tt>startConnectivityEstablishment</tt> methods.
- * <p>
- * <b>Note</b>: Because <tt>TransportManager</tt> deals with
- * <tt>MediaType</tt>s, not content names and
- * <tt>TransportManagerJabberImpl</tt> does not implement translating from
- * content name to <tt>MediaType</tt>, implementers are expected to call
- * {@link TransportManager#closeStreamConnector(MediaType)}.
- * </p>
- *
- * @param name the name of the content to be removed from the
- * transport-related part of the session represented by this
- * <tt>TransportManagerJabberImpl</tt>
- */
- public abstract void removeContent(String name);
-
- /**
- * Removes a content with a specific name from a specific collection of
- * contents and closes any associated <tt>StreamConnector</tt>.
- *
- * @param contents the collection of contents to remove the content with the
- * specified name from
- * @param name the name of the content to remove
- * @return the removed <tt>ContentPacketExtension</tt> if any; otherwise,
- * <tt>null</tt>
- */
- protected ContentPacketExtension removeContent(
- Iterable<ContentPacketExtension> contents,
- String name)
- {
- for (Iterator<ContentPacketExtension> contentIter = contents.iterator();
- contentIter.hasNext();)
- {
- ContentPacketExtension content = contentIter.next();
-
- if (name.equals(content.getName()))
- {
- contentIter.remove();
-
- // closeStreamConnector
- MediaType mediaType = JingleUtils.getMediaType(content);
- if (mediaType != null)
- {
- closeStreamConnector(mediaType);
- }
-
- return content;
- }
- }
- return null;
- }
-
- /**
- * Clones a specific <tt>IceUdpTransportPacketExtension</tt> and its
- * candidates.
- *
- * @param src the <tt>IceUdpTransportPacketExtension</tt> to be cloned
- * @return a new <tt>IceUdpTransportPacketExtension</tt> instance which has
- * the same run-time type, attributes, namespace, text and candidates as the
- * specified <tt>src</tt>
- * @throws OperationFailedException if an error occurs during the cloing of
- * the specified <tt>src</tt> and its candidates
- */
- static IceUdpTransportPacketExtension cloneTransportAndCandidates(
- IceUdpTransportPacketExtension src)
- throws OperationFailedException
- {
- try
- {
- return IceUdpTransportPacketExtension
- .cloneTransportAndCandidates(src);
- }
- catch (Exception e)
- {
- ProtocolProviderServiceJabberImpl
- .throwOperationFailedException(
- "Failed to close transport and candidates.",
- OperationFailedException.GENERAL_ERROR,
- e,
- logger);
-
- }
- return null;
- }
-
- /**
- * Releases the resources acquired by this <tt>TransportManager</tt> and
- * prepares it for garbage collection.
- */
- public void close()
- {
- for (MediaType mediaType : MediaType.values())
- closeStreamConnector(mediaType);
- }
-
- /**
- * Closes a specific <tt>StreamConnector</tt> associated with a specific
- * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to
- * the specified <tt>streamConnector</tt>, it remains.
- * Also expires the <tt>ColibriConferenceIQ.Channel</tt> associated with
- * the closed <tt>StreamConnector</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> associated with the specified
- * <tt>streamConnector</tt>
- * @param streamConnector the <tt>StreamConnector</tt> to be closed
- */
- @Override
- protected void closeStreamConnector(
- MediaType mediaType,
- StreamConnector streamConnector)
- {
- try
- {
- boolean superCloseStreamConnector = true;
-
- if (streamConnector instanceof ColibriStreamConnector)
- {
- CallPeerJabberImpl peer = getCallPeer();
-
- if (peer != null)
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- superCloseStreamConnector = false;
- call.closeColibriStreamConnector(
- peer,
- mediaType,
- (ColibriStreamConnector) streamConnector);
- }
- }
- }
- if (superCloseStreamConnector)
- super.closeStreamConnector(mediaType, streamConnector);
- }
- finally
- {
- /*
- * Expire the ColibriConferenceIQ.Channel associated with the closed
- * StreamConnector.
- */
- if (colibri != null)
- {
- ColibriConferenceIQ.Content content
- = colibri.getContent(mediaType.toString());
-
- if (content != null)
- {
- List<ColibriConferenceIQ.Channel> channels
- = content.getChannels();
-
- if (channels.size() == 2)
- {
- ColibriConferenceIQ requestConferenceIQ
- = new ColibriConferenceIQ();
-
- requestConferenceIQ.setID(colibri.getID());
-
- ColibriConferenceIQ.Content requestContent
- = requestConferenceIQ.getOrCreateContent(
- content.getName());
-
- requestContent.addChannel(channels.get(1 /* remote */));
-
- /*
- * Regardless of whether the request to expire the
- * Channel associated with mediaType succeeds, consider
- * the Channel in question expired. Since
- * RawUdpTransportManager allocates a single channel per
- * MediaType, consider the whole Content expired.
- */
- colibri.removeContent(content);
-
- CallPeerJabberImpl peer = getCallPeer();
-
- if (peer != null)
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- call.expireColibriChannels(
- peer,
- requestConferenceIQ);
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * Adds support for telephony conferences utilizing the Jitsi Videobridge
- * server-side technology.
- *
- * @see #doCreateStreamConnector(MediaType)
- */
- @Override
- protected StreamConnector createStreamConnector(final MediaType mediaType)
- throws OperationFailedException
- {
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, true /* local */);
-
- if (channel != null)
- {
- CallPeerJabberImpl peer = getCallPeer();
- CallJabberImpl call = peer.getCall();
- StreamConnector streamConnector
- = call.createColibriStreamConnector(
- peer,
- mediaType,
- channel,
- new StreamConnectorFactory()
- {
- public StreamConnector createStreamConnector()
- {
- try
- {
- return doCreateStreamConnector(mediaType);
- }
- catch (OperationFailedException ofe)
- {
- return null;
- }
- }
- });
-
- if (streamConnector != null)
- return streamConnector;
- }
-
- return doCreateStreamConnector(mediaType);
- }
-
- protected abstract PacketExtension createTransport(String media)
- throws OperationFailedException;
-
- protected PacketExtension createTransportForStartCandidateHarvest(
- String media)
- throws OperationFailedException
- {
- PacketExtension pe = null;
-
- if (getCallPeer().isJitsiVideobridge())
- {
- MediaType mediaType = MediaType.parseString(media);
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType, false /* remote */);
-
- if (channel != null)
- pe = cloneTransportAndCandidates(channel.getTransport());
- }
- else
- pe = createTransport(media);
- return pe;
- }
-
- /**
- * Initializes a new <tt>PacketExtension</tt> instance appropriate to the
- * type of Jingle transport represented by this <tt>TransportManager</tt>.
- * The new instance is not initialized with any attributes or child
- * extensions.
- *
- * @return a new <tt>PacketExtension</tt> instance appropriate to the type
- * of Jingle transport represented by this <tt>TransportManager</tt>
- */
- protected abstract PacketExtension createTransportPacketExtension();
-
- /**
- * Creates a media <tt>StreamConnector</tt> for a stream of a specific
- * <tt>MediaType</tt>. The minimum and maximum of the media port boundaries
- * are taken into account.
- *
- * @param mediaType the <tt>MediaType</tt> of the stream for which a
- * <tt>StreamConnector</tt> is to be created
- * @return a <tt>StreamConnector</tt> for the stream of the specified
- * <tt>mediaType</tt>
- * @throws OperationFailedException if the binding of the sockets fails
- */
- protected StreamConnector doCreateStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- return super.createStreamConnector(mediaType);
- }
-
- /**
- * Finds a <tt>TransportManagerJabberImpl</tt> participating in a telephony
- * conference utilizing the Jitsi Videobridge server-side technology that
- * this instance is participating in which is establishing the connectivity
- * with the Jitsi Videobridge server (as opposed to a <tt>CallPeer</tt>).
- *
- * @return a <tt>TransportManagerJabberImpl</tt> which is participating in
- * a telephony conference utilizing the Jitsi Videobridge server-side
- * technology that this instance is participating in which is establishing
- * the connectivity with the Jitsi Videobridge server (as opposed to a
- * <tt>CallPeer</tt>).
- */
- TransportManagerJabberImpl
- findTransportManagerEstablishingConnectivityWithJitsiVideobridge()
- {
- Call call = getCallPeer().getCall();
- TransportManagerJabberImpl transportManager = null;
-
- if (call != null)
- {
- CallConference conference = call.getConference();
-
- if ((conference != null) && conference.isJitsiVideobridge())
- {
- for (Call aCall : conference.getCalls())
- {
- Iterator<? extends CallPeer> callPeerIter
- = aCall.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- CallPeer aCallPeer = callPeerIter.next();
-
- if (aCallPeer instanceof CallPeerJabberImpl)
- {
- TransportManagerJabberImpl aTransportManager
- = ((CallPeerJabberImpl) aCallPeer)
- .getMediaHandler()
- .getTransportManager();
-
- if (aTransportManager
- .isEstablishingConnectivityWithJitsiVideobridge)
- {
- transportManager = aTransportManager;
- break;
- }
- }
- }
- }
- }
- }
- return transportManager;
- }
-
- /**
- * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content
- * associated with a specific <tt>MediaType</tt> and is to be either locally
- * or remotely used.
- * <p>
- * <b>Note</b>: Modifications to the <tt>ColibriConferenceIQ.Channel</tt>
- * instance returned by the method propagate to (the state of) this
- * instance.
- * </p>
- *
- * @param mediaType the <tt>MediaType</tt> associated with the content which
- * contains the <tt>ColibriConferenceIQ.Channel</tt> to get
- * @param local <tt>true</tt> if the <tt>ColibriConferenceIQ.Channel</tt>
- * which is to be used locally is to be returned or <tt>false</tt> for the
- * one which is to be used remotely
- * @return the <tt>ColibriConferenceIQ.Channel</tt> which belongs to a
- * content associated with the specified <tt>mediaType</tt> and which is to
- * be used in accord with the specified <tt>local</tt> indicator if such a
- * channel exists; otherwise, <tt>null</tt>
- */
- ColibriConferenceIQ.Channel getColibriChannel(
- MediaType mediaType,
- boolean local)
- {
- ColibriConferenceIQ.Channel channel = null;
-
- if (colibri != null)
- {
- ColibriConferenceIQ.Content content
- = colibri.getContent(mediaType.toString());
-
- if (content != null)
- {
- List<ColibriConferenceIQ.Channel> channels
- = content.getChannels();
-
- if (channels.size() == 2)
- channel = channels.get(local ? 0 : 1);
- }
- }
- return channel;
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.net.*;
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * <tt>TransportManager</tt>s gather local candidates for incoming and outgoing
+ * calls. Their work starts by calling a start method which, using the remote
+ * peer's session description, would start the harvest. Calling a second wrapup
+ * method would deliver the candidate harvest, possibly after blocking if it has
+ * not yet completed.
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ */
+public abstract class TransportManagerJabberImpl
+ extends TransportManager<CallPeerJabberImpl>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>TransportManagerJabberImpl</tt> class
+ * and its instances to print debug messages.
+ */
+ private static final Logger logger
+ = Logger.getLogger(TransportManagerJabberImpl.class);
+
+ /**
+ * The ID that we will be assigning to our next candidate. We use
+ * <tt>int</tt>s for interoperability reasons (Emil: I believe that GTalk
+ * uses <tt>int</tt>s. If that turns out not to be the case we can stop
+ * using <tt>int</tt>s here if that's an issue).
+ */
+ private static int nextID = 1;
+
+ /**
+ * The information pertaining to the Jisti Videobridge conference which the
+ * local peer represented by this instance is a focus of. It gives a view of
+ * the whole Jitsi Videobridge conference managed by the associated
+ * <tt>CallJabberImpl</tt> which provides information specific to this
+ * <tt>TransportManager</tt> only.
+ */
+ private ColibriConferenceIQ colibri;
+
+ /**
+ * The generation of the candidates we are currently generating
+ */
+ private int currentGeneration = 0;
+
+ /**
+ * The indicator which determines whether this <tt>TransportManager</tt>
+ * instance is responsible to establish the connectivity with the associated
+ * Jitsi Videobridge (in case it is being employed at all).
+ */
+ boolean isEstablishingConnectivityWithJitsiVideobridge = false;
+
+ /**
+ * The indicator which determines whether this <tt>TransportManager</tt>
+ * instance is yet to start establishing the connectivity with the
+ * associated Jitsi Videobridge (in case it is being employed at all).
+ */
+ boolean startConnectivityEstablishmentWithJitsiVideobridge = false;
+
+ /**
+ * Creates a new instance of this transport manager, binding it to the
+ * specified peer.
+ *
+ * @param callPeer the {@link CallPeer} whose traffic we will be taking
+ * care of.
+ */
+ protected TransportManagerJabberImpl(CallPeerJabberImpl callPeer)
+ {
+ super(callPeer);
+ }
+
+ /**
+ * Returns the <tt>InetAddress</tt> that is most likely to be to be used
+ * as a next hop when contacting the specified <tt>destination</tt>. This is
+ * an utility method that is used whenever we have to choose one of our
+ * local addresses to put in the Via, Contact or (in the case of no
+ * registrar accounts) From headers.
+ *
+ * @param peer the CallPeer that we would contact.
+ *
+ * @return the <tt>InetAddress</tt> that is most likely to be to be used
+ * as a next hop when contacting the specified <tt>destination</tt>.
+ *
+ * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
+ * host/IP/FQDN
+ */
+ @Override
+ protected InetAddress getIntendedDestination(CallPeerJabberImpl peer)
+ {
+ return peer.getProtocolProvider().getNextHop();
+ }
+
+ /**
+ * Returns the ID that we will be assigning to the next candidate we create.
+ *
+ * @return the next ID to use with a candidate.
+ */
+ protected String getNextID()
+ {
+ int nextID;
+
+ synchronized (TransportManagerJabberImpl.class)
+ {
+ nextID = TransportManagerJabberImpl.nextID++;
+ }
+ return Integer.toString(nextID);
+ }
+
+ /**
+ * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of
+ * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * is to have its <tt>target</tt> set to the returned
+ * <tt>MediaStreamTarget</tt>
+ * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt>
+ * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt>
+ */
+ public abstract MediaStreamTarget getStreamTarget(MediaType mediaType);
+
+ /**
+ * Gets the XML namespace of the Jingle transport implemented by this
+ * <tt>TransportManagerJabberImpl</tt>.
+ *
+ * @return the XML namespace of the Jingle transport implemented by this
+ * <tt>TransportManagerJabberImpl</tt>
+ */
+ public abstract String getXmlNamespace();
+
+ /**
+ * Returns the generation that our current candidates belong to.
+ *
+ * @return the generation that we should assign to candidates that we are
+ * currently advertising.
+ */
+ protected int getCurrentGeneration()
+ {
+ return currentGeneration;
+ }
+
+ /**
+ * Increments the generation that we are assigning candidates.
+ */
+ protected void incrementGeneration()
+ {
+ currentGeneration++;
+ }
+
+ /**
+ * Sends transport-related information received from the remote peer to the
+ * associated Jiitsi Videobridge in order to update the (remote)
+ * <tt>ColibriConferenceIQ.Channel</tt> associated with this
+ * <tt>TransportManager</tt> instance.
+ *
+ * @param map a <tt>Map</tt> of media-IceUdpTransportPacketExtension pairs
+ * which represents the transport-related information which has been
+ * received from the remote peer and which is to be sent to the associated
+ * Jitsi Videobridge
+ */
+ protected void sendTransportInfoToJitsiVideobridge(
+ Map<String,IceUdpTransportPacketExtension> map)
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+ boolean initiator = !peer.isInitiator();
+ ColibriConferenceIQ conferenceRequest = null;
+
+ for (Map.Entry<String,IceUdpTransportPacketExtension> e
+ : map.entrySet())
+ {
+ String media = e.getKey();
+ MediaType mediaType = MediaType.parseString(media);
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, false /* remote */);
+
+ if (channel != null)
+ {
+ IceUdpTransportPacketExtension transport;
+
+ try
+ {
+ transport = cloneTransportAndCandidates(e.getValue());
+ }
+ catch (OperationFailedException ofe)
+ {
+ transport = null;
+ }
+ if (transport == null)
+ continue;
+
+ ColibriConferenceIQ.Channel channelRequest
+ = new ColibriConferenceIQ.Channel();
+
+ channelRequest.setID(channel.getID());
+ channelRequest.setInitiator(initiator);
+ channelRequest.setTransport(transport);
+
+ if (conferenceRequest == null)
+ {
+ if (colibri == null)
+ break;
+ else
+ {
+ String id = colibri.getID();
+
+ if ((id == null) || (id.length() == 0))
+ break;
+ else
+ {
+ conferenceRequest = new ColibriConferenceIQ();
+ conferenceRequest.setID(id);
+ conferenceRequest.setTo(colibri.getFrom());
+ conferenceRequest.setType(IQ.Type.SET);
+ }
+ }
+ }
+ conferenceRequest.getOrCreateContent(media).addChannel(
+ channelRequest);
+ }
+ }
+ if (conferenceRequest != null)
+ {
+ peer.getProtocolProvider().getConnection().sendPacket(
+ conferenceRequest);
+ }
+ }
+
+ /**
+ * Starts transport candidate harvest for a specific
+ * <tt>ContentPacketExtension</tt> that we are going to offer or answer
+ * with.
+ *
+ * @param theirContent the <tt>ContentPacketExtension</tt> offered by the
+ * remote peer to which we are going to answer with <tt>ourContent</tt> or
+ * <tt>null</tt> if <tt>ourContent</tt> will be an offer to the remote peer
+ * @param ourContent the <tt>ContentPacketExtension</tt> for which transport
+ * candidate harvest is to be started
+ * @param transportInfoSender a <tt>TransportInfoSender</tt> if the
+ * harvested transport candidates are to be sent in a
+ * <tt>transport-info</tt> rather than in <tt>ourContent</tt>; otherwise,
+ * <tt>null</tt>
+ * @param media the media of the <tt>RtpDescriptionPacketExtension</tt>
+ * child of <tt>ourContent</tt>
+ * @return a <tt>PacketExtension</tt> to be added as a child to
+ * <tt>ourContent</tt>; otherwise, <tt>null</tt>
+ * @throws OperationFailedException if anything goes wrong while starting
+ * transport candidate harvest for the specified <tt>ourContent</tt>
+ */
+ protected abstract PacketExtension startCandidateHarvest(
+ ContentPacketExtension theirContent,
+ ContentPacketExtension ourContent,
+ TransportInfoSender transportInfoSender,
+ String media)
+ throws OperationFailedException;
+
+ /**
+ * Starts transport candidate harvest. This method should complete rapidly
+ * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
+ * are necessary, they should be executed in a separate thread. Candidate
+ * harvest would then need to be concluded in the
+ * {@link #wrapupCandidateHarvest()} method which would be called once we
+ * absolutely need the candidates.
+ *
+ * @param theirOffer a media description offer that we've received from the
+ * remote party and that we should use in case we need to know what
+ * transports our peer is using.
+ * @param ourAnswer the content descriptions that we should be adding our
+ * transport lists to (although not necessarily in this very instance).
+ * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
+ * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
+ * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
+ * <tt>TransportManagerJabberImpl</tt> wishes to utilize
+ * <tt>transport-info</tt>. Local candidate addresses sent by this
+ * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
+ * expected to not be included in the result of
+ * {@link #wrapupCandidateHarvest()}.
+ *
+ * @throws OperationFailedException if we fail to allocate a port number.
+ */
+ public void startCandidateHarvest(
+ List<ContentPacketExtension> theirOffer,
+ List<ContentPacketExtension> ourAnswer,
+ TransportInfoSender transportInfoSender)
+ throws OperationFailedException
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+ CallJabberImpl call = peer.getCall();
+ boolean isJitsiVideobridge = call.getConference().isJitsiVideobridge();
+ List<ContentPacketExtension> cpes
+ = (theirOffer == null) ? ourAnswer : theirOffer;
+
+ /*
+ * If Jitsi Videobridge is to be used, determine which channels are to
+ * be allocated and attempt to allocate them now.
+ */
+ if (isJitsiVideobridge)
+ {
+ Map<ContentPacketExtension,ContentPacketExtension> contentMap
+ = new LinkedHashMap
+ <ContentPacketExtension,ContentPacketExtension>();
+
+ for (ContentPacketExtension cpe : cpes)
+ {
+ MediaType mediaType = JingleUtils.getMediaType(cpe);
+
+ /*
+ * The existence of a content for the mediaType and regardless
+ * of the existence of channels in it signals that a channel
+ * allocation request has already been sent for that mediaType.
+ */
+ if ((colibri == null)
+ || (colibri.getContent(mediaType.toString()) == null))
+ {
+ ContentPacketExtension local, remote;
+
+ if (cpes == ourAnswer)
+ {
+ local = cpe;
+ remote
+ = (theirOffer == null)
+ ? null
+ : findContentByName(theirOffer, cpe.getName());
+ }
+ else
+ {
+ local = findContentByName(ourAnswer, cpe.getName());
+ remote = cpe;
+ }
+ contentMap.put(local, remote);
+ }
+ }
+ if (!contentMap.isEmpty())
+ {
+ /*
+ * We are about to request the channel allocations for the media
+ * types found in contentMap. Regardless of the response, we do
+ * not want to repeat these requests.
+ */
+ if (colibri == null)
+ colibri = new ColibriConferenceIQ();
+ for (Map.Entry<ContentPacketExtension,ContentPacketExtension> e
+ : contentMap.entrySet())
+ {
+ ContentPacketExtension cpe = e.getValue();
+
+ if (cpe == null)
+ cpe = e.getKey();
+
+ colibri.getOrCreateContent(
+ JingleUtils.getMediaType(cpe).toString());
+ }
+
+ ColibriConferenceIQ conferenceResult
+ = call.createColibriChannels(peer, contentMap);
+
+ if (conferenceResult != null)
+ {
+ String videobridgeID = colibri.getID();
+ String conferenceResultID = conferenceResult.getID();
+
+ if (videobridgeID == null)
+ colibri.setID(conferenceResultID);
+ else if (!videobridgeID.equals(conferenceResultID))
+ throw new IllegalStateException("conference.id");
+
+ String videobridgeFrom = conferenceResult.getFrom();
+
+ if ((videobridgeFrom != null)
+ && (videobridgeFrom.length() != 0))
+ {
+ colibri.setFrom(videobridgeFrom);
+ }
+
+ for (ColibriConferenceIQ.Content contentResult
+ : conferenceResult.getContents())
+ {
+ ColibriConferenceIQ.Content content
+ = colibri.getOrCreateContent(
+ contentResult.getName());
+
+ for (ColibriConferenceIQ.Channel channelResult
+ : contentResult.getChannels())
+ {
+ if (content.getChannel(channelResult.getID())
+ == null)
+ {
+ content.addChannel(channelResult);
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * The call fails if the createColibriChannels method fails
+ * which may happen if the conference packet times out or it
+ * can't be built.
+ */
+ ProtocolProviderServiceJabberImpl
+ .throwOperationFailedException(
+ "Failed to allocate colibri channel.",
+ OperationFailedException.GENERAL_ERROR,
+ null,
+ logger);
+ }
+ }
+ }
+
+ for (ContentPacketExtension cpe : cpes)
+ {
+ String contentName = cpe.getName();
+ ContentPacketExtension ourContent
+ = findContentByName(ourAnswer, contentName);
+
+ //it might be that we decided not to reply to this content
+ if (ourContent != null)
+ {
+ ContentPacketExtension theirContent
+ = (theirOffer == null)
+ ? null
+ : findContentByName(theirOffer, contentName);
+ RtpDescriptionPacketExtension rtpDesc
+ = ourContent.getFirstChildOfType(
+ RtpDescriptionPacketExtension.class);
+ String media = rtpDesc.getMedia();
+ PacketExtension pe
+ = startCandidateHarvest(
+ theirContent,
+ ourContent,
+ transportInfoSender,
+ media);
+
+ if (pe != null)
+ ourContent.addChildExtension(pe);
+ }
+ }
+ }
+
+ /**
+ * Starts transport candidate harvest. This method should complete rapidly
+ * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests
+ * are necessary, they should be executed in a separate thread. Candidate
+ * harvest would then need to be concluded in the
+ * {@link #wrapupCandidateHarvest()} method which would be called once we
+ * absolutely need the candidates.
+ *
+ * @param ourOffer the content descriptions that we should be adding our
+ * transport lists to (although not necessarily in this very instance).
+ * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by
+ * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt>
+ * <tt>JingleIQ</tt>s from the local peer to the remote peer if this
+ * <tt>TransportManagerJabberImpl</tt> wishes to utilize
+ * <tt>transport-info</tt>. Local candidate addresses sent by this
+ * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are
+ * expected to not be included in the result of
+ * {@link #wrapupCandidateHarvest()}.
+ * @throws OperationFailedException if we fail to allocate a port number.
+ */
+ public void startCandidateHarvest(
+ List<ContentPacketExtension> ourOffer,
+ TransportInfoSender transportInfoSender)
+ throws OperationFailedException
+ {
+ startCandidateHarvest(
+ /* theirOffer */ null,
+ ourOffer,
+ transportInfoSender);
+ }
+
+ /**
+ * Notifies the transport manager that it should conclude candidate
+ * harvesting as soon as possible and return the lists of candidates
+ * gathered so far.
+ *
+ * @return the content list that we received earlier (possibly cloned into
+ * a new instance) and that we have updated with transport lists.
+ */
+ public abstract List<ContentPacketExtension> wrapupCandidateHarvest();
+
+ /**
+ * Looks through the <tt>cpExtList</tt> and returns the {@link
+ * ContentPacketExtension} with the specified name.
+ *
+ * @param cpExtList the list that we will be searching for a specific
+ * content.
+ * @param name the name of the content element we are looking for.
+ * @return the {@link ContentPacketExtension} with the specified name or
+ * <tt>null</tt> if no such content element exists.
+ */
+ public static ContentPacketExtension findContentByName(
+ Iterable<ContentPacketExtension> cpExtList,
+ String name)
+ {
+ for(ContentPacketExtension cpExt : cpExtList)
+ {
+ if(cpExt.getName().equals(name))
+ return cpExt;
+ }
+ return null;
+ }
+
+ /**
+ * Starts the connectivity establishment of this
+ * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between
+ * the local and the remote peers given the remote counterpart of the
+ * negotiation between them.
+ *
+ * @param remote the collection of <tt>ContentPacketExtension</tt>s which
+ * represents the remote counterpart of the negotiation between the local
+ * and the remote peer
+ * @return <tt>true</tt> if connectivity establishment has been started in
+ * response to the call; otherwise, <tt>false</tt>.
+ * <tt>TransportManagerJabberImpl</tt> implementations which do not perform
+ * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The
+ * default implementation does not perform connectivity checks and always
+ * returns <tt>true</tt>.
+ */
+ public boolean startConnectivityEstablishment(
+ Iterable<ContentPacketExtension> remote)
+ {
+ return true;
+ }
+
+ /**
+ * Starts the connectivity establishment of this
+ * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between
+ * the local and the remote peers given the remote counterpart of the
+ * negotiation between them.
+ *
+ * @param remote a <tt>Map</tt> of
+ * media-<tt>IceUdpTransportPacketExtension</tt> pairs which represents the
+ * remote counterpart of the negotiation between the local and the remote
+ * peers
+ * @return <tt>true</tt> if connectivity establishment has been started in
+ * response to the call; otherwise, <tt>false</tt>.
+ * <tt>TransportManagerJabberImpl</tt> implementations which do not perform
+ * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The
+ * default implementation does not perform connectivity checks and always
+ * returns <tt>true</tt>.
+ */
+ protected boolean startConnectivityEstablishment(
+ Map<String,IceUdpTransportPacketExtension> remote)
+ {
+ return true;
+ }
+
+ /**
+ * Notifies this <tt>TransportManagerJabberImpl</tt> that it should conclude
+ * any started connectivity establishment.
+ *
+ * @throws OperationFailedException if anything goes wrong with connectivity
+ * establishment (i.e. ICE failed, ...)
+ */
+ public void wrapupConnectivityEstablishment()
+ throws OperationFailedException
+ {
+ }
+
+ /**
+ * Removes a content with a specific name from the transport-related part of
+ * the session represented by this <tt>TransportManagerJabberImpl</tt> which
+ * may have been reported through previous calls to the
+ * <tt>startCandidateHarvest</tt> and
+ * <tt>startConnectivityEstablishment</tt> methods.
+ * <p>
+ * <b>Note</b>: Because <tt>TransportManager</tt> deals with
+ * <tt>MediaType</tt>s, not content names and
+ * <tt>TransportManagerJabberImpl</tt> does not implement translating from
+ * content name to <tt>MediaType</tt>, implementers are expected to call
+ * {@link TransportManager#closeStreamConnector(MediaType)}.
+ * </p>
+ *
+ * @param name the name of the content to be removed from the
+ * transport-related part of the session represented by this
+ * <tt>TransportManagerJabberImpl</tt>
+ */
+ public abstract void removeContent(String name);
+
+ /**
+ * Removes a content with a specific name from a specific collection of
+ * contents and closes any associated <tt>StreamConnector</tt>.
+ *
+ * @param contents the collection of contents to remove the content with the
+ * specified name from
+ * @param name the name of the content to remove
+ * @return the removed <tt>ContentPacketExtension</tt> if any; otherwise,
+ * <tt>null</tt>
+ */
+ protected ContentPacketExtension removeContent(
+ Iterable<ContentPacketExtension> contents,
+ String name)
+ {
+ for (Iterator<ContentPacketExtension> contentIter = contents.iterator();
+ contentIter.hasNext();)
+ {
+ ContentPacketExtension content = contentIter.next();
+
+ if (name.equals(content.getName()))
+ {
+ contentIter.remove();
+
+ // closeStreamConnector
+ MediaType mediaType = JingleUtils.getMediaType(content);
+ if (mediaType != null)
+ {
+ closeStreamConnector(mediaType);
+ }
+
+ return content;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Clones a specific <tt>IceUdpTransportPacketExtension</tt> and its
+ * candidates.
+ *
+ * @param src the <tt>IceUdpTransportPacketExtension</tt> to be cloned
+ * @return a new <tt>IceUdpTransportPacketExtension</tt> instance which has
+ * the same run-time type, attributes, namespace, text and candidates as the
+ * specified <tt>src</tt>
+ * @throws OperationFailedException if an error occurs during the cloing of
+ * the specified <tt>src</tt> and its candidates
+ */
+ static IceUdpTransportPacketExtension cloneTransportAndCandidates(
+ IceUdpTransportPacketExtension src)
+ throws OperationFailedException
+ {
+ try
+ {
+ return IceUdpTransportPacketExtension
+ .cloneTransportAndCandidates(src);
+ }
+ catch (Exception e)
+ {
+ ProtocolProviderServiceJabberImpl
+ .throwOperationFailedException(
+ "Failed to close transport and candidates.",
+ OperationFailedException.GENERAL_ERROR,
+ e,
+ logger);
+
+ }
+ return null;
+ }
+
+ /**
+ * Releases the resources acquired by this <tt>TransportManager</tt> and
+ * prepares it for garbage collection.
+ */
+ public void close()
+ {
+ for (MediaType mediaType : MediaType.values())
+ closeStreamConnector(mediaType);
+ }
+
+ /**
+ * Closes a specific <tt>StreamConnector</tt> associated with a specific
+ * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to
+ * the specified <tt>streamConnector</tt>, it remains.
+ * Also expires the <tt>ColibriConferenceIQ.Channel</tt> associated with
+ * the closed <tt>StreamConnector</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> associated with the specified
+ * <tt>streamConnector</tt>
+ * @param streamConnector the <tt>StreamConnector</tt> to be closed
+ */
+ @Override
+ protected void closeStreamConnector(
+ MediaType mediaType,
+ StreamConnector streamConnector)
+ {
+ try
+ {
+ boolean superCloseStreamConnector = true;
+
+ if (streamConnector instanceof ColibriStreamConnector)
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+
+ if (peer != null)
+ {
+ CallJabberImpl call = peer.getCall();
+
+ if (call != null)
+ {
+ superCloseStreamConnector = false;
+ call.closeColibriStreamConnector(
+ peer,
+ mediaType,
+ (ColibriStreamConnector) streamConnector);
+ }
+ }
+ }
+ if (superCloseStreamConnector)
+ super.closeStreamConnector(mediaType, streamConnector);
+ }
+ finally
+ {
+ /*
+ * Expire the ColibriConferenceIQ.Channel associated with the closed
+ * StreamConnector.
+ */
+ if (colibri != null)
+ {
+ ColibriConferenceIQ.Content content
+ = colibri.getContent(mediaType.toString());
+
+ if (content != null)
+ {
+ List<ColibriConferenceIQ.Channel> channels
+ = content.getChannels();
+
+ if (channels.size() == 2)
+ {
+ ColibriConferenceIQ requestConferenceIQ
+ = new ColibriConferenceIQ();
+
+ requestConferenceIQ.setID(colibri.getID());
+
+ ColibriConferenceIQ.Content requestContent
+ = requestConferenceIQ.getOrCreateContent(
+ content.getName());
+
+ requestContent.addChannel(channels.get(1 /* remote */));
+
+ /*
+ * Regardless of whether the request to expire the
+ * Channel associated with mediaType succeeds, consider
+ * the Channel in question expired. Since
+ * RawUdpTransportManager allocates a single channel per
+ * MediaType, consider the whole Content expired.
+ */
+ colibri.removeContent(content);
+
+ CallPeerJabberImpl peer = getCallPeer();
+
+ if (peer != null)
+ {
+ CallJabberImpl call = peer.getCall();
+
+ if (call != null)
+ {
+ call.expireColibriChannels(
+ peer,
+ requestConferenceIQ);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Adds support for telephony conferences utilizing the Jitsi Videobridge
+ * server-side technology.
+ *
+ * @see #doCreateStreamConnector(MediaType)
+ */
+ @Override
+ protected StreamConnector createStreamConnector(final MediaType mediaType)
+ throws OperationFailedException
+ {
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, true /* local */);
+
+ if (channel != null)
+ {
+ CallPeerJabberImpl peer = getCallPeer();
+ CallJabberImpl call = peer.getCall();
+ StreamConnector streamConnector
+ = call.createColibriStreamConnector(
+ peer,
+ mediaType,
+ channel,
+ new StreamConnectorFactory()
+ {
+ public StreamConnector createStreamConnector()
+ {
+ try
+ {
+ return doCreateStreamConnector(mediaType);
+ }
+ catch (OperationFailedException ofe)
+ {
+ return null;
+ }
+ }
+ });
+
+ if (streamConnector != null)
+ return streamConnector;
+ }
+
+ return doCreateStreamConnector(mediaType);
+ }
+
+ protected abstract PacketExtension createTransport(String media)
+ throws OperationFailedException;
+
+ protected PacketExtension createTransportForStartCandidateHarvest(
+ String media)
+ throws OperationFailedException
+ {
+ PacketExtension pe = null;
+
+ if (getCallPeer().isJitsiVideobridge())
+ {
+ MediaType mediaType = MediaType.parseString(media);
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType, false /* remote */);
+
+ if (channel != null)
+ pe = cloneTransportAndCandidates(channel.getTransport());
+ }
+ else
+ pe = createTransport(media);
+ return pe;
+ }
+
+ /**
+ * Initializes a new <tt>PacketExtension</tt> instance appropriate to the
+ * type of Jingle transport represented by this <tt>TransportManager</tt>.
+ * The new instance is not initialized with any attributes or child
+ * extensions.
+ *
+ * @return a new <tt>PacketExtension</tt> instance appropriate to the type
+ * of Jingle transport represented by this <tt>TransportManager</tt>
+ */
+ protected abstract PacketExtension createTransportPacketExtension();
+
+ /**
+ * Creates a media <tt>StreamConnector</tt> for a stream of a specific
+ * <tt>MediaType</tt>. The minimum and maximum of the media port boundaries
+ * are taken into account.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the stream for which a
+ * <tt>StreamConnector</tt> is to be created
+ * @return a <tt>StreamConnector</tt> for the stream of the specified
+ * <tt>mediaType</tt>
+ * @throws OperationFailedException if the binding of the sockets fails
+ */
+ protected StreamConnector doCreateStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ return super.createStreamConnector(mediaType);
+ }
+
+ /**
+ * Finds a <tt>TransportManagerJabberImpl</tt> participating in a telephony
+ * conference utilizing the Jitsi Videobridge server-side technology that
+ * this instance is participating in which is establishing the connectivity
+ * with the Jitsi Videobridge server (as opposed to a <tt>CallPeer</tt>).
+ *
+ * @return a <tt>TransportManagerJabberImpl</tt> which is participating in
+ * a telephony conference utilizing the Jitsi Videobridge server-side
+ * technology that this instance is participating in which is establishing
+ * the connectivity with the Jitsi Videobridge server (as opposed to a
+ * <tt>CallPeer</tt>).
+ */
+ TransportManagerJabberImpl
+ findTransportManagerEstablishingConnectivityWithJitsiVideobridge()
+ {
+ Call call = getCallPeer().getCall();
+ TransportManagerJabberImpl transportManager = null;
+
+ if (call != null)
+ {
+ CallConference conference = call.getConference();
+
+ if ((conference != null) && conference.isJitsiVideobridge())
+ {
+ for (Call aCall : conference.getCalls())
+ {
+ Iterator<? extends CallPeer> callPeerIter
+ = aCall.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ CallPeer aCallPeer = callPeerIter.next();
+
+ if (aCallPeer instanceof CallPeerJabberImpl)
+ {
+ TransportManagerJabberImpl aTransportManager
+ = ((CallPeerJabberImpl) aCallPeer)
+ .getMediaHandler()
+ .getTransportManager();
+
+ if (aTransportManager
+ .isEstablishingConnectivityWithJitsiVideobridge)
+ {
+ transportManager = aTransportManager;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return transportManager;
+ }
+
+ /**
+ * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content
+ * associated with a specific <tt>MediaType</tt> and is to be either locally
+ * or remotely used.
+ * <p>
+ * <b>Note</b>: Modifications to the <tt>ColibriConferenceIQ.Channel</tt>
+ * instance returned by the method propagate to (the state of) this
+ * instance.
+ * </p>
+ *
+ * @param mediaType the <tt>MediaType</tt> associated with the content which
+ * contains the <tt>ColibriConferenceIQ.Channel</tt> to get
+ * @param local <tt>true</tt> if the <tt>ColibriConferenceIQ.Channel</tt>
+ * which is to be used locally is to be returned or <tt>false</tt> for the
+ * one which is to be used remotely
+ * @return the <tt>ColibriConferenceIQ.Channel</tt> which belongs to a
+ * content associated with the specified <tt>mediaType</tt> and which is to
+ * be used in accord with the specified <tt>local</tt> indicator if such a
+ * channel exists; otherwise, <tt>null</tt>
+ */
+ ColibriConferenceIQ.Channel getColibriChannel(
+ MediaType mediaType,
+ boolean local)
+ {
+ ColibriConferenceIQ.Channel channel = null;
+
+ if (colibri != null)
+ {
+ ColibriConferenceIQ.Content content
+ = colibri.getContent(mediaType.toString());
+
+ if (content != null)
+ {
+ List<ColibriConferenceIQ.Channel> channels
+ = content.getChannels();
+
+ if (channels.size() == 2)
+ channel = channels.get(local ? 0 : 1);
+ }
+ }
+ return channel;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java
index 1285581..778d086 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/DefaultPacketExtensionProvider.java
@@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.protocol.jabber.extensions;
import java.util.logging.*;
+import net.java.sip.communicator.service.protocol.jabber.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.*;
import org.xmlpull.v1.*;
@@ -41,6 +42,13 @@ public class DefaultPacketExtensionProvider<C extends AbstractPacketExtension>
.getLogger(DefaultPacketExtensionProvider.class.getName());
/**
+ * The <tt>AbstractSmackInteroperabilityLayer</tt> instance implementing
+ * necessary methods
+ */
+ private AbstractSmackInteroperabilityLayer smackInteroperabilityLayer =
+ AbstractSmackInteroperabilityLayer.getInstance();
+
+ /**
* The {@link Class} that the packets we will be parsing here belong to.
*/
private final Class<C> packetClass;
@@ -100,8 +108,7 @@ public class DefaultPacketExtensionProvider<C extends AbstractPacketExtension>
if (eventType == XmlPullParser.START_TAG)
{
- PacketExtensionProvider provider
- = (PacketExtensionProvider)ProviderManager.getInstance()
+ PacketExtensionProvider provider = smackInteroperabilityLayer
.getExtensionProvider( elementName, namespace );
if(provider == null)
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java
index ebdcbb0..97d9ff8 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/EntityCapsManager.java
@@ -243,6 +243,7 @@ public class EntityCapsManager
if ((user != null) && (node != null) && (hash != null) && (ver != null))
{
Caps caps = userCaps.get(user);
+ String bareJid=StringUtils.parseBareAddress(user);
if ((caps == null)
|| !caps.node.equals(node)
@@ -270,7 +271,9 @@ public class EntityCapsManager
String nodeVer = caps.getNodeVer();
for (UserCapsNodeListener listener : listeners)
- listener.userCapsNodeAdded(user, nodeVer, online);
+ listener.userCapsNodeAdded(user,
+ getFullJidsByBareJid(bareJid),
+ nodeVer, online);
}
}
}
@@ -305,6 +308,8 @@ public class EntityCapsManager
{
Caps caps = null;
String lastRemovedJid = null;
+ String bareJid=StringUtils.parseBareAddress(
+ contact.getAddress());
Iterator<String> iter = userCaps.keySet().iterator();
while(iter.hasNext())
@@ -337,7 +342,9 @@ public class EntityCapsManager
for (UserCapsNodeListener listener : listeners)
listener.userCapsNodeRemoved(
- lastRemovedJid, nodeVer, false);
+ lastRemovedJid,
+ getFullJidsByBareJid(bareJid),
+ nodeVer, false);
}
}
}
@@ -350,6 +357,7 @@ public class EntityCapsManager
public void removeUserCapsNode(String user)
{
Caps caps = userCaps.remove(user);
+ String bareJid=StringUtils.parseBareAddress(user);
// Fire userCapsNodeRemoved.
if (caps != null)
@@ -367,7 +375,9 @@ public class EntityCapsManager
String nodeVer = caps.getNodeVer();
for (UserCapsNodeListener listener : listeners)
- listener.userCapsNodeRemoved(user, nodeVer, false);
+ listener.userCapsNodeRemoved(user,
+ getFullJidsByBareJid(bareJid),
+ nodeVer, false);
}
}
}
@@ -404,6 +414,24 @@ public class EntityCapsManager
{
return userCaps.get(user);
}
+
+ /**
+ * Gets the full Jids (with resources) as Strings.
+ *
+ * @param the bare Jid
+ * @return the full Jids as an ArrayList <tt>user</tt>
+ */
+ public ArrayList<String> getFullJidsByBareJid(String bareJid)
+ {
+ ArrayList<String> jids = new ArrayList<String>();
+ for(String jid: userCaps.keySet())
+ {
+ if(bareJid.equals(StringUtils.parseBareAddress(jid))){
+ jids.add(jid);
+ }
+ }
+ return jids;
+ }
/**
* Get the discover info given a user name. The discover info is returned if
@@ -605,14 +633,9 @@ public class EntityCapsManager
* @param connection the connection that we'd like this manager to register
* with.
*/
- public void addPacketListener(XMPPConnection connection)
+ public void addPacketListener(Connection connection)
{
- PacketFilter filter
- = new AndFilter(
- new PacketTypeFilter(Presence.class),
- new PacketExtensionFilter(
- CapsPacketExtension.ELEMENT_NAME,
- CapsPacketExtension.NAMESPACE));
+ PacketFilter filter = new PacketTypeFilter(Presence.class);
connection.addPacketListener(new CapsPacketListener(), filter);
}
@@ -913,48 +936,48 @@ public class EntityCapsManager
*/
public void processPacket(Packet packet)
{
+ // Check it the packet indicates that the user is online. We
+ // will use this information to decide if we're going to send
+ // the discover info request.
+ boolean online
+ = (packet instanceof Presence)
+ && ((Presence) packet).isAvailable();
+
CapsPacketExtension ext
= (CapsPacketExtension)
packet.getExtension(
CapsPacketExtension.ELEMENT_NAME,
CapsPacketExtension.NAMESPACE);
- /*
- * Before Version 1.4 of XEP-0115: Entity Capabilities, the 'ver'
- * attribute was generated differently and the 'hash' attribute was
- * absent. The 'ver' attribute in Version 1.3 represents the
- * specific version of the client and thus does not provide a way to
- * validate the DiscoverInfo sent by the client. If
- * EntityCapsManager receives no 'hash' attribute, it will assume
- * the legacy format and will not cache it because the DiscoverInfo
- * to be received from the client later on will not be trustworthy.
- */
- String hash = ext.getHash();
-
- /* Google Talk web does not set hash but we need it to be cached */
- if(hash == null)
- hash = "";
-
- if (hash != null)
+ if(ext != null && online)
{
- // Check it the packet indicates that the user is online. We
- // will use this information to decide if we're going to send
- // the discover info request.
- boolean online
- = (packet instanceof Presence)
- && ((Presence) packet).isAvailable();
-
- if(online)
- {
- addUserCapsNode(
- packet.getFrom(),
- ext.getNode(), hash, ext.getVersion(),
- ext.getExtensions(), online);
- }
- else
- {
- removeUserCapsNode(packet.getFrom());
- }
+ /*
+ * Before Version 1.4 of XEP-0115: Entity Capabilities,
+ * the 'ver' attribute was generated differently and the 'hash'
+ * attribute was absent. The 'ver' attribute in Version 1.3
+ * represents the specific version of the client and thus does
+ * not provide a way to validate the DiscoverInfo sent by
+ * the client. If EntityCapsManager receives no 'hash'
+ * attribute, it will assume the legacy format and will not
+ * cache it because the DiscoverInfo to be received from
+ * the client later on will not be trustworthy.
+ */
+ String hash = ext.getHash();
+
+ /* Google Talk web does not set hash, but we need it to
+ * be cached
+ */
+ if (hash == null)
+ hash = "";
+
+ addUserCapsNode(
+ packet.getFrom(),
+ ext.getNode(), hash, ext.getVersion(),
+ ext.getExtensions(), online);
+ }
+ else if (!online)
+ {
+ removeUserCapsNode(packet.getFrom());
}
}
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java
index 5ee38b0..eda921f 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/caps/UserCapsNodeListener.java
@@ -17,6 +17,8 @@
*/
package net.java.sip.communicator.impl.protocol.jabber.extensions.caps;
+import java.util.ArrayList;
+
/**
* Represents a listener of events notifying about changes in the list of user
* caps nodes of <tt>EntityCapsManager</tt>.
@@ -30,18 +32,22 @@ public interface UserCapsNodeListener
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user for which we're notified is online
*/
- public void userCapsNodeAdded(String user, String node, boolean online);
+ public void userCapsNodeAdded(String user, ArrayList<String> fullJids,
+ String node, boolean online);
/**
* Notifies this listener that an <tt>EntityCapsManager</tt> has removed a
* record for a specific user about the caps node the user has.
*
* @param user the user (full JID)
+ * @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user for which we're notified is online
*/
- public void userCapsNodeRemoved(String user, String node, boolean online);
+ public void userCapsNodeRemoved(String user, ArrayList<String> fullJids,
+ String node, boolean online);
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java
index 547ebe8..48207c2 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriBuilder.java
@@ -37,9 +37,9 @@ import java.util.*;
* Add one or multiple requests of the same type by calling
* {@link #addAllocateChannelsReq(boolean, String, boolean, java.util.List)}}
* or {@link #addExpireChannelsReq(ColibriConferenceIQ)}
- * or {@link #addTransportUpdateReq(boolean, java.util.Map, ColibriConferenceIQ)}
- * or {@link #addBundleTransportUpdateReq(
- * boolean, IceUdpTransportPacketExtension, ColibriConferenceIQ)}.
+ * or {@link #addRtpDescription(Map, ColibriConferenceIQ)}
+ * and {@link #addSSSRCGroupsInfo(Map, ColibriConferenceIQ)}
+ * and {@link #addSSSRCInfo(Map, ColibriConferenceIQ)}.
* </li>
* <li>
* Compile the request by calling {@link #getRequest(String)}. Then send it to
@@ -119,6 +119,20 @@ public class ColibriBuilder
private SimulcastMode simulcastMode;
/**
+ * Specifies the audio packet delay that will be set on all created audio
+ * channels. When set to <tt>null</tt> the builder will clear the attribute
+ * which stands for 'undefined'.
+ **/
+ private Integer audioPacketDelay;
+
+ /**
+ * Channel 'rtp-level-relay-type' option that will be used with all created
+ * audio channel. Possible values: mixer or translator (default).
+ *
+ */
+ private RTPLevelRelayType rtpLevelRelayType;
+
+ /**
* Creates new instance of {@link ColibriBuilder} for given
* <tt>conferenceState</tt>.
*
@@ -161,18 +175,26 @@ public class ColibriBuilder
* @param contents the list of {@link ContentPacketExtension} describing
* channels media.
*
- * @return this instance fo calls chaining purpose.
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
*/
- public ColibriBuilder addAllocateChannelsReq(
- boolean useBundle,
- String endpointName,
- boolean peerIsInitiator,
+ public boolean addAllocateChannelsReq(
+ boolean useBundle,
+ String endpointName,
+ boolean peerIsInitiator,
List<ContentPacketExtension> contents)
{
+ Objects.requireNonNull(endpointName, "endpointName");
+ Objects.requireNonNull(contents, "contents");
+
assertRequestType(RequestType.ALLOCATE_CHANNELS);
request.setType(IQ.Type.GET);
+ boolean hasAnyChanges = false;
+
for (ContentPacketExtension cpe : contents)
{
MediaType mediaType = JingleUtils.getMediaType(cpe);
@@ -193,7 +215,7 @@ public class ColibriBuilder
remoteChannelRequest.setChannelBundleId(endpointName);
}
- if (mediaType != MediaType.DATA)
+ if (remoteChannelRequest instanceof ColibriConferenceIQ.Channel)
{
RtpDescriptionPacketExtension rdpe
= cpe.getFirstChildOfType(
@@ -213,6 +235,13 @@ public class ColibriBuilder
remoteRtpChannelRequest.setAdaptiveLastN(adaptiveLastN);
remoteRtpChannelRequest.setAdaptiveSimulcast(adaptiveSimulcast);
remoteRtpChannelRequest.setSimulcastMode(simulcastMode);
+ if (MediaType.AUDIO.equals(mediaType))
+ {
+ // When audioPacketDelay is null it will clear the attribute
+ remoteRtpChannelRequest.setPacketDelay(audioPacketDelay);
+ // Set rtp packet relay type for this channel
+ remoteRtpChannelRequest.setRTPLevelRelayType(rtpLevelRelayType);
+ }
}
// Copy transport
@@ -221,13 +250,17 @@ public class ColibriBuilder
copyTransportOnChannel(cpe, remoteChannelRequest);
}
- if (mediaType != MediaType.DATA)
+ if (remoteChannelRequest instanceof ColibriConferenceIQ.Channel)
{
+ hasAnyChanges = true;
+
contentRequest.addChannel(
(ColibriConferenceIQ.Channel) remoteChannelRequest);
}
else
{
+ hasAnyChanges = true;
+
contentRequest.addSctpConnection(
(ColibriConferenceIQ.SctpConnection) remoteChannelRequest);
}
@@ -246,6 +279,8 @@ public class ColibriBuilder
IceUdpTransportPacketExtension.class);
if (transport != null)
{
+ hasAnyChanges = true;
+
bundle.setTransport(
IceUdpTransportPacketExtension
.cloneTransportAndCandidates(transport, true));
@@ -254,106 +289,38 @@ public class ColibriBuilder
request.addChannelBundle(bundle);
}
- return this;
- }
-
- /**
- * Adds next ICE transport update request to
- * {@link RequestType#TRANSPORT_UPDATE} query currently being built.
- *
- * @param initiator the value that will be set in 'initiator'
- * attribute({@link ColibriConferenceIQ.Channel#initiator}).
- * @param map the map of content name to transport extensions. Maps
- * transport to media types.
- * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
- * Colibri channels to be updated.
- *
- * @return this instance fo calls chaining purpose.
- */
- public ColibriBuilder addTransportUpdateReq(
- boolean initiator,
- Map<String, IceUdpTransportPacketExtension> map,
- ColibriConferenceIQ localChannelsInfo)
- {
- if (conferenceState == null
- || StringUtils.isNullOrEmpty(conferenceState.getID()))
- {
- // We are not initialized yet
- return null;
- }
-
- assertRequestType(RequestType.TRANSPORT_UPDATE);
-
- request.setType(IQ.Type.SET);
-
- for (Map.Entry<String,IceUdpTransportPacketExtension> e
- : map.entrySet())
- {
- String contentName = e.getKey();
- ColibriConferenceIQ.ChannelCommon channel
- = getColibriChannel(localChannelsInfo, contentName);
-
- if (channel != null)
- {
- IceUdpTransportPacketExtension transport
- = IceUdpTransportPacketExtension
- .cloneTransportAndCandidates(e.getValue(), true);
-
- ColibriConferenceIQ.ChannelCommon channelRequest
- = channel instanceof ColibriConferenceIQ.Channel
- ? new ColibriConferenceIQ.Channel()
- : new ColibriConferenceIQ.SctpConnection();
-
- channelRequest.setID(channel.getID());
- channelRequest.setEndpoint(channel.getEndpoint());
- channelRequest.setInitiator(initiator);
- channelRequest.setTransport(transport);
-
- if (channelRequest instanceof ColibriConferenceIQ.Channel)
- {
- request.getOrCreateContent(contentName)
- .addChannel(
- (ColibriConferenceIQ.Channel) channelRequest);
- }
- else
- {
- request.getOrCreateContent(contentName)
- .addSctpConnection(
- (ColibriConferenceIQ.SctpConnection) channelRequest);
- }
- }
- }
- return this;
+ return hasAnyChanges;
}
/**
- * Adds next request to {@link RequestType#BUNDLE_TRANSPORT_UPDATE} query.
- * @param initiator the value that will be set in 'initiator'
- * attribute({@link ColibriConferenceIQ.Channel#initiator}).
+ * Adds next request to {@link RequestType#CHANNEL_INFO_UPDATE} query.
* @param localChannelsInfo the {@link ColibriConferenceIQ} instance that
* describes the channel for which bundle transport will be updated.
* It should contain the description of only one "channel bundle".
* If it contains more than one then the first one will be used.
- * @return this instance for calls chaining purpose.
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
* @throws IllegalArgumentException if <tt>localChannelsInfo</tt> does not
* describe any channel bundles.
*/
- public ColibriBuilder addBundleTransportUpdateReq(
- boolean initiator,
- IceUdpTransportPacketExtension transport,
- ColibriConferenceIQ localChannelsInfo)
+ public boolean addBundleTransportUpdateReq(
+ IceUdpTransportPacketExtension transport,
+ ColibriConferenceIQ localChannelsInfo)
throws IllegalArgumentException
{
- // FIXME:'initiator' not used on bundle transport update ?
+ Objects.requireNonNull(transport, "transport");
+ Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
- return null;
+ return false;
}
- assertRequestType(RequestType.BUNDLE_TRANSPORT_UPDATE);
+ assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
request.setType(IQ.Type.SET);
@@ -371,7 +338,7 @@ public class ColibriBuilder
else
{
throw new IllegalArgumentException(
- "Expected ChannelBundle as not found");
+ "Expected ChannelBundle as not found");
}
ColibriConferenceIQ.ChannelBundle bundleUpdate
@@ -387,7 +354,7 @@ public class ColibriBuilder
request.addChannelBundle(bundleUpdate);
- return this;
+ return true;
}
/**
@@ -395,15 +362,20 @@ public class ColibriBuilder
* {@link RequestType#EXPIRE_CHANNELS} query currently being built.
* @param channelInfo the {@link ColibriConferenceIQ} instance that contains
* info about the channels to be expired.
- * @return this instance for the purpose of calls chaining.
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
*/
- public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo)
+ public boolean addExpireChannelsReq(ColibriConferenceIQ channelInfo)
{
+ Objects.requireNonNull(channelInfo, "channelInfo");
+
// Formulate the ColibriConferenceIQ request which is to be sent.
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
- return null;
+ return false;
}
assertRequestType(RequestType.EXPIRE_CHANNELS);
@@ -411,7 +383,7 @@ public class ColibriBuilder
request.setType(IQ.Type.SET);
for (ColibriConferenceIQ.Content expiredContent
- : channelInfo.getContents())
+ : channelInfo.getContents())
{
ColibriConferenceIQ.Content stateContent
= conferenceState.getContent(expiredContent.getName());
@@ -420,7 +392,7 @@ public class ColibriBuilder
{
ColibriConferenceIQ.Content requestContent
= request.getOrCreateContent(
- stateContent.getName());
+ stateContent.getName());
for (ColibriConferenceIQ.Channel expiredChannel
: expiredContent.getChannels())
@@ -490,7 +462,7 @@ public class ColibriBuilder
*/
/*if (stateContent.getChannelCount() == 1)
{
- stateChannel = stateContent.getChannel(0);
+ stateChannel = stateContent.getRtpChannel(0);
ColibriConferenceIQ.Channel channelRequest
= new ColibriConferenceIQ.Channel();
@@ -537,7 +509,294 @@ public class ColibriBuilder
}
}
- return this;
+ return hasAnyChannelsToExpire;
+ }
+
+ /**
+ * Adds next payload type information update request to
+ * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
+ *
+ * @param map the map of content name to RTP description packet extension.
+ * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
+ * Colibri channels to be updated.
+ *
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
+ */
+ public boolean addRtpDescription(
+ Map<String, RtpDescriptionPacketExtension> map,
+ ColibriConferenceIQ localChannelsInfo)
+ {
+ Objects.requireNonNull(map, "map");
+ Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
+
+ if (conferenceState == null
+ || StringUtils.isNullOrEmpty(conferenceState.getID()))
+ {
+ // We are not initialized yet
+ return false;
+ }
+
+ assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
+
+ request.setType(IQ.Type.SET);
+
+ boolean anyUpdates = false;
+
+ for (Map.Entry<String, RtpDescriptionPacketExtension> e
+ : map.entrySet())
+ {
+ String contentName = e.getKey();
+ ColibriConferenceIQ.ChannelCommon channel
+ = getColibriChannel(localChannelsInfo, contentName);
+
+ if (channel != null
+ && channel instanceof ColibriConferenceIQ.Channel)
+ {
+ RtpDescriptionPacketExtension rtpPE = e.getValue();
+ if (rtpPE == null)
+ {
+ continue;
+ }
+
+ List<PayloadTypePacketExtension> pts = rtpPE.getPayloadTypes();
+ if (pts == null || pts.isEmpty())
+ {
+ continue;
+ }
+
+ anyUpdates = true;
+
+ ColibriConferenceIQ.Channel channelRequest
+ = (ColibriConferenceIQ.Channel) getRequestChannel(
+ request.getOrCreateContent(contentName),
+ channel);
+ if (channelRequest == null)
+ {
+ channelRequest = new ColibriConferenceIQ.Channel();
+ channelRequest.setID(channel.getID());
+ }
+
+ for (PayloadTypePacketExtension ptPE : pts)
+ {
+ channelRequest.addPayloadType(ptPE);
+ }
+ }
+ }
+
+ return anyUpdates;
+ }
+
+ /**
+ * Adds next SSRC information update request to
+ * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
+ *
+ * @param ssrcMap the map of content name to the list of
+ * <tt>SourcePacketExtension</tt>.
+ * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
+ * Colibri channels to be updated.
+ *
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
+ */
+ public boolean addSSSRCInfo(
+ Map<String, List<SourcePacketExtension>> ssrcMap,
+ ColibriConferenceIQ localChannelsInfo)
+ {
+ Objects.requireNonNull(ssrcMap, "ssrcMap");
+ Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
+
+ if (conferenceState == null
+ || StringUtils.isNullOrEmpty(conferenceState.getID()))
+ {
+ // We are not initialized yet
+ return false;
+ }
+
+ assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
+
+ request.setType(IQ.Type.SET);
+
+ boolean anyUpdates = false;
+
+ // Go over SSRCs
+ for (String contentName : ssrcMap.keySet())
+ {
+ // Get channel from local channel info
+ ColibriConferenceIQ.ChannelCommon rtpChanel
+ = getRtpChannel(localChannelsInfo, contentName);
+ if (rtpChanel == null)
+ {
+ // There's no channel for this content name in localChannelsInfo
+ continue;
+ }
+
+ anyUpdates = true;
+
+ // Ok we have channel for this content, let's add SSRCs
+ ColibriConferenceIQ.Channel reqChannel
+ = (ColibriConferenceIQ.Channel) getRequestChannel(
+ request.getOrCreateContent(contentName), rtpChanel);
+
+ for (SourcePacketExtension ssrc : ssrcMap.get(contentName))
+ {
+ reqChannel.addSource(ssrc.copy());
+ }
+
+ if (reqChannel.getSources() == null
+ || reqChannel.getSources().isEmpty())
+ {
+ // Put an empty source to remove all sources
+ SourcePacketExtension emptySource = new SourcePacketExtension();
+ emptySource.setSSRC(-1L);
+ reqChannel.addSource(emptySource);
+ }
+ }
+
+ return anyUpdates;
+ }
+
+ /**
+ * Adds next SSRC group information update request to
+ * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
+ *
+ * @param ssrcGroupMap the map of content name to the list of
+ * <tt>SourceGroupPacketExtension</tt>.
+ * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
+ * Colibri channels to be updated.
+ *
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
+ */
+ public boolean addSSSRCGroupsInfo(
+ Map<String, List<SourceGroupPacketExtension>> ssrcGroupMap,
+ ColibriConferenceIQ localChannelsInfo)
+ {
+ Objects.requireNonNull(ssrcGroupMap, "ssrcGroupMap");
+ Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
+
+ if (conferenceState == null
+ || StringUtils.isNullOrEmpty(conferenceState.getID()))
+ {
+ // We are not initialized yet
+ return false;
+ }
+
+ assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
+
+ request.setType(IQ.Type.SET);
+
+ boolean anyUpdates = false;
+
+ // Go over SSRC groups
+ for (String contentName : ssrcGroupMap.keySet())
+ {
+ // Get channel from local channel info
+ ColibriConferenceIQ.Channel rtpChannel
+ = getRtpChannel(localChannelsInfo, contentName);
+ if (rtpChannel == null)
+ {
+ // There's no channel for this content name in localChannelsInfo
+ continue;
+ }
+
+ List<SourceGroupPacketExtension> groups
+ = ssrcGroupMap.get(contentName);
+
+ // Ok we have channel for this content, let's add SSRCs
+ ColibriConferenceIQ.Channel reqChannel
+ = (ColibriConferenceIQ.Channel) getRequestChannel(
+ request.getOrCreateContent(contentName), rtpChannel);
+
+ if (groups.isEmpty() && "video".equalsIgnoreCase(contentName))
+ {
+ anyUpdates = true;
+
+ // Put empty source group to turn off simulcast layers
+ reqChannel.addSourceGroup(
+ SourceGroupPacketExtension.createSimulcastGroup());
+ }
+
+ for (SourceGroupPacketExtension group : groups)
+ {
+ anyUpdates = true;
+
+ reqChannel.addSourceGroup(group);
+ }
+ }
+
+ return anyUpdates;
+ }
+
+ /**
+ * Adds next ICE transport update request to
+ * {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
+ *
+ * @param map the map of content name to transport extensions. Maps
+ * transport to media types.
+ * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
+ * Colibri channels to be updated.
+ *
+ * @return <tt>true</tt> if the request yields any changes in Colibri
+ * channels state on the bridge or <tt>false</tt> otherwise.
+ * In general when <tt>false</tt> is returned for all
+ * combined requests it makes no sense to send it.
+ */
+ public boolean addTransportUpdateReq(
+ Map<String, IceUdpTransportPacketExtension> map,
+ ColibriConferenceIQ localChannelsInfo)
+ {
+ Objects.requireNonNull(map, "map");
+ Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
+
+ if (conferenceState == null
+ || StringUtils.isNullOrEmpty(conferenceState.getID()))
+ {
+ // We are not initialized yet
+ return false;
+ }
+
+ boolean hasAnyChanges = false;
+
+ assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
+
+ request.setType(IQ.Type.SET);
+
+ for (Map.Entry<String,IceUdpTransportPacketExtension> e
+ : map.entrySet())
+ {
+ String contentName = e.getKey();
+ ColibriConferenceIQ.ChannelCommon channel
+ = getColibriChannel(localChannelsInfo, contentName);
+
+ if (channel != null)
+ {
+ IceUdpTransportPacketExtension transport
+ = IceUdpTransportPacketExtension
+ .cloneTransportAndCandidates(e.getValue(), true);
+
+ ColibriConferenceIQ.ChannelCommon channelRequest
+ = channel instanceof ColibriConferenceIQ.Channel
+ ? new ColibriConferenceIQ.Channel()
+ : new ColibriConferenceIQ.SctpConnection();
+
+ channelRequest.setID(channel.getID());
+ channelRequest.setEndpoint(channel.getEndpoint());
+ channelRequest.setTransport(transport);
+
+ request.getOrCreateContent(contentName)
+ .addChannelCommon(channelRequest);
+
+ hasAnyChanges = true;
+ }
+ }
+ return hasAnyChanges;
}
/**
@@ -583,10 +842,12 @@ public class ColibriBuilder
request.setTo(videobridge);
- if (requestType == RequestType.EXPIRE_CHANNELS
- && !hasAnyChannelsToExpire)
+ if (requestType == RequestType.EXPIRE_CHANNELS)
{
- return null;
+ if (!hasAnyChannelsToExpire)
+ return null;
+
+ hasAnyChannelsToExpire = false;
}
return request;
@@ -720,6 +981,28 @@ public class ColibriBuilder
}
/**
+ * Returns an <tt>Integer</tt> which stands for the audio packet delay
+ * that will be set on all created audio channels or <tt>null</tt> if
+ * the builder should leave not include the XML attribute at all.
+ */
+ public Integer getAudioPacketDelay()
+ {
+ return audioPacketDelay;
+ }
+
+ /**
+ * Configures audio channels packet delay.
+ * @param audioPacketDelay an <tt>Integer</tt> value which stands for
+ * the audio packet delay that will be set on all created audio channels or
+ * <tt>null</tt> if the builder should not set that channel property to any
+ * value.
+ */
+ public void setAudioPacketDelay(Integer audioPacketDelay)
+ {
+ this.audioPacketDelay = audioPacketDelay;
+ }
+
+ /**
* Sets channel 'simulcast-mode' option that will be added to the
* request when channels are created.
* @param simulcastMode a <tt>SimulcastMode</tt> value to specify
@@ -732,69 +1015,64 @@ public class ColibriBuilder
}
/**
- * Adds next payload type information update request to
- * {@link RequestType#RTP_DESCRIPTION_UPDATE} query currently being built.
+ * Creates a new instance of <tt>localChannelInfo</tt> and initializes only
+ * the fields required to identify particular Colibri channel on the bridge.
+ * This instance is meant to be used in Colibri
+ * {@link RequestType#CHANNEL_INFO_UPDATE} requests. This instance is also
+ * added to given <tt>requestContent</tt> which used to construct current
+ * request.
*
- * @param map the map of content name to RTP description packet extension.
- * @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
- * Colibri channels to be updated.
+ * @param requestContent <tt>Content</tt> of Colibri update request to which
+ * new instance wil be automatically added after has been created.
+ * @param localChannelInfo the original channel for which "update request"
+ * equivalent is to be created with this call.
*
- * @return this instance for calls chaining purpose.
+ * @return new instance of <tt>localChannelInfo</tt> and initialized with
+ * only those fields required to identify particular Colibri channel on
+ * the bridge.
*/
- public ColibriBuilder addRtpDescription(
- Map<String, RtpDescriptionPacketExtension> map,
- ColibriConferenceIQ localChannelsInfo) {
-
- if (conferenceState == null
- || StringUtils.isNullOrEmpty(conferenceState.getID()))
- {
- // We are not initialized yet
- return null;
- }
-
- assertRequestType(RequestType.RTP_DESCRIPTION_UPDATE);
-
- request.setType(IQ.Type.SET);
-
- for (Map.Entry<String, RtpDescriptionPacketExtension> e
- : map.entrySet())
+ private ColibriConferenceIQ.ChannelCommon getRequestChannel(
+ ColibriConferenceIQ.Content requestContent,
+ ColibriConferenceIQ.ChannelCommon localChannelInfo)
+ {
+ ColibriConferenceIQ.ChannelCommon reqChannel
+ = requestContent.getChannel(localChannelInfo.getID());
+ if (reqChannel == null)
{
- String contentName = e.getKey();
- ColibriConferenceIQ.ChannelCommon channel
- = getColibriChannel(localChannelsInfo, contentName);
-
- if (channel != null
- && channel instanceof ColibriConferenceIQ.Channel)
+ if (localChannelInfo instanceof ColibriConferenceIQ.Channel)
{
- RtpDescriptionPacketExtension rtpPE = e.getValue();
- if (rtpPE == null)
- {
- continue;
- }
-
- List<PayloadTypePacketExtension> pts = rtpPE.getPayloadTypes();
- if (pts == null || pts.isEmpty())
- {
- continue;
- }
-
- ColibriConferenceIQ.Channel channelRequest
- = new ColibriConferenceIQ.Channel();
+ reqChannel = new ColibriConferenceIQ.Channel();
+ }
+ else if (
+ localChannelInfo instanceof ColibriConferenceIQ.SctpConnection)
+ {
+ reqChannel = new ColibriConferenceIQ.SctpConnection();
+ }
+ else
+ {
+ throw new RuntimeException(
+ "Unsupported ChannelCommon class: "
+ + localChannelInfo.getClass());
+ }
- channelRequest.setID(channel.getID());
+ reqChannel.setID(localChannelInfo.getID());
- for (PayloadTypePacketExtension ptPE : rtpPE.getPayloadTypes())
- {
- channelRequest.addPayloadType(ptPE);
- }
+ requestContent.addChannelCommon(reqChannel);
+ }
+ return reqChannel;
+ }
- request.getOrCreateContent(contentName)
- .addChannel(channelRequest);
- }
+ private ColibriConferenceIQ.Channel getRtpChannel(
+ ColibriConferenceIQ localChannelsInfo,
+ String contentName)
+ {
+ ColibriConferenceIQ.Content content
+ = localChannelsInfo.getContent(contentName);
- }
+ if (content == null)
+ return null;
- return this;
+ return content.getChannelCount() > 0 ? content.getChannel(0) : null;
}
/**
@@ -808,19 +1086,10 @@ public class ColibriBuilder
ALLOCATE_CHANNELS,
/**
- * Updates transport information for channels that use RTP bundle.
- */
- BUNDLE_TRANSPORT_UPDATE,
-
- /**
- * Updates channel transport information(ICE transport candidates).
+ * An update request which is meant to modify some values of existing
+ * Colibri channels on the bridge.
*/
- TRANSPORT_UPDATE,
-
- /**
- * Updates the RTP description of a channel (payload types).
- */
- RTP_DESCRIPTION_UPDATE,
+ CHANNEL_INFO_UPDATE,
/**
* Expires specified Colibri channels.
@@ -833,4 +1102,28 @@ public class ColibriBuilder
*/
UNDEFINED;
}
+
+ /**
+ * Configures RTP-level relay (RFC 3550, section 2.3).
+ * @param rtpLevelRelayType an <tt>RTPLevelRelayType</tt> value which
+ * stands for the rtp level relay type that will be set on all created
+ * audio channels.
+ */
+ public void setRTPLevelRelayType(RTPLevelRelayType rtpLevelRelayType)
+ {
+ this.rtpLevelRelayType = rtpLevelRelayType;
+ }
+
+ /**
+ * Configures RTP-level relay (RFC 3550, section 2.3).
+ * @param rtpLevelRelayType a <tt>String</tt> value which
+ * stands for the rtp level relay type that will be set on all created
+ * audio channels.
+ */
+ public void setRTPLevelRelayType(String rtpLevelRelayType)
+ {
+ setRTPLevelRelayType
+ (RTPLevelRelayType.parseRTPLevelRelayType(rtpLevelRelayType));
+ }
+
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java
index 52368bf..d5cf175 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriConferenceIQ.java
@@ -161,7 +161,7 @@ public class ColibriConferenceIQ
throw new NullPointerException("channelBundle");
return
- channelBundles.contains(channelBundles)
+ channelBundles.contains(channelBundle)
? false
: channelBundles.add(channelBundle);
}
@@ -543,6 +543,14 @@ public class ColibriConferenceIQ
= "receive-simulcast-layer";
/**
+ * The XML name of the <tt>packet-delay</tt> attribute of
+ * a <tt>channel</tt> of a <tt>content</tt> of a <tt>conference</tt> IQ
+ * which represents the value of the {@link #packetDelay} property of
+ * <tt>ColibriConferenceIQ.Channel</tt>.
+ */
+ public static final String PACKET_DELAY_ATTR_NAME = "packet-delay";
+
+ /**
* The XML name of the <tt>rtcpport</tt> attribute of a <tt>channel</tt>
* of a <tt>content</tt> of a <tt>conference</tt> IQ which represents
* the value of the <tt>rtcpPort</tt> property of
@@ -613,6 +621,11 @@ public class ColibriConferenceIQ
private SimulcastMode simulcastMode;
/**
+ * The amount of delay added to the RTP stream in a number of packets.
+ */
+ private Integer packetDelay;
+
+ /**
* The <tt>payload-type</tt> elements defined by XEP-0167: Jingle RTP
* Sessions associated with this <tt>channel</tt>.
*/
@@ -893,6 +906,18 @@ public class ColibriConferenceIQ
}
/**
+ * Returns an <tt>Integer</tt> which stands for the amount of delay
+ * added to the RTP stream in a number of packets.
+ *
+ * @return <tt>Integer</tt> with the value or <tt>null</tt> if
+ * unspecified.
+ */
+ public Integer getPacketDelay()
+ {
+ return packetDelay;
+ }
+
+ /**
* Gets a list of <tt>payload-type</tt> elements defined by XEP-0167:
* Jingle RTP Sessions added to this <tt>channel</tt>.
*
@@ -1077,8 +1102,16 @@ public class ColibriConferenceIQ
if (adaptiveSimulcast != null)
{
- xml.append(' ').append(adaptiveSimulcast).append("='")
- .append(adaptiveSimulcast).append('\'');
+ xml.append(' ').append(ADAPTIVE_SIMULCAST_ATTR_NAME)
+ .append("='").append(adaptiveSimulcast).append('\'');
+ }
+
+ // packet-delay
+ Integer packetDelay = getPacketDelay();
+ if (packetDelay != null)
+ {
+ xml.append(' ').append(PACKET_DELAY_ATTR_NAME).append("='")
+ .append(packetDelay).append('\'');
}
// simulcastMode
@@ -1314,6 +1347,17 @@ public class ColibriConferenceIQ
}
/**
+ * Configures channel's packet delay which tells by how many packets
+ * the RTP streams will be delayed.
+ * @param packetDelay an <tt>Integer</tt> value which stands for
+ * the packet delay that will be set or <tt>null</tt> to leave undefined
+ */
+ public void setPacketDelay(Integer packetDelay)
+ {
+ this.packetDelay = packetDelay;
+ }
+
+ /**
* Sets the value of the 'simulcast-mode' flag.
* @param simulcastMode the value to set.
*/
@@ -1925,6 +1969,25 @@ public class ColibriConferenceIQ
}
/**
+ * Adds <tt>ChannelCommon</tt> to this <tt>Content</tt>.
+ * @param channelCommon {@link ChannelCommon} instance to be added to
+ * this content.
+ * @return <tt>true</tt> if given <tt>channelCommon</tt> has been
+ * actually added to this <tt>Content</tt> instance.
+ */
+ public boolean addChannelCommon(ChannelCommon channelCommon)
+ {
+ if (channelCommon instanceof Channel)
+ {
+ return addChannel((Channel) channelCommon);
+ }
+ else
+ {
+ return addSctpConnection((SctpConnection) channelCommon);
+ }
+ }
+
+ /**
* Adds a specific <tt>SctpConnection</tt> to the list of
* <tt>SctpConnection</tt>s included into this <tt>Content</tt>.
*
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java
index 39e299f..ce676ef 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriIQProvider.java
@@ -20,6 +20,7 @@ package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri;
import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.service.protocol.jabber.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.*;
import org.jivesoftware.smack.packet.*;
@@ -36,89 +37,101 @@ import org.xmlpull.v1.*;
public class ColibriIQProvider
implements IQProvider
{
+
+ /**
+ * Smack interoperation layer
+ */
+ private AbstractSmackInteroperabilityLayer smackInteroperabilityLayer =
+ AbstractSmackInteroperabilityLayer.getInstance();
+
/** Initializes a new <tt>ColibriIQProvider</tt> instance. */
public ColibriIQProvider()
{
- ProviderManager providerManager = ProviderManager.getInstance();
-
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
PayloadTypePacketExtension.ELEMENT_NAME,
ColibriConferenceIQ.NAMESPACE,
new DefaultPacketExtensionProvider<PayloadTypePacketExtension>(
PayloadTypePacketExtension.class));
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
RtcpFbPacketExtension.ELEMENT_NAME,
RtcpFbPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<RtcpFbPacketExtension>(
RtcpFbPacketExtension.class));
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
RTPHdrExtPacketExtension.ELEMENT_NAME,
ColibriConferenceIQ.NAMESPACE,
new DefaultPacketExtensionProvider<RTPHdrExtPacketExtension>(
RTPHdrExtPacketExtension.class));
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
SourcePacketExtension.ELEMENT_NAME,
SourcePacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<SourcePacketExtension>(
SourcePacketExtension.class));
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
SourceGroupPacketExtension.ELEMENT_NAME,
SourceGroupPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<SourceGroupPacketExtension>(
SourceGroupPacketExtension.class));
PacketExtensionProvider parameterProvider
- = new DefaultPacketExtensionProvider<ParameterPacketExtension>(
- ParameterPacketExtension.class);
+ = new DefaultPacketExtensionProvider<ParameterPacketExtension>(
+ ParameterPacketExtension.class);
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
ParameterPacketExtension.ELEMENT_NAME,
ColibriConferenceIQ.NAMESPACE,
parameterProvider);
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
ParameterPacketExtension.ELEMENT_NAME,
SourcePacketExtension.NAMESPACE,
parameterProvider);
// Shutdown IQ
- providerManager.addIQProvider(
- GracefulShutdownIQ.ELEMENT_NAME,
- GracefulShutdownIQ.NAMESPACE,
+ smackInteroperabilityLayer.addIQProvider(
+ ShutdownIQ.GRACEFUL_ELEMENT_NAME,
+ ShutdownIQ.NAMESPACE,
+ this);
+ smackInteroperabilityLayer.addIQProvider(
+ ShutdownIQ.FORCE_ELEMENT_NAME,
+ ShutdownIQ.NAMESPACE,
this);
// Shutdown extension
PacketExtensionProvider shutdownProvider
- = new DefaultPacketExtensionProvider
- <ColibriConferenceIQ.GracefulShutdown>(
- ColibriConferenceIQ.GracefulShutdown.class);
+ = new DefaultPacketExtensionProvider
+ <ColibriConferenceIQ.GracefulShutdown>(
+ ColibriConferenceIQ.GracefulShutdown.class);
- providerManager.addExtensionProvider(
- ColibriConferenceIQ.GracefulShutdown.ELEMENT_NAME,
- ColibriConferenceIQ.GracefulShutdown.NAMESPACE,
- shutdownProvider);
+ smackInteroperabilityLayer.addExtensionProvider(
+ ColibriConferenceIQ.GracefulShutdown.ELEMENT_NAME,
+ ColibriConferenceIQ.GracefulShutdown.NAMESPACE,
+ shutdownProvider);
// ColibriStatsIQ
- providerManager.addIQProvider(
- ColibriStatsIQ.ELEMENT_NAME,
- ColibriStatsIQ.NAMESPACE,
- this);
+ smackInteroperabilityLayer.addIQProvider(
+ ColibriStatsIQ.ELEMENT_NAME,
+ ColibriStatsIQ.NAMESPACE,
+ this);
// ColibriStatsExtension
PacketExtensionProvider statsProvider
- = new DefaultPacketExtensionProvider<ColibriStatsExtension>(
- ColibriStatsExtension.class);
+ = new DefaultPacketExtensionProvider<ColibriStatsExtension>(
+ ColibriStatsExtension.class);
- providerManager.addExtensionProvider(
- ColibriStatsExtension.ELEMENT_NAME,
- ColibriStatsExtension.NAMESPACE,
- statsProvider);
+ smackInteroperabilityLayer.addExtensionProvider(
+ ColibriStatsExtension.ELEMENT_NAME,
+ ColibriStatsExtension.NAMESPACE,
+ statsProvider);
// ColibriStatsExtension.Stat
PacketExtensionProvider statProvider
- = new DefaultPacketExtensionProvider<ColibriStatsExtension.Stat>(
- ColibriStatsExtension.Stat.class);
-
- providerManager.addExtensionProvider(
- ColibriStatsExtension.Stat.ELEMENT_NAME,
- ColibriStatsExtension.NAMESPACE,
- statProvider);
+ = new DefaultPacketExtensionProvider
+ <ColibriStatsExtension.Stat>(
+ ColibriStatsExtension.Stat.class);
+
+ smackInteroperabilityLayer.addExtensionProvider(
+ ColibriStatsExtension.Stat.ELEMENT_NAME,
+ ColibriStatsExtension.NAMESPACE,
+ statProvider);
+
+
}
private void addChildExtension(
@@ -199,8 +212,7 @@ public class ColibriIQProvider
throws Exception
{
PacketExtensionProvider extensionProvider
- = (PacketExtensionProvider)
- ProviderManager.getInstance().getExtensionProvider(
+ = smackInteroperabilityLayer.getExtensionProvider(
name,
namespace);
PacketExtension extension;
@@ -416,6 +428,15 @@ public class ColibriIQProvider
if ((expire != null) && (expire.length() != 0))
channel.setExpire(Integer.parseInt(expire));
+ String packetDelay
+ = parser.getAttributeValue(
+ "",
+ ColibriConferenceIQ.Channel
+ .PACKET_DELAY_ATTR_NAME);
+ if (!StringUtils.isNullOrEmpty(packetDelay))
+ channel.setPacketDelay(
+ Integer.parseInt(packetDelay));
+
// host
String host
= parser.getAttributeValue(
@@ -464,6 +485,18 @@ public class ColibriIQProvider
channel.setAdaptiveLastN(
Boolean.parseBoolean(adaptiveLastN));
+ String adaptiveSimulcast
+ = parser.getAttributeValue(
+ "",
+ ColibriConferenceIQ.Channel
+ .ADAPTIVE_SIMULCAST_ATTR_NAME);
+
+ if (!StringUtils.isNullOrEmpty(adaptiveSimulcast))
+ {
+ channel.setAdaptiveSimulcast(
+ Boolean.parseBoolean(adaptiveSimulcast));
+ }
+
// simulcastMode
String simulcastMode
= parser.getAttributeValue(
@@ -802,12 +835,12 @@ public class ColibriIQProvider
iq = conference;
}
- else if (GracefulShutdownIQ.ELEMENT_NAME.equals(parser.getName())
- && GracefulShutdownIQ.NAMESPACE.equals(namespace))
+ else if (ShutdownIQ.NAMESPACE.equals(namespace) &&
+ ShutdownIQ.isValidElementName(parser.getName()))
{
String rootElement = parser.getName();
- iq = new GracefulShutdownIQ();
+ iq = ShutdownIQ.createShutdownIQ(rootElement);
boolean done = false;
@@ -825,12 +858,6 @@ public class ColibriIQProvider
}
break;
}
-
- case XmlPullParser.TEXT:
- {
- // Parse some text here
- break;
- }
}
}
}
@@ -891,12 +918,6 @@ public class ColibriIQProvider
}
break;
}
-
- case XmlPullParser.TEXT:
- {
- // Parse some text here
- break;
- }
}
}
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java
index d5a6ce1..ef80392 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,74 +15,74 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri;
-
-import org.jitsi.service.neomedia.*;
-
-/**
- * Implements a <tt>StreamConnector</tt> which allows sharing a specific
- * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s
- * for the purposes of the Jitsi Videobridge.
- *
- * @author Lyubomir Marinov
- */
-public class ColibriStreamConnector
- extends StreamConnectorDelegate<StreamConnector>
-{
- /**
- * Initializes a new <tt>ColibriStreamConnector</tt> instance which is to
- * share a specific <tt>StreamConnector</tt> instance among multiple
- * <tt>TransportManager</tt>s for the purposes of the Jitsi Videobridge.
- *
- * @param streamConnector the <tt>StreamConnector</tt> instance to be shared
- * by the new instance among multiple <tt>TransportManager</tt>s for the
- * purposes of the Jitsi Videobridge
- */
- public ColibriStreamConnector(StreamConnector streamConnector)
- {
- super(streamConnector);
- }
-
- /**
- * {@inheritDoc}
- *
- * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the
- * closing of the <tt>StreamConnector</tt> wrapped by this instance because
- * the latter is shared and it is not clear whether no
- * <tt>TransportManager</tt> is using it.
- */
- @Override
- public void close()
- {
- /*
- * Do not close the shared StreamConnector because it is not clear
- * whether no TransportManager is using it.
- */
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #close()} on this instance when it is clear that no
- * <tt>TransportManager</tt> is using it in order to release the resources
- * allocated by this instance throughout its life time (that need explicit
- * disposal).
- */
- @Override
- protected void finalize()
- throws Throwable
- {
- try
- {
- /*
- * Close the shared StreamConnector because it is clear that no
- * TrasportManager is using it.
- */
- super.close();
- }
- finally
- {
- super.finalize();
- }
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri;
+
+import org.jitsi.service.neomedia.*;
+
+/**
+ * Implements a <tt>StreamConnector</tt> which allows sharing a specific
+ * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s
+ * for the purposes of the Jitsi Videobridge.
+ *
+ * @author Lyubomir Marinov
+ */
+public class ColibriStreamConnector
+ extends StreamConnectorDelegate<StreamConnector>
+{
+ /**
+ * Initializes a new <tt>ColibriStreamConnector</tt> instance which is to
+ * share a specific <tt>StreamConnector</tt> instance among multiple
+ * <tt>TransportManager</tt>s for the purposes of the Jitsi Videobridge.
+ *
+ * @param streamConnector the <tt>StreamConnector</tt> instance to be shared
+ * by the new instance among multiple <tt>TransportManager</tt>s for the
+ * purposes of the Jitsi Videobridge
+ */
+ public ColibriStreamConnector(StreamConnector streamConnector)
+ {
+ super(streamConnector);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the
+ * closing of the <tt>StreamConnector</tt> wrapped by this instance because
+ * the latter is shared and it is not clear whether no
+ * <tt>TransportManager</tt> is using it.
+ */
+ @Override
+ public void close()
+ {
+ /*
+ * Do not close the shared StreamConnector because it is not clear
+ * whether no TransportManager is using it.
+ */
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #close()} on this instance when it is clear that no
+ * <tt>TransportManager</tt> is using it in order to release the resources
+ * allocated by this instance throughout its life time (that need explicit
+ * disposal).
+ */
+ @Override
+ protected void finalize()
+ throws Throwable
+ {
+ try
+ {
+ /*
+ * Close the shared StreamConnector because it is clear that no
+ * TrasportManager is using it.
+ */
+ super.close();
+ }
+ finally
+ {
+ super.finalize();
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java
new file mode 100644
index 0000000..4df251b
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ShutdownIQ.java
@@ -0,0 +1,134 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri;
+
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * The IQ used to trigger the graceful shutdown mode of the videobridge or force
+ * shutdown the one which receives the stanza(given that source JID is
+ * authorized to do so).
+ *
+ * @author Pawel Domas
+ */
+public class ShutdownIQ
+ extends IQ
+{
+ /**
+ * XML namespace name for shutdown IQs.
+ */
+ final static public String NAMESPACE = ColibriConferenceIQ.NAMESPACE;
+
+ /**
+ * Force shutdown IQ element name.
+ */
+ final static public String FORCE_ELEMENT_NAME = "force-shutdown";
+
+ /**
+ * Graceful shutdown IQ element name.
+ */
+ final static public String GRACEFUL_ELEMENT_NAME = "graceful-shutdown";
+
+ /**
+ * The element name of this IQ. Either {@link #FORCE_ELEMENT_NAME} or
+ * {@link #GRACEFUL_ELEMENT_NAME}.
+ */
+ private final String elementName;
+
+ /**
+ * Checks if given element is a valid one for <tt>ShutdownIQ</tt>.
+ *
+ * @param elementName the name if XML element name inside of the IQ.
+ *
+ * @return <tt>true</tt> if given <tt>elementName</tt> is correct for
+ * <tt>ShutdownIQ</tt>.
+ */
+ public static boolean isValidElementName(String elementName)
+ {
+ return GRACEFUL_ELEMENT_NAME.equals(elementName)
+ || FORCE_ELEMENT_NAME.equals(elementName);
+ }
+
+ /**
+ * Creates shutdown IQ for given element name.
+ *
+ * @param elementName can be {@link #FORCE_ELEMENT_NAME} or
+ * {@link #GRACEFUL_ELEMENT_NAME}
+ *
+ * @return new <tt>ShutdownIQ</tt> instance for given element name.
+ *
+ * @throws IllegalArgumentException if given element name is neither
+ * {@link #FORCE_ELEMENT_NAME} nor {@link #GRACEFUL_ELEMENT_NAME}.
+ */
+ public static ShutdownIQ createShutdownIQ(String elementName)
+ {
+ if (!isValidElementName(elementName))
+ {
+ throw new IllegalArgumentException(
+ "Invalid element name: " + elementName);
+ }
+
+ if (GRACEFUL_ELEMENT_NAME.equals(elementName))
+ {
+ return createGracefulShutdownIQ();
+ }
+ else
+ {
+ return createForceShutdownIQ();
+ }
+ }
+
+ /**
+ * Creates and returns new instance of graceful shutdown IQ.
+ */
+ public static ShutdownIQ createGracefulShutdownIQ()
+ {
+ return new ShutdownIQ(GRACEFUL_ELEMENT_NAME);
+ }
+
+ /**
+ * Creates and returns new instance of force shutdown IQ.
+ */
+ public static ShutdownIQ createForceShutdownIQ()
+ {
+ return new ShutdownIQ(FORCE_ELEMENT_NAME);
+ }
+
+ private ShutdownIQ(String elementName)
+ {
+ this.elementName = elementName;
+ }
+
+ /**
+ * Returns <tt>true</tt> if this IQ instance is a "graceful shutdown" one.
+ * Otherwise it is a force shutdown IQ.
+ */
+ public boolean isGracefulShutdown()
+ {
+ return elementName.equals(GRACEFUL_ELEMENT_NAME);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getChildElementXML()
+ {
+ return "<" + elementName + " xmlns='" + NAMESPACE + "' />";
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/GracefulShutdownIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQ.java
index 9808b11..661bb1b 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/GracefulShutdownIQ.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQ.java
@@ -15,23 +15,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber.extensions.colibri;
+package net.java.sip.communicator.impl.protocol.jabber.extensions.health;
import org.jivesoftware.smack.packet.*;
/**
- * The IQ used to trigger the graceful shutdown mode of the videobridge which
- * receives the stanza(given that source JID is authorized to start it).
+ * The health check IQ used to trigger health checks on the Jitsi Videobridge.
*
* @author Pawel Domas
*/
-public class GracefulShutdownIQ
+public class HealthCheckIQ
extends IQ
{
- public static final String NAMESPACE = ColibriConferenceIQ.NAMESPACE;
+ /**
+ * Health check IQ element name.
+ */
+ final static public String ELEMENT_NAME = "healthcheck";
- public static final String ELEMENT_NAME = "graceful-shutdown";
+ /**
+ * XML namespace name for health check IQs.
+ */
+ final static public String NAMESPACE
+ = "http://jitsi.org/protocol/healthcheck";
+ /**
+ * {@inheritDoc}
+ */
@Override
public String getChildElementXML()
{
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java
new file mode 100644
index 0000000..9c2903d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/health/HealthCheckIQProvider.java
@@ -0,0 +1,94 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.health;
+
+import net.java.sip.communicator.service.protocol.jabber.*;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.*;
+
+import org.xmlpull.v1.*;
+
+/**
+ * The <tt>IQProvider</tt> for {@link HealthCheckIQ}.
+ *
+ * @author Pawel Domas
+ */
+public class HealthCheckIQProvider
+ implements IQProvider
+{
+ /**
+ * Registers <tt>HealthCheckIQProvider</tt> as an <tt>IQProvider</tt>
+ * in {@link AbstractSmackInteroperabilityLayer}.
+ */
+ public static void registerIQProvider()
+ {
+ AbstractSmackInteroperabilityLayer smackInteropLayer =
+ AbstractSmackInteroperabilityLayer.getInstance();
+
+ // ColibriStatsIQ
+ smackInteropLayer.addIQProvider(
+ HealthCheckIQ.ELEMENT_NAME,
+ HealthCheckIQ.NAMESPACE,
+ new HealthCheckIQProvider());
+ }
+
+ /**
+ * Parses <tt>HealthCheckIQ</tt>.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public IQ parseIQ(XmlPullParser parser)
+ throws Exception
+ {
+ String namespace = parser.getNamespace();
+ IQ iq;
+
+ if (HealthCheckIQ.ELEMENT_NAME.equals(parser.getName())
+ && HealthCheckIQ.NAMESPACE.equals(namespace))
+ {
+ String rootElement = parser.getName();
+
+ iq = new HealthCheckIQ();
+
+ boolean done = false;
+
+ while (!done)
+ {
+ switch (parser.next())
+ {
+ case XmlPullParser.END_TAG:
+ {
+ String name = parser.getName();
+
+ if (rootElement.equals(name))
+ {
+ done = true;
+ }
+ break;
+ }
+ }
+ }
+ }
+ else
+ iq = null;
+
+ return iq;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java
new file mode 100644
index 0000000..8b964af
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java
@@ -0,0 +1,413 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri;
+
+import org.jitsi.util.*;
+
+import org.jivesoftware.smack.packet.*;
+
+import java.util.*;
+
+/**
+ * The IQ used to control conference recording with Jibri component.
+ *
+ * Start the recording:
+ *
+ * 1. Send Jibri IQ with {@link Action#START} to Jibri.
+ * 2. Jibri replies with RESULT and status {@link Status#PENDING}.
+ * 3. Jibri sends SET IQ with status {@link Status#ON} once recording actually
+ * starts.
+ *
+ * Stop the recording:
+ *
+ * 1. Send Jibri IQ with {@link Action#STOP} to Jibri.
+ * 2. Jibri replies with {@link Status#OFF} immediately if the recording has
+ * been stopped already or sends separate Jibri SET IQ later on if it takes
+ * more time.
+ *
+ * @author lishunyang
+ * @author Pawel Domas
+ */
+public class JibriIq
+ extends IQ
+{
+ /**
+ * Attribute name of "action".
+ */
+ public static final String ACTION_ATTR_NAME = "action";
+
+ /**
+ * XML element name of the Jibri IQ.
+ */
+ public static final String ELEMENT_NAME = "jibri";
+
+ /**
+ * XML namespace of the Jibri IQ.
+ */
+ public static final String NAMESPACE = "http://jitsi.org/protocol/jibri";
+
+ /**
+ * The name of XML attribute which stores the recording status.
+ */
+ static final String STATUS_ATTR_NAME = "status";
+
+ /**
+ * The name of XML attribute which stores the stream id.
+ */
+ static final String STREAM_ID_ATTR_NAME = "streamid";
+
+ /**
+ * The name of XML attribute which stores the name of the conference room to
+ * be recorded.
+ */
+ static final String ROOM_ATTR_NAME = "room";
+
+ /**
+ * Holds the action.
+ */
+ private Action action = Action.UNDEFINED;
+
+ /**
+ * XMPPError stores error details for {@link Status#FAILED}.
+ */
+ private XMPPError error;
+
+ /**
+ * Holds recording status.
+ */
+ private Status status = Status.UNDEFINED;
+
+ /**
+ * The ID of the stream which will be used to record the conference. The
+ * value depends on recording service provider.
+ */
+ private String streamId = null;
+
+ /**
+ * The name of the conference room to be recorded.
+ */
+ private String room = null;
+
+ /**
+ * Returns the value of {@link #STREAM_ID_ATTR_NAME} attribute.
+ * @return a <tt>String</tt> which contains the value of "stream id"
+ * attribute or <tt>null</tt> if empty.
+ */
+ public String getStreamId()
+ {
+ return streamId;
+ }
+
+ /**
+ * Sets the value for {@link #STREAM_ID_ATTR_NAME} attribute.
+ * @param streamId a <tt>String</tt> for the stream id attribute or
+ * <tt>null</tt> to remove it from XML element.
+ */
+ public void setStreamId(String streamId)
+ {
+ this.streamId = streamId;
+ }
+
+ /**
+ * Returns the value of {@link #ROOM_ATTR_NAME} attribute.
+ * @return a <tt>String</tt> which contains the value of the room attribute
+ * or <tt>null</tt> if empty.
+ * @see #room
+ */
+ public String getRoom()
+ {
+ return room;
+ }
+
+ /**
+ * Sets the value for {@link #ROOM_ATTR_NAME} attribute.
+ * @param room a <tt>String</tt> for the room attribute or <tt>null</tt> to
+ * remove it from XML element.
+ * @see #room
+ */
+ public void setRoom(String room)
+ {
+ this.room = room;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getChildElementXML()
+ {
+ StringBuilder xml = new StringBuilder();
+
+ xml.append('<').append(ELEMENT_NAME);
+ xml.append(" xmlns='").append(NAMESPACE).append("' ");
+
+ if (action != Action.UNDEFINED)
+ {
+ printStringAttribute(xml, ACTION_ATTR_NAME, action.toString());
+ }
+
+ if (status != Status.UNDEFINED)
+ {
+ printStringAttribute(xml, STATUS_ATTR_NAME, status.toString());
+ }
+
+ if (room != null)
+ {
+ printStringAttribute(xml, ROOM_ATTR_NAME, room);
+ }
+
+ if (streamId != null)
+ {
+ printStringAttribute(xml, STREAM_ID_ATTR_NAME, streamId);
+ }
+
+ Collection<PacketExtension> extensions = getExtensions();
+ if (extensions.size() > 0)
+ {
+ xml.append(">");
+ for (PacketExtension extension : extensions)
+ {
+ xml.append(extension.toXML());
+ }
+ xml.append("</").append(ELEMENT_NAME).append(">");
+ }
+ else
+ {
+ xml.append("/>");
+ }
+
+ return xml.toString();
+ }
+
+ private void printStringAttribute(
+ StringBuilder xml, String attrName, String attr)
+ {
+ if (!StringUtils.isNullOrEmpty(attr))
+ {
+ attr = org.jivesoftware.smack.util.StringUtils.escapeForXML(attr);
+ xml.append(attrName).append("='")
+ .append(attr).append("' ");
+ }
+ }
+
+ /**
+ * Sets the value of 'action' attribute.
+ *
+ * @param action the value to be set as 'action' attribute of this IQ.
+ */
+ public void setAction(Action action)
+ {
+ this.action = action;
+ }
+
+ /**
+ * Returns the value of 'action' attribute.
+ */
+ public Action getAction()
+ {
+ return action;
+ }
+
+ /**
+ * Sets the value of 'status' attribute.
+ */
+ public void setStatus(Status status)
+ {
+ this.status = status;
+ }
+
+ /**
+ * Returns the value of 'status' attribute.
+ */
+ public Status getStatus()
+ {
+ return status;
+ }
+
+ /**
+ * Sets the <tt>XMPPError</tt> which will provide details about Jibri
+ * failure. It is expected to be set when this IQ's status value is
+ * {@link Status#FAILED}.
+ *
+ * @param error <tt>XMPPError</tt> to be set on this <tt>JibriIq</tt>
+ * instance.
+ */
+ public void setXMPPError(XMPPError error)
+ {
+ this.error = error;
+ }
+
+ /**
+ * Returns {@link XMPPError} with Jibri error details when the status is
+ * {@link Status#FAILED}.
+ */
+ public XMPPError getError()
+ {
+ return error;
+ }
+
+ /**
+ * Enumerative value of attribute "action" in recording extension.
+ *
+ * @author lishunyang
+ * @author Pawel Domas
+ *
+ */
+ public enum Action
+ {
+ /**
+ * Start the recording.
+ */
+ START("start"),
+ /**
+ * Stop the recording.
+ */
+ STOP("stop"),
+ /**
+ * Unknown/uninitialized
+ */
+ UNDEFINED("undefined");
+
+ private String name;
+
+ Action(String name)
+ {
+ this.name = name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Parses <tt>Action</tt> from given string.
+ *
+ * @param action the string representation of <tt>Action</tt>.
+ *
+ * @return <tt>Action</tt> value for given string or
+ * {@link #UNDEFINED} if given string does not
+ * reflect any of valid values.
+ */
+ public static Action parse(String action)
+ {
+ if (StringUtils.isNullOrEmpty(action))
+ return UNDEFINED;
+
+ try
+ {
+ return Action.valueOf(action.toUpperCase());
+ }
+ catch(IllegalArgumentException e)
+ {
+ return UNDEFINED;
+ }
+ }
+ }
+
+ /**
+ * The enumeration of recording status values.
+ */
+ public enum Status
+ {
+ /**
+ * Recording is in progress.
+ */
+ ON("on"),
+
+ /**
+ * Recording stopped.
+ */
+ OFF("off"),
+
+ /**
+ * Starting the recording process.
+ */
+ PENDING("pending"),
+
+ /**
+ * The recorder has failed and the service is retrying on another
+ * instance.
+ */
+ RETRYING("retrying"),
+
+ /**
+ * An error occurred any point during startup, recording or shutdown.
+ */
+ FAILED("failed"),
+
+ /**
+ * There are Jibri instances connected to the system, but all of them
+ * are currently busy.
+ */
+ BUSY("busy"),
+
+ /**
+ * Unknown/uninitialized.
+ */
+ UNDEFINED("undefined");
+
+ /**
+ * Status name holder.
+ */
+ private String name;
+
+ /**
+ * Creates new {@link Status} instance.
+ * @param name a string corresponding to one of {@link Status} values.
+ */
+ Status(String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Parses <tt>Status</tt> from given string.
+ *
+ * @param status the string representation of <tt>Status</tt>.
+ *
+ * @return <tt>Status</tt> value for given string or
+ * {@link #UNDEFINED} if given string does not
+ * reflect any of valid values.
+ */
+ public static Status parse(String status)
+ {
+ if (StringUtils.isNullOrEmpty(status))
+ return UNDEFINED;
+
+ try
+ {
+ return Status.valueOf(status.toUpperCase());
+ }
+ catch(IllegalArgumentException e)
+ {
+ return UNDEFINED;
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java
new file mode 100644
index 0000000..155853c
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java
@@ -0,0 +1,112 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri;
+
+import org.jitsi.util.*;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.*;
+import org.jivesoftware.smack.util.PacketParserUtils;
+
+import org.xmlpull.v1.*;
+
+/**
+ * Parses {@link JibriIq}.
+ */
+public class JibriIqProvider
+ implements IQProvider
+{
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IQ parseIQ(XmlPullParser parser)
+ throws Exception
+ {
+ String namespace = parser.getNamespace();
+
+ // Check the namespace
+ if (!JibriIq.NAMESPACE.equals(namespace))
+ {
+ return null;
+ }
+
+ String rootElement = parser.getName();
+
+ JibriIq iq;
+
+ if (JibriIq.ELEMENT_NAME.equals(rootElement))
+ {
+ iq = new JibriIq();
+
+ String action
+ = parser.getAttributeValue("", JibriIq.ACTION_ATTR_NAME);
+ iq.setAction(JibriIq.Action.parse(action));
+
+ String status
+ = parser.getAttributeValue("", JibriIq.STATUS_ATTR_NAME);
+ iq.setStatus(JibriIq.Status.parse(status));
+
+ String room
+ = parser.getAttributeValue("", JibriIq.ROOM_ATTR_NAME);
+ if (!StringUtils.isNullOrEmpty(room))
+ iq.setRoom(room);
+
+ String streamId
+ = parser.getAttributeValue("", JibriIq.STREAM_ID_ATTR_NAME);
+ if (!StringUtils.isNullOrEmpty(streamId))
+ iq.setStreamId(streamId);
+ }
+ else
+ {
+ return null;
+ }
+
+ boolean done = false;
+
+ while (!done)
+ {
+ switch (parser.next())
+ {
+ case XmlPullParser.START_TAG:
+ {
+ String name = parser.getName();
+
+ if ("error".equals(name))
+ {
+ XMPPError error = PacketParserUtils.parseError(parser);
+ iq.setXMPPError(error);
+ }
+ break;
+ }
+ case XmlPullParser.END_TAG:
+ {
+ String name = parser.getName();
+
+ if (rootElement.equals(name))
+ {
+ done = true;
+ }
+ break;
+ }
+ }
+ }
+
+ return iq;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java
new file mode 100644
index 0000000..e046b68
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java
@@ -0,0 +1,121 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+
+import org.jitsi.util.*;
+
+import org.jivesoftware.smack.provider.*;
+
+/**
+ * Status extension included in MUC presence by Jibri to indicate it's status.
+ * One of:
+ * <li>idle</li> - the instance is idle and can be used for recording
+ * <li>busy</li> - the instance is currently recording or doing something very
+ * important and should not be disturbed
+ *
+ *
+ */
+public class JibriStatusPacketExt
+ extends AbstractPacketExtension
+{
+ /**
+ * The namespace of this packet extension.
+ */
+ public static final String NAMESPACE = JibriIq.NAMESPACE;
+
+ /**
+ * XML element name of this packet extension.
+ */
+ public static final String ELEMENT_NAME = "jibri-status";
+
+ private static final String STATUS_ATTRIBUTE = "status";
+
+ /**
+ * Creates new instance of <tt>VideoMutedExtension</tt>.
+ */
+ public JibriStatusPacketExt()
+ {
+ super(NAMESPACE, ELEMENT_NAME);
+ }
+
+ static public void registerExtensionProvider()
+ {
+ ProviderManager.getInstance().addExtensionProvider(
+ ELEMENT_NAME,
+ NAMESPACE,
+ new DefaultPacketExtensionProvider<JibriStatusPacketExt>(
+ JibriStatusPacketExt.class)
+ );
+ }
+
+ public Status getStatus()
+ {
+ return Status.parse(getAttributeAsString(STATUS_ATTRIBUTE));
+ }
+
+ public void setStatus(Status status)
+ {
+ setAttribute(STATUS_ATTRIBUTE, String.valueOf(status));
+ }
+
+ public enum Status
+ {
+ IDLE("idle"),
+ BUSY("busy"),
+ UNDEFINED("undefined");
+
+ private String name;
+
+ Status(String name)
+ {
+ this.name = name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Parses <tt>Status</tt> from given string.
+ *
+ * @param status the string representation of <tt>Status</tt>.
+ *
+ * @return <tt>Status</tt> value for given string or
+ * {@link #UNDEFINED} if given string does not
+ * reflect any of valid values.
+ */
+ public static Status parse(String status)
+ {
+ if (StringUtils.isNullOrEmpty(status))
+ return UNDEFINED;
+
+ try
+ {
+ return Status.valueOf(status.toUpperCase());
+ }
+ catch(IllegalArgumentException e)
+ {
+ return UNDEFINED;
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java
new file mode 100644
index 0000000..13177cf
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java
@@ -0,0 +1,126 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+
+import org.jivesoftware.smack.packet.*;
+
+import java.util.*;
+
+/**
+ * The packet extension added to Jicofo MUC presence to broadcast current
+ * recording status to all conference participants.
+ *
+ * Status meaning:
+ * <tt>{@link JibriIq.Status#UNDEFINED}</tt> - recording not available
+ * <tt>{@link JibriIq.Status#OFF}</tt> - recording stopped(available to start)
+ * <tt>{@link JibriIq.Status#PENDING}</tt> - starting recording
+ * <tt>{@link JibriIq.Status#ON}</tt> - recording in progress
+ */
+public class RecordingStatus
+ extends AbstractPacketExtension
+{
+ /**
+ * The namespace of this packet extension.
+ */
+ public static final String NAMESPACE = JibriIq.NAMESPACE;
+
+ /**
+ * XML element name of this packet extension.
+ */
+ public static final String ELEMENT_NAME = "jibri-recording-status";
+
+ /**
+ * The name of XML attribute which holds the recording status.
+ */
+ private static final String STATUS_ATTRIBUTE = "status";
+
+ public RecordingStatus()
+ {
+ super(NAMESPACE, ELEMENT_NAME);
+ }
+
+ /**
+ * Returns the value of current recording status stored in it's attribute.
+ * @return one of {@link JibriIq.Status}
+ */
+ public JibriIq.Status getStatus()
+ {
+ String statusAttr = getAttributeAsString(STATUS_ATTRIBUTE);
+
+ return JibriIq.Status.parse(statusAttr);
+ }
+
+ /**
+ * Sets new value for the recording status.
+ * @param status one of {@link JibriIq.Status}
+ */
+ public void setStatus(JibriIq.Status status)
+ {
+ setAttribute(STATUS_ATTRIBUTE, String.valueOf(status));
+ }
+
+ /**
+ * Returns <tt>XMPPError</tt> associated with current
+ * {@link RecordingStatus}.
+ */
+ public XMPPError getError()
+ {
+ XMPPErrorPE errorPe = getErrorPE();
+ return errorPe != null ? errorPe.getError() : null;
+ }
+
+ /**
+ * Gets <tt>{@link XMPPErrorPE}</tt> from the list of child packet
+ * extensions.
+ * @return {@link XMPPErrorPE} or <tt>null</tt> if not found.
+ */
+ private XMPPErrorPE getErrorPE()
+ {
+ List<? extends PacketExtension> errorPe
+ = getChildExtensionsOfType(XMPPErrorPE.class);
+
+ return (XMPPErrorPE) (!errorPe.isEmpty() ? errorPe.get(0) : null);
+ }
+
+ /**
+ * Sets <tt>XMPPError</tt> on this <tt>RecordingStatus</tt>.
+ * @param error <tt>XMPPError</tt> to add error details to this
+ * <tt>RecordingStatus</tt> instance or <tt>null</tt> to have it removed.
+ */
+ public void setError(XMPPError error)
+ {
+ if (error != null)
+ {
+ // Wrap and add XMPPError as packet extension
+ XMPPErrorPE errorPe = getErrorPE();
+ if (errorPe == null)
+ {
+ errorPe = new XMPPErrorPE(error);
+ addChildExtension(errorPe);
+ }
+ errorPe.setError(error);
+ }
+ else
+ {
+ // Remove error PE
+ getChildExtensions().remove(getErrorPE());
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java
new file mode 100644
index 0000000..a72f310
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java
@@ -0,0 +1,93 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jibri;
+
+import org.jivesoftware.smack.packet.*;
+
+import java.util.*;
+
+/**
+ * Wraps Smack's <tt>XMPPError</tt> into <tt>PacketExtension</tt>, so that it
+ * can be easily inserted into {@link RecordingStatus}.
+ */
+public class XMPPErrorPE
+ implements PacketExtension
+{
+ /**
+ * <tt>XMPPError</tt> wrapped into this <tt>XMPPErrorPE</tt>.
+ */
+ private XMPPError error;
+
+ /**
+ * Creates new instance of <tt>XMPPErrorPE</tt>.
+ * @param xmppError the instance of <tt>XMPPError</tt> that will be wrapped
+ * by the newly created <tt>XMPPErrorPE</tt>.
+ */
+ public XMPPErrorPE(XMPPError xmppError)
+ {
+ setError(xmppError);
+ }
+
+ /**
+ * Returns the underlying instance of <tt>XMPPError</tt>.
+ */
+ public XMPPError getError()
+ {
+ return error;
+ }
+
+ /**
+ * Sets new instance of <tt>XMPPError</tt> to be wrapped by this
+ * <tt>XMPPErrorPE</tt>.
+ * @param error <tt>XMPPError</tt> that will be wrapped by this
+ * <TT>XMPPErrorPE</TT>.
+ */
+ public void setError(XMPPError error)
+ {
+ Objects.requireNonNull(error, "error");
+
+ this.error = error;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getElementName()
+ {
+ return "error";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getNamespace()
+ {
+ return "";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toXML()
+ {
+ return error.toXML();
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java
index 0c5b190..46b1e77 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CandidatePacketExtension.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,441 +15,441 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
-import org.ice4j.ice.*;
-
-/**
- * @author Emil Ivov
- */
-public class CandidatePacketExtension extends AbstractPacketExtension
- implements Comparable<CandidatePacketExtension>
-{
- /**
- * The name of the "candidate" element.
- */
- public static final String ELEMENT_NAME = "candidate";
-
- /**
- * The name of the "component" element.
- */
- public static final String COMPONENT_ATTR_NAME = "component";
-
- /**
- * The "component" ID for RTP components.
- */
- public static final int RTP_COMPONENT_ID = 1;
-
- /**
- * The "component" ID for RTCP components.
- */
- public static final int RTCP_COMPONENT_ID = 2;
-
- /**
- * The name of the "foundation" element.
- */
- public static final String FOUNDATION_ATTR_NAME = "foundation";
-
- /**
- * The name of the "generation" element.
- */
- public static final String GENERATION_ATTR_NAME = "generation";
-
- /**
- * The name of the "id" element.
- */
- public static final String ID_ATTR_NAME = "id";
-
- /**
- * The name of the "ip" element.
- */
- public static final String IP_ATTR_NAME = "ip";
-
- /**
- * The name of the "network" element.
- */
- public static final String NETWORK_ATTR_NAME = "network";
-
- /**
- * The name of the "port" element.
- */
- public static final String PORT_ATTR_NAME = "port";
-
- /**
- * The name of the "priority" element.
- */
- public static final String PRIORITY_ATTR_NAME = "priority";
-
- /**
- * The name of the "protocol" element.
- */
- public static final String PROTOCOL_ATTR_NAME = "protocol";
-
- /**
- * The name of the "rel-addr" element.
- */
- public static final String REL_ADDR_ATTR_NAME = "rel-addr";
-
- /**
- * The name of the "rel-port" element.
- */
- public static final String REL_PORT_ATTR_NAME = "rel-port";
-
- /**
- * The name of the "type" element.
- */
- public static final String TYPE_ATTR_NAME = "type";
-
- /**
- * The name of the "tcptype" element.
- */
- public static final String TCPTYPE_ATTR_NAME = "tcptype";
-
- /**
- * Creates a new {@link CandidatePacketExtension}
- */
- public CandidatePacketExtension()
- {
- super(null, ELEMENT_NAME);
- }
-
- /**
- * Creates a new {@link CandidatePacketExtension} with the specified
- * <tt>elementName</tt> so that this class would be usable as a
- * <tt>RemoteCandidatePacketExtension</tt> parent.
- *
- * @param elementName the element name that this instance should be using.
- */
- protected CandidatePacketExtension(String elementName)
- {
- super(null, elementName);
- }
-
- /**
- * Sets a component ID as defined in ICE-CORE.
- *
- * @param component a component ID as defined in ICE-CORE.
- */
- public void setComponent(int component)
- {
- super.setAttribute(COMPONENT_ATTR_NAME, component);
- }
-
- /**
- * Returns a component ID as defined in ICE-CORE.
- *
- * @return a component ID as defined in ICE-CORE.
- */
- public int getComponent()
- {
- return super.getAttributeAsInt(COMPONENT_ATTR_NAME);
- }
-
- /**
- * Sets the candidate foundation as defined in ICE-CORE.
- *
- * @param foundation the candidate foundation as defined in ICE-CORE.
- */
- public void setFoundation(String foundation)
- {
- super.setAttribute(FOUNDATION_ATTR_NAME, foundation);
- }
-
- /**
- * Returns the candidate foundation as defined in ICE-CORE.
- *
- * @return the candidate foundation as defined in ICE-CORE.
- */
- public String getFoundation()
- {
- return super.getAttributeAsString(FOUNDATION_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's generation index. A generation is an index,
- * starting at 0, that enables the parties to keep track of updates to the
- * candidate throughout the life of the session. For details, see the ICE
- * Restarts section of XEP-0176.
- *
- * @param generation this candidate's generation index.
- */
- public void setGeneration(int generation)
- {
- super.setAttribute(GENERATION_ATTR_NAME, generation);
- }
-
- /**
- * Returns this candidate's generation. A generation is an index, starting at
- * 0, that enables the parties to keep track of updates to the candidate
- * throughout the life of the session. For details, see the ICE Restarts
- * section of XEP-0176.
- *
- * @return this candidate's generation index.
- */
- public int getGeneration()
- {
- return super.getAttributeAsInt(GENERATION_ATTR_NAME);
- }
-
- /**
- * Sets this candidates's unique identifier <tt>String</tt>.
- *
- * @param id this candidates's unique identifier <tt>String</tt>
- */
- public void setID(String id)
- {
- super.setAttribute(ID_ATTR_NAME, id);
- }
-
- /**
- * Returns this candidates's unique identifier <tt>String</tt>.
- *
- * @return this candidates's unique identifier <tt>String</tt>
- */
- public String getID()
- {
- return super.getAttributeAsString(ID_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's Internet Protocol (IP) address; this can be either
- * an IPv4 address or an IPv6 address.
- *
- * @param ip this candidate's IPv4 or IPv6 address.
- */
- public void setIP(String ip)
- {
- super.setAttribute(IP_ATTR_NAME, ip);
- }
-
- /**
- * Returns this candidate's Internet Protocol (IP) address; this can be
- * either an IPv4 address or an IPv6 address.
- *
- * @return this candidate's IPv4 or IPv6 address.
- */
- public String getIP()
- {
- return super.getAttributeAsString(IP_ATTR_NAME);
- }
-
- /**
- * The network index indicating the interface that the candidate belongs to.
- * The network ID is used for diagnostic purposes only in cases where the
- * calling hardware has more than one Network Interface Card.
- *
- * @param network the network index indicating the interface that the
- * candidate belongs to.
- */
- public void setNetwork(int network)
- {
- super.setAttribute(NETWORK_ATTR_NAME, network);
- }
-
- /**
- * Returns the network index indicating the interface that the candidate
- * belongs to. The network ID is used for diagnostic purposes only in cases
- * where the calling hardware has more than one Network Interface Card.
- *
- * @return the network index indicating the interface that the candidate
- * belongs to.
- */
- public int getNetwork()
- {
- return super.getAttributeAsInt(NETWORK_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's port number.
- *
- * @param port this candidate's port number.
- */
- public void setPort(int port)
- {
- super.setAttribute(PORT_ATTR_NAME, port);
- }
-
- /**
- * Returns this candidate's port number.
- *
- * @return this candidate's port number.
- */
- public int getPort()
- {
- return super.getAttributeAsInt(PORT_ATTR_NAME);
- }
-
- /**
- * This candidate's priority as defined in ICE's RFC 5245
- *
- * @param priority this candidate's priority
- */
- public void setPriority(long priority)
- {
- super.setAttribute(PRIORITY_ATTR_NAME, priority);
- }
-
- /**
- * This candidate's priority as defined in ICE's RFC 5245
- *
- * @return this candidate's priority
- */
- public int getPriority()
- {
- return super.getAttributeAsInt(PRIORITY_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's transport protocol.
- *
- * @param protocol this candidate's transport protocol.
- */
- public void setProtocol(String protocol)
- {
- super.setAttribute(PROTOCOL_ATTR_NAME, protocol);
- }
-
- /**
- * Sets this candidate's transport protocol.
- *
- * @return this candidate's transport protocol.
- */
- public String getProtocol()
- {
- return super.getAttributeAsString(PROTOCOL_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's related address as described by ICE's RFC 5245.
- *
- * @param relAddr this candidate's related address as described by ICE's
- * RFC 5245.
- */
- public void setRelAddr(String relAddr)
- {
- super.setAttribute(REL_ADDR_ATTR_NAME, relAddr);
- }
-
- /**
- * Returns this candidate's related address as described by ICE's RFC 5245.
- *
- * @return this candidate's related address as described by ICE's RFC 5245.
- */
- public String getRelAddr()
- {
- return super.getAttributeAsString(REL_ADDR_ATTR_NAME);
- }
-
- /**
- * Sets this candidate's related port as described by ICE's RFC 5245.
- *
- * @param relPort this candidate's related port as described by ICE's
- * RFC 5245.
- */
- public void setRelPort(int relPort)
- {
- super.setAttribute(REL_PORT_ATTR_NAME, relPort);
- }
-
- /**
- * Returns this candidate's related port as described by ICE's RFC 5245.
- *
- * @return this candidate's related port as described by ICE's RFC 5245.
- */
- public int getRelPort()
- {
- return super.getAttributeAsInt(REL_PORT_ATTR_NAME);
- }
-
- /**
- * Sets a Candidate Type as defined in ICE-CORE. The allowable values are
- * "host" for host candidates, "prflx" for peer reflexive candidates,
- * "relay" for relayed candidates, and "srflx" for server reflexive
- * candidates. All allowable values are enumerated in the {@link
- * CandidateType} enum.
- *
- * @param type this candidates' type as per ICE's RFC 5245.
- */
- public void setType(CandidateType type)
- {
- super.setAttribute(TYPE_ATTR_NAME, type);
- }
-
- /**
- * Returns a Candidate Type as defined in ICE-CORE. The allowable values are
- * "host" for host candidates, "prflx" for peer reflexive candidates,
- * "relay" for relayed candidates, and "srflx" for server reflexive
- * candidates. All allowable values are enumerated in the {@link
- * CandidateType} enum.
- *
- * @return this candidates' type as per ICE's RFC 5245.
- */
- public CandidateType getType()
- {
- return CandidateType.valueOf(getAttributeAsString(TYPE_ATTR_NAME));
- }
-
- /**
- * Compares this instance with another CandidatePacketExtension by
- * preference of type: host < local < prflx < srflx < stun < relay.
- *
- * @return 0 if the type are equal. -1 if this instance type is preferred.
- * Otherwise 1.
- */
- public int compareTo(CandidatePacketExtension candidatePacketExtension)
- {
- // If the types are different.
- if(this.getType() != candidatePacketExtension.getType())
- {
- CandidateType[] types = {
- CandidateType.host,
- CandidateType.local,
- CandidateType.prflx,
- CandidateType.srflx,
- CandidateType.stun,
- CandidateType.relay
- };
- for(int i = 0; i < types.length; ++i)
- {
- // this object is preferred.
- if(types[i] == this.getType())
- {
- return -1;
- }
- // the candidatePacketExtension is preferred.
- else if(types[i] == candidatePacketExtension.getType())
- {
- return 1;
- }
- }
- }
- // If the types are equal.
- return 0;
- }
-
- /**
- * Gets the TCP type for this <tt>CandidatePacketExtension</tt>.
- */
- public CandidateTcpType getTcpType()
- {
- String tcpTypeString = getAttributeAsString(TCPTYPE_ATTR_NAME);
- try
- {
- return CandidateTcpType.parse(tcpTypeString);
- }
- catch (IllegalArgumentException iae)
- {
- return null;
- }
- }
-
- /**
- * Sets the TCP type for this <tt>CandidatePacketExtension</tt>.
- * @param tcpType
- */
- public void setTcpType(CandidateTcpType tcpType)
- {
- setAttribute(TCPTYPE_ATTR_NAME, tcpType.toString());
- }
-}
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+import org.ice4j.ice.*;
+
+/**
+ * @author Emil Ivov
+ */
+public class CandidatePacketExtension extends AbstractPacketExtension
+ implements Comparable<CandidatePacketExtension>
+{
+ /**
+ * The name of the "candidate" element.
+ */
+ public static final String ELEMENT_NAME = "candidate";
+
+ /**
+ * The name of the "component" element.
+ */
+ public static final String COMPONENT_ATTR_NAME = "component";
+
+ /**
+ * The "component" ID for RTP components.
+ */
+ public static final int RTP_COMPONENT_ID = 1;
+
+ /**
+ * The "component" ID for RTCP components.
+ */
+ public static final int RTCP_COMPONENT_ID = 2;
+
+ /**
+ * The name of the "foundation" element.
+ */
+ public static final String FOUNDATION_ATTR_NAME = "foundation";
+
+ /**
+ * The name of the "generation" element.
+ */
+ public static final String GENERATION_ATTR_NAME = "generation";
+
+ /**
+ * The name of the "id" element.
+ */
+ public static final String ID_ATTR_NAME = "id";
+
+ /**
+ * The name of the "ip" element.
+ */
+ public static final String IP_ATTR_NAME = "ip";
+
+ /**
+ * The name of the "network" element.
+ */
+ public static final String NETWORK_ATTR_NAME = "network";
+
+ /**
+ * The name of the "port" element.
+ */
+ public static final String PORT_ATTR_NAME = "port";
+
+ /**
+ * The name of the "priority" element.
+ */
+ public static final String PRIORITY_ATTR_NAME = "priority";
+
+ /**
+ * The name of the "protocol" element.
+ */
+ public static final String PROTOCOL_ATTR_NAME = "protocol";
+
+ /**
+ * The name of the "rel-addr" element.
+ */
+ public static final String REL_ADDR_ATTR_NAME = "rel-addr";
+
+ /**
+ * The name of the "rel-port" element.
+ */
+ public static final String REL_PORT_ATTR_NAME = "rel-port";
+
+ /**
+ * The name of the "type" element.
+ */
+ public static final String TYPE_ATTR_NAME = "type";
+
+ /**
+ * The name of the "tcptype" element.
+ */
+ public static final String TCPTYPE_ATTR_NAME = "tcptype";
+
+ /**
+ * Creates a new {@link CandidatePacketExtension}
+ */
+ public CandidatePacketExtension()
+ {
+ super(null, ELEMENT_NAME);
+ }
+
+ /**
+ * Creates a new {@link CandidatePacketExtension} with the specified
+ * <tt>elementName</tt> so that this class would be usable as a
+ * <tt>RemoteCandidatePacketExtension</tt> parent.
+ *
+ * @param elementName the element name that this instance should be using.
+ */
+ protected CandidatePacketExtension(String elementName)
+ {
+ super(null, elementName);
+ }
+
+ /**
+ * Sets a component ID as defined in ICE-CORE.
+ *
+ * @param component a component ID as defined in ICE-CORE.
+ */
+ public void setComponent(int component)
+ {
+ super.setAttribute(COMPONENT_ATTR_NAME, component);
+ }
+
+ /**
+ * Returns a component ID as defined in ICE-CORE.
+ *
+ * @return a component ID as defined in ICE-CORE.
+ */
+ public int getComponent()
+ {
+ return super.getAttributeAsInt(COMPONENT_ATTR_NAME);
+ }
+
+ /**
+ * Sets the candidate foundation as defined in ICE-CORE.
+ *
+ * @param foundation the candidate foundation as defined in ICE-CORE.
+ */
+ public void setFoundation(String foundation)
+ {
+ super.setAttribute(FOUNDATION_ATTR_NAME, foundation);
+ }
+
+ /**
+ * Returns the candidate foundation as defined in ICE-CORE.
+ *
+ * @return the candidate foundation as defined in ICE-CORE.
+ */
+ public String getFoundation()
+ {
+ return super.getAttributeAsString(FOUNDATION_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidate's generation index. A generation is an index,
+ * starting at 0, that enables the parties to keep track of updates to the
+ * candidate throughout the life of the session. For details, see the ICE
+ * Restarts section of XEP-0176.
+ *
+ * @param generation this candidate's generation index.
+ */
+ public void setGeneration(int generation)
+ {
+ super.setAttribute(GENERATION_ATTR_NAME, generation);
+ }
+
+ /**
+ * Returns this candidate's generation. A generation is an index, starting at
+ * 0, that enables the parties to keep track of updates to the candidate
+ * throughout the life of the session. For details, see the ICE Restarts
+ * section of XEP-0176.
+ *
+ * @return this candidate's generation index.
+ */
+ public int getGeneration()
+ {
+ return super.getAttributeAsInt(GENERATION_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidates's unique identifier <tt>String</tt>.
+ *
+ * @param id this candidates's unique identifier <tt>String</tt>
+ */
+ public void setID(String id)
+ {
+ super.setAttribute(ID_ATTR_NAME, id);
+ }
+
+ /**
+ * Returns this candidates's unique identifier <tt>String</tt>.
+ *
+ * @return this candidates's unique identifier <tt>String</tt>
+ */
+ public String getID()
+ {
+ return super.getAttributeAsString(ID_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidate's Internet Protocol (IP) address; this can be either
+ * an IPv4 address or an IPv6 address.
+ *
+ * @param ip this candidate's IPv4 or IPv6 address.
+ */
+ public void setIP(String ip)
+ {
+ super.setAttribute(IP_ATTR_NAME, ip);
+ }
+
+ /**
+ * Returns this candidate's Internet Protocol (IP) address; this can be
+ * either an IPv4 address or an IPv6 address.
+ *
+ * @return this candidate's IPv4 or IPv6 address.
+ */
+ public String getIP()
+ {
+ return super.getAttributeAsString(IP_ATTR_NAME);
+ }
+
+ /**
+ * The network index indicating the interface that the candidate belongs to.
+ * The network ID is used for diagnostic purposes only in cases where the
+ * calling hardware has more than one Network Interface Card.
+ *
+ * @param network the network index indicating the interface that the
+ * candidate belongs to.
+ */
+ public void setNetwork(int network)
+ {
+ super.setAttribute(NETWORK_ATTR_NAME, network);
+ }
+
+ /**
+ * Returns the network index indicating the interface that the candidate
+ * belongs to. The network ID is used for diagnostic purposes only in cases
+ * where the calling hardware has more than one Network Interface Card.
+ *
+ * @return the network index indicating the interface that the candidate
+ * belongs to.
+ */
+ public int getNetwork()
+ {
+ return super.getAttributeAsInt(NETWORK_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidate's port number.
+ *
+ * @param port this candidate's port number.
+ */
+ public void setPort(int port)
+ {
+ super.setAttribute(PORT_ATTR_NAME, port);
+ }
+
+ /**
+ * Returns this candidate's port number.
+ *
+ * @return this candidate's port number.
+ */
+ public int getPort()
+ {
+ return super.getAttributeAsInt(PORT_ATTR_NAME);
+ }
+
+ /**
+ * This candidate's priority as defined in ICE's RFC 5245
+ *
+ * @param priority this candidate's priority
+ */
+ public void setPriority(long priority)
+ {
+ super.setAttribute(PRIORITY_ATTR_NAME, priority);
+ }
+
+ /**
+ * This candidate's priority as defined in ICE's RFC 5245
+ *
+ * @return this candidate's priority
+ */
+ public int getPriority()
+ {
+ return super.getAttributeAsInt(PRIORITY_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidate's transport protocol.
+ *
+ * @param protocol this candidate's transport protocol.
+ */
+ public void setProtocol(String protocol)
+ {
+ super.setAttribute(PROTOCOL_ATTR_NAME, protocol);
+ }
+
+ /**
+ * Sets this candidate's transport protocol.
+ *
+ * @return this candidate's transport protocol.
+ */
+ public String getProtocol()
+ {
+ return super.getAttributeAsString(PROTOCOL_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidate's related address as described by ICE's RFC 5245.
+ *
+ * @param relAddr this candidate's related address as described by ICE's
+ * RFC 5245.
+ */
+ public void setRelAddr(String relAddr)
+ {
+ super.setAttribute(REL_ADDR_ATTR_NAME, relAddr);
+ }
+
+ /**
+ * Returns this candidate's related address as described by ICE's RFC 5245.
+ *
+ * @return this candidate's related address as described by ICE's RFC 5245.
+ */
+ public String getRelAddr()
+ {
+ return super.getAttributeAsString(REL_ADDR_ATTR_NAME);
+ }
+
+ /**
+ * Sets this candidate's related port as described by ICE's RFC 5245.
+ *
+ * @param relPort this candidate's related port as described by ICE's
+ * RFC 5245.
+ */
+ public void setRelPort(int relPort)
+ {
+ super.setAttribute(REL_PORT_ATTR_NAME, relPort);
+ }
+
+ /**
+ * Returns this candidate's related port as described by ICE's RFC 5245.
+ *
+ * @return this candidate's related port as described by ICE's RFC 5245.
+ */
+ public int getRelPort()
+ {
+ return super.getAttributeAsInt(REL_PORT_ATTR_NAME);
+ }
+
+ /**
+ * Sets a Candidate Type as defined in ICE-CORE. The allowable values are
+ * "host" for host candidates, "prflx" for peer reflexive candidates,
+ * "relay" for relayed candidates, and "srflx" for server reflexive
+ * candidates. All allowable values are enumerated in the {@link
+ * CandidateType} enum.
+ *
+ * @param type this candidates' type as per ICE's RFC 5245.
+ */
+ public void setType(CandidateType type)
+ {
+ super.setAttribute(TYPE_ATTR_NAME, type);
+ }
+
+ /**
+ * Returns a Candidate Type as defined in ICE-CORE. The allowable values are
+ * "host" for host candidates, "prflx" for peer reflexive candidates,
+ * "relay" for relayed candidates, and "srflx" for server reflexive
+ * candidates. All allowable values are enumerated in the {@link
+ * CandidateType} enum.
+ *
+ * @return this candidates' type as per ICE's RFC 5245.
+ */
+ public CandidateType getType()
+ {
+ return CandidateType.valueOf(getAttributeAsString(TYPE_ATTR_NAME));
+ }
+
+ /**
+ * Compares this instance with another CandidatePacketExtension by
+ * preference of type: host < local < prflx < srflx < stun < relay.
+ *
+ * @return 0 if the type are equal. -1 if this instance type is preferred.
+ * Otherwise 1.
+ */
+ public int compareTo(CandidatePacketExtension candidatePacketExtension)
+ {
+ // If the types are different.
+ if(this.getType() != candidatePacketExtension.getType())
+ {
+ CandidateType[] types = {
+ CandidateType.host,
+ CandidateType.local,
+ CandidateType.prflx,
+ CandidateType.srflx,
+ CandidateType.stun,
+ CandidateType.relay
+ };
+ for(int i = 0; i < types.length; ++i)
+ {
+ // this object is preferred.
+ if(types[i] == this.getType())
+ {
+ return -1;
+ }
+ // the candidatePacketExtension is preferred.
+ else if(types[i] == candidatePacketExtension.getType())
+ {
+ return 1;
+ }
+ }
+ }
+ // If the types are equal.
+ return 0;
+ }
+
+ /**
+ * Gets the TCP type for this <tt>CandidatePacketExtension</tt>.
+ */
+ public CandidateTcpType getTcpType()
+ {
+ String tcpTypeString = getAttributeAsString(TCPTYPE_ATTR_NAME);
+ try
+ {
+ return CandidateTcpType.parse(tcpTypeString);
+ }
+ catch (IllegalArgumentException iae)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the TCP type for this <tt>CandidatePacketExtension</tt>.
+ * @param tcpType
+ */
+ public void setTcpType(CandidateTcpType tcpType)
+ {
+ setAttribute(TCPTYPE_ATTR_NAME, tcpType.toString());
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java
index 69b2e47..3ccbd72 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/CryptoPacketExtension.java
@@ -17,6 +17,8 @@
*/
package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle;
+import java.util.Objects;
+
import ch.imvs.sdes4j.srtp.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
@@ -332,4 +334,14 @@ public class CryptoPacketExtension
}
return false;
}
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(
+ getCryptoSuite(),
+ getKeyParams(),
+ getSessionParams(),
+ getTag());
+ }
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java
index 1112c8c..173701f 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java
@@ -191,6 +191,27 @@ public class IceUdpTransportPacketExtension
}
/**
+ * Removes given <tt>PacketExtension</tt> from the list of child packet
+ * extensions. <tt>CandidatePacketExtension</tt> are not taken into account
+ * in this method and {@link #removeCandidate(CandidatePacketExtension)}
+ * should be used instead.
+ *
+ * @param childExtension <tt>PacketExtension</tt> instance to be removed
+ * from child packet extensions list.
+ *
+ * @return <tt>true</tt> if given <tt>childExtension</tt> has been in the
+ * list and was removed or <tt>false</tt> otherwise.
+ */
+ public boolean removeChildExtension(PacketExtension childExtension)
+ {
+ List<? extends PacketExtension> childExtensions
+ = super.getChildExtensions();
+
+ return childExtensions != null
+ && childExtensions.remove(childExtension);
+ }
+
+ /**
* Returns the list of {@link CandidatePacketExtension}s currently
* registered with this transport.
*
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java
index 9183fa0..65b0849 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQ.java
@@ -21,6 +21,7 @@ import java.math.*;
import java.security.*;
import java.util.*;
+import net.java.sip.communicator.service.protocol.jabber.*;
import org.jivesoftware.smack.packet.*;
/**
@@ -62,7 +63,8 @@ public class JingleIQ extends IQ
* The name of the argument that contains the session id.
*/
public static final String SID_ATTR_NAME = "sid";
-
+
+
/**
* The <tt>JingleAction</tt> that describes the purpose of this
* <tt>jingle</tt> element.
@@ -104,8 +106,8 @@ public class JingleIQ extends IQ
* The list of "content" elements included in this IQ.
*/
private final List<ContentPacketExtension> contentList
- = new ArrayList<ContentPacketExtension>();
-
+ = new ArrayList<ContentPacketExtension>();
+
/**
* Returns the XML string of this Jingle IQ's "section" sub-element.
*
@@ -124,17 +126,18 @@ public class JingleIQ extends IQ
if( initiator != null)
bldr.append(" " + INITIATOR_ATTR_NAME
- + "='" + getInitiator() + "'");
+ + "='" + getInitiator() + "'");
if( responder != null)
bldr.append(" " + RESPONDER_ATTR_NAME
- + "='" + getResponder() + "'");
+ + "='" + getResponder() + "'");
bldr.append(" " + SID_ATTR_NAME
- + "='" + getSID() + "'");
-
- String extensionsXML = getExtensionsXML();
+ + "='" + getSID() + "'");
+ CharSequence extensionsXMLSeq = getExtensionsXML();
+ String extensionsXML = extensionsXMLSeq.toString();
+
if ((contentList.size() == 0)
&& (reason == null)
&& (sessionInfo == null)
@@ -348,7 +351,7 @@ public class JingleIQ extends IQ
* otherwise.
*/
public boolean containsContentChildOfType(
- Class<? extends PacketExtension> contentType)
+ Class<? extends PacketExtension> contentType)
{
if(getContentForType(contentType) != null)
return true;
@@ -369,14 +372,14 @@ public class JingleIQ extends IQ
* found.
*/
public ContentPacketExtension getContentForType(
- Class<? extends PacketExtension> contentType)
+ Class<? extends PacketExtension> contentType)
{
synchronized(contentList)
{
for(ContentPacketExtension content : contentList)
{
PacketExtension child
- = content.getFirstChildOfType(contentType);
+ = content.getFirstChildOfType(contentType);
if(child != null)
return content;
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java
index 11f909a..49934e5 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java
@@ -20,6 +20,7 @@ package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle;
import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet.*;
+import net.java.sip.communicator.service.protocol.jabber.*;
import org.jivesoftware.smack.provider.*;
import org.xmlpull.v1.*;
@@ -37,174 +38,187 @@ public class JingleIQProvider implements IQProvider
*/
public JingleIQProvider()
{
- ProviderManager providerManager = ProviderManager.getInstance();
+
+ AbstractSmackInteroperabilityLayer smackInteroperabilityLayer =
+ AbstractSmackInteroperabilityLayer.getInstance();
//<description/> provider
- providerManager.addExtensionProvider(
- RtpDescriptionPacketExtension.ELEMENT_NAME,
- RtpDescriptionPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <RtpDescriptionPacketExtension>(
- RtpDescriptionPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ RtpDescriptionPacketExtension.ELEMENT_NAME,
+ RtpDescriptionPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <RtpDescriptionPacketExtension>(
+ RtpDescriptionPacketExtension.class));
//<payload-type/> provider
- providerManager.addExtensionProvider(
- PayloadTypePacketExtension.ELEMENT_NAME,
- RtpDescriptionPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <PayloadTypePacketExtension>(
- PayloadTypePacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ PayloadTypePacketExtension.ELEMENT_NAME,
+ RtpDescriptionPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <PayloadTypePacketExtension>(
+ PayloadTypePacketExtension.class));
//<parameter/> provider
- providerManager.addExtensionProvider(
- ParameterPacketExtension.ELEMENT_NAME,
- RtpDescriptionPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <ParameterPacketExtension>(ParameterPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ ParameterPacketExtension.ELEMENT_NAME,
+ RtpDescriptionPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <ParameterPacketExtension>
+ (ParameterPacketExtension.class));
//<rtp-hdrext/> provider
- providerManager.addExtensionProvider(
- RTPHdrExtPacketExtension.ELEMENT_NAME,
- RTPHdrExtPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <RTPHdrExtPacketExtension>(RTPHdrExtPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ RTPHdrExtPacketExtension.ELEMENT_NAME,
+ RTPHdrExtPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <RTPHdrExtPacketExtension>
+ (RTPHdrExtPacketExtension.class));
// <sctpmap/> provider
- providerManager.addExtensionProvider(
- SctpMapExtension.ELEMENT_NAME,
- SctpMapExtension.NAMESPACE,
- new SctpMapExtensionProvider());
+ smackInteroperabilityLayer.addExtensionProvider(
+ SctpMapExtension.ELEMENT_NAME,
+ SctpMapExtension.NAMESPACE,
+ new SctpMapExtensionProvider());
//<encryption/> provider
- providerManager.addExtensionProvider(
- EncryptionPacketExtension.ELEMENT_NAME,
- RtpDescriptionPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <EncryptionPacketExtension>(EncryptionPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ EncryptionPacketExtension.ELEMENT_NAME,
+ RtpDescriptionPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <EncryptionPacketExtension>
+ (EncryptionPacketExtension.class));
//<zrtp-hash/> provider
- providerManager.addExtensionProvider(
- ZrtpHashPacketExtension.ELEMENT_NAME,
- ZrtpHashPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <ZrtpHashPacketExtension>(ZrtpHashPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ ZrtpHashPacketExtension.ELEMENT_NAME,
+ ZrtpHashPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <ZrtpHashPacketExtension>
+ (ZrtpHashPacketExtension.class));
//<crypto/> provider
- providerManager.addExtensionProvider(
- CryptoPacketExtension.ELEMENT_NAME,
- RtpDescriptionPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <CryptoPacketExtension>(CryptoPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ CryptoPacketExtension.ELEMENT_NAME,
+ RtpDescriptionPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <CryptoPacketExtension>
+ (CryptoPacketExtension.class));
// <bundle/> provider
- providerManager.addExtensionProvider(
- BundlePacketExtension.ELEMENT_NAME,
- BundlePacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <BundlePacketExtension>(BundlePacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ BundlePacketExtension.ELEMENT_NAME,
+ BundlePacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <BundlePacketExtension>
+ (BundlePacketExtension.class));
// <group/> provider
- providerManager.addExtensionProvider(
- GroupPacketExtension.ELEMENT_NAME,
- GroupPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider
- <GroupPacketExtension>(GroupPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ GroupPacketExtension.ELEMENT_NAME,
+ GroupPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <GroupPacketExtension>(GroupPacketExtension.class));
//ice-udp transport
- providerManager.addExtensionProvider(
- IceUdpTransportPacketExtension.ELEMENT_NAME,
- IceUdpTransportPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider<IceUdpTransportPacketExtension>(
- IceUdpTransportPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ IceUdpTransportPacketExtension.ELEMENT_NAME,
+ IceUdpTransportPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <IceUdpTransportPacketExtension>(
+ IceUdpTransportPacketExtension.class));
//<raw-udp/> provider
- providerManager.addExtensionProvider(
- RawUdpTransportPacketExtension.ELEMENT_NAME,
- RawUdpTransportPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider<RawUdpTransportPacketExtension>(
- RawUdpTransportPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ RawUdpTransportPacketExtension.ELEMENT_NAME,
+ RawUdpTransportPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <RawUdpTransportPacketExtension>(
+ RawUdpTransportPacketExtension.class));
//ice-udp <candidate/> provider
- providerManager.addExtensionProvider(
- CandidatePacketExtension.ELEMENT_NAME,
- IceUdpTransportPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider<CandidatePacketExtension>(
- CandidatePacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ CandidatePacketExtension.ELEMENT_NAME,
+ IceUdpTransportPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <CandidatePacketExtension>(
+ CandidatePacketExtension.class));
//raw-udp <candidate/> provider
- providerManager.addExtensionProvider(
- CandidatePacketExtension.ELEMENT_NAME,
- RawUdpTransportPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider<CandidatePacketExtension>(
- CandidatePacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ CandidatePacketExtension.ELEMENT_NAME,
+ RawUdpTransportPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <CandidatePacketExtension>(
+ CandidatePacketExtension.class));
//ice-udp <remote-candidate/> provider
- providerManager.addExtensionProvider(
- RemoteCandidatePacketExtension.ELEMENT_NAME,
- IceUdpTransportPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider<RemoteCandidatePacketExtension>(
- RemoteCandidatePacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ RemoteCandidatePacketExtension.ELEMENT_NAME,
+ IceUdpTransportPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider
+ <RemoteCandidatePacketExtension>(
+ RemoteCandidatePacketExtension.class));
//inputevt <inputevt/> provider
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
InputEvtPacketExtension.ELEMENT_NAME,
InputEvtPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<InputEvtPacketExtension>(
InputEvtPacketExtension.class));
//coin <conference-info/> provider
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
CoinPacketExtension.ELEMENT_NAME,
CoinPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<CoinPacketExtension>(
CoinPacketExtension.class));
// DTLS-SRTP
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
DtlsFingerprintPacketExtension.ELEMENT_NAME,
DtlsFingerprintPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider
- <DtlsFingerprintPacketExtension>(
+ <DtlsFingerprintPacketExtension>(
DtlsFingerprintPacketExtension.class));
/*
* XEP-0251: Jingle Session Transfer <transfer/> and <transferred>
* providers
*/
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
TransferPacketExtension.ELEMENT_NAME,
TransferPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<TransferPacketExtension>(
TransferPacketExtension.class));
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
TransferredPacketExtension.ELEMENT_NAME,
TransferredPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<TransferredPacketExtension>(
TransferredPacketExtension.class));
//conference description <callid/> provider
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
ConferenceDescriptionPacketExtension.CALLID_ELEM_NAME,
ConferenceDescriptionPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<CallIdPacketExtension>(
CallIdPacketExtension.class));
//rtcp-fb
- providerManager.addExtensionProvider(
- RtcpFbPacketExtension.ELEMENT_NAME,
- RtcpFbPacketExtension.NAMESPACE,
- new DefaultPacketExtensionProvider<RtcpFbPacketExtension>(
- RtcpFbPacketExtension.class));
+ smackInteroperabilityLayer.addExtensionProvider(
+ RtcpFbPacketExtension.ELEMENT_NAME,
+ RtcpFbPacketExtension.NAMESPACE,
+ new DefaultPacketExtensionProvider<RtcpFbPacketExtension>(
+ RtcpFbPacketExtension.class));
//rtcp-mux
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
RtcpmuxPacketExtension.ELEMENT_NAME,
IceUdpTransportPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<RtcpmuxPacketExtension>(
RtcpmuxPacketExtension.class));
//ssrcInfo
- providerManager.addExtensionProvider(
+ smackInteroperabilityLayer.addExtensionProvider(
SSRCInfoPacketExtension.ELEMENT_NAME,
SSRCInfoPacketExtension.NAMESPACE,
new DefaultPacketExtensionProvider<SSRCInfoPacketExtension>(
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java
new file mode 100644
index 0000000..bcc3397
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/ComponentVersionsExtension.java
@@ -0,0 +1,135 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+
+/**
+ * The packet extension is used by Jicofo to broadcast versions of all video
+ * conferencing system components. This packets extension is added to jicofo's
+ * MUC presence. It will contain {@link Component} children which carry each
+ * component's name and version.
+ *
+ * @author Pawel Domas
+ */
+public class ComponentVersionsExtension
+ extends AbstractPacketExtension
+{
+ /**
+ * The XML element name of {@link ComponentVersionsExtension}.
+ */
+ public static final String ELEMENT_NAME = "versions";
+
+ /**
+ * The name of XML sub-elements which carry the info about particular
+ * component's version.
+ */
+ public static final String COMPONENT_ELEMENT_NAME = "component";
+
+ /**
+ * Constant for {@link Component} name used to signal the version of
+ * conference focus.
+ */
+ public static final String COMPONENT_FOCUS = "focus";
+
+ /**
+ * Constant for {@link Component} name used to signal the version of
+ * XMPP server.
+ */
+ public static final String COMPONENT_XMPP_SERVER = "xmpp";
+
+ /**
+ * Constant for {@link Component} name used to signal the version of
+ * the videobridge.
+ */
+ public static final String COMPONENT_VIDEOBRIDGE = "videobridge";
+
+ /**
+ * The XML element namespace of {@link ComponentVersionsExtension}.
+ */
+ public static final String NAMESPACE = "http://jitsi.org/jitmeet";
+
+ /**
+ * Creates an {@link AbstractPacketExtension} instance for the specified
+ * <tt>namespace</tt> and <tt>elementName</tt>.
+ */
+ public ComponentVersionsExtension()
+ {
+ super(NAMESPACE, ELEMENT_NAME);
+ }
+
+ /**
+ * Adds component's version to this extension.
+ *
+ * @param componentName the name of the component for which
+ * child {@link Component} extension will be added.
+ * @param versionStr human readable string that describes component's
+ * version.
+ */
+ public void addComponentVersion(String componentName, String versionStr)
+ {
+ Component v = new Component();
+
+ v.setName(componentName);
+ v.setText(versionStr);
+
+ addChildExtension(v);
+ }
+
+ /**
+ * Component child element of {@link ComponentVersionsExtension}. The name
+ * of the component is carried in name attribute and the version string is
+ * the text value.
+ */
+ public class Component
+ extends AbstractPacketExtension
+ {
+ /**
+ * The name of that attribute that carries component's name.
+ */
+ private final String NAME_ATTR_NAME = "name";
+
+ /**
+ * Creates new instance of {@link Component} packet extension.
+ */
+ public Component()
+ {
+ super(NAMESPACE, COMPONENT_ELEMENT_NAME);
+ }
+
+ /**
+ * Returns the value of the name attribute.
+ * @return <tt>String</tt> which describes the name of video
+ * conferencing system component.
+ */
+ public String getName()
+ {
+ return getAttributeAsString(NAME_ATTR_NAME);
+ }
+
+ /**
+ * Sets new value for the component's name attribute.
+ * @param name a <tt>String</tt> which describes the name of video
+ * conferencing system component.
+ */
+ public void setName(String name)
+ {
+ setAttribute(NAME_ATTR_NAME, name);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java
index 239c708..7384c9e 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/SSRCInfoPacketExtension.java
@@ -1,8 +1,19 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet;
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java
new file mode 100644
index 0000000..cf76d89
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/VideoMutedExtension.java
@@ -0,0 +1,70 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+
+/**
+ * Video muted extension that is included in users presence in Jitsi-meet
+ * conferences. It does carry the info about user's video muted status.
+ *
+ * @author Pawel Domas
+ */
+public class VideoMutedExtension
+ extends AbstractPacketExtension
+{
+ /**
+ * The namespace of this packet extension.
+ */
+ public static final String NAMESPACE = "http://jitsi.org/jitmeet/video";
+
+ /**
+ * XML element name of this packet extension.
+ */
+ public static final String ELEMENT_NAME = "videomuted";
+
+ /**
+ * Creates new instance of <tt>VideoMutedExtension</tt>.
+ */
+ public VideoMutedExtension()
+ {
+ super(NAMESPACE, ELEMENT_NAME);
+ }
+
+ /**
+ * Check whether or not user's video is in muted status.
+ * @return <tt>true</tt> if muted, <tt>false</tt> if unmuted or
+ * <tt>null</tt> if no valid info found in the extension body.
+ */
+ public Boolean isVideoMuted()
+ {
+ return Boolean.valueOf(getText());
+ }
+
+ /**
+ * Sets user's video muted status.
+ *
+ * @param videoMuted <tt>true</tt> or <tt>false</tt> which indicates video
+ * muted status of the user.
+ */
+ public void setVideoMuted(Boolean videoMuted)
+ {
+ setText(
+ String.valueOf(videoMuted));
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java b/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java
index 93cdead..8094f68 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/jinglesdp/JingleUtils.java
@@ -179,12 +179,17 @@ public class JingleUtils
else
paramsMap.put(paramName, param.getValue());
}
-
- // video-related attributes in payload-type element
+
for(String attr : payloadType.getAttributeNames())
{
+ //video-related attributes in payload-type element
if(attr.equals("width") || attr.equals("height"))
paramsMap.put(attr, payloadType.getAttributeAsString(attr));
+
+ //update ptime with the actual value from the payload
+ if (attr.equals(PayloadTypePacketExtension.PTIME_ATTR_NAME))
+ advancedMap.put(PayloadTypePacketExtension.PTIME_ATTR_NAME,
+ Integer.toString(payloadType.getPtime()));
}
//now create the format.