diff options
Diffstat (limited to 'src/net/java/sip/communicator/impl/protocol/jabber')
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. |