/** * * Copyright the original author or authors * * 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 org.jivesoftware.smackx.jingleold; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.AbstractConnectionClosedListener; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smackx.jingleold.listeners.JingleListener; import org.jivesoftware.smackx.jingleold.listeners.JingleMediaListener; import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener; import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener; import org.jivesoftware.smackx.jingleold.media.JingleMediaManager; import org.jivesoftware.smackx.jingleold.media.JingleMediaSession; import org.jivesoftware.smackx.jingleold.media.MediaNegotiator; import org.jivesoftware.smackx.jingleold.media.MediaReceivedListener; import org.jivesoftware.smackx.jingleold.media.PayloadType; import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager; import org.jivesoftware.smackx.jingleold.nat.TransportCandidate; import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator; import org.jivesoftware.smackx.jingleold.nat.TransportResolver; import org.jivesoftware.smackx.jingleold.packet.Jingle; import org.jivesoftware.smackx.jingleold.packet.JingleError; import org.jxmpp.jid.Jid; /** * An abstract Jingle session. This class contains some basic properties of * every Jingle session. However, the concrete implementation can be found in * subclasses. * * @author Alvaro Saurin * @author Jeff Williams */ public final class JingleSession extends JingleNegotiator implements MediaReceivedListener { private static final Logger LOGGER = Logger.getLogger(JingleSession.class.getName()); // static private static final HashMap sessions = new HashMap<>(); private static final Random randomGenerator = new Random(); // non-static private Jid initiator; // Who started the communication private Jid responder; // The other endpoint private String sid; // A unique id that identifies this session private ConnectionListener connectionListener; private StanzaListener packetListener; private StanzaFilter packetFilter; protected List jingleMediaManagers = null; private JingleSessionState sessionState; private final List contentNegotiators; private final XMPPConnection connection; private String sessionInitPacketID; private final Map mediaSessionMap; /** * Full featured JingleSession constructor. * * @param conn * the XMPPConnection which is used * @param initiator * the initiator JID * @param responder * the responder JID * @param sessionid * the session ID * @param jingleMediaManagers * the jingleMediaManager */ public JingleSession(XMPPConnection conn, Jid initiator, Jid responder, String sessionid, List jingleMediaManagers) { super(); this.initiator = initiator; this.responder = responder; this.sid = sessionid; this.jingleMediaManagers = jingleMediaManagers; this.setSession(this); this.connection = conn; // Initially, we don't known the session state. setSessionState(JingleSessionStateUnknown.getInstance()); contentNegotiators = new ArrayList<>(); mediaSessionMap = new HashMap<>(); // Add the session to the list and register the listeners registerInstance(); installConnectionListeners(conn); } /** * JingleSession constructor (for an outgoing Jingle session). * * @param conn * Connection * @param initiator * the initiator JID * @param responder * the responder JID * @param jingleMediaManagers * the jingleMediaManager */ public JingleSession(XMPPConnection conn, JingleSessionRequest request, Jid initiator, Jid responder, List jingleMediaManagers) { this(conn, initiator, responder, generateSessionId(), jingleMediaManagers); // sessionRequest = request; // unused } /** * Get the session initiator. * * @return the initiator */ public Jid getInitiator() { return initiator; } @Override public XMPPConnection getConnection() { return connection; } /** * Set the session initiator. * * @param initiator * the initiator to set */ public void setInitiator(Jid initiator) { this.initiator = initiator; } /** * Get the Media Manager of this Jingle Session. * * @return the JingleMediaManagers */ public List getMediaManagers() { return jingleMediaManagers; } /** * Set the Media Manager of this Jingle Session. * * @param jingleMediaManagers */ public void setMediaManagers(List jingleMediaManagers) { this.jingleMediaManagers = jingleMediaManagers; } /** * Get the session responder. * * @return the responder */ public Jid getResponder() { return responder; } /** * Set the session responder. * * @param responder * the receptor to set */ public void setResponder(Jid responder) { this.responder = responder; } /** * Get the session ID. * * @return the sid */ public String getSid() { return sid; } /** * Set the session ID * * @param sessionId * the sid to set */ protected void setSid(String sessionId) { sid = sessionId; } /** * Generate a unique session ID. */ protected static String generateSessionId() { return String.valueOf(randomGenerator.nextInt(Integer.MAX_VALUE) + randomGenerator.nextInt(Integer.MAX_VALUE)); } /** * Validate the state changes. */ public void setSessionState(JingleSessionState stateIs) { LOGGER.fine("Session state change: " + sessionState + "->" + stateIs); stateIs.enter(); sessionState = stateIs; } public JingleSessionState getSessionState() { return sessionState; } /** * Return true if all of the media managers have finished. */ public boolean isFullyEstablished() { boolean result = true; for (ContentNegotiator contentNegotiator : contentNegotiators) { if (!contentNegotiator.isFullyEstablished()) result = false; } return result; } // ---------------------------------------------------------------------------------------------------------- // Receive section // ---------------------------------------------------------------------------------------------------------- /** * Process and respond to an incoming packet. This method is called * from the stanza listener dispatcher when a new stanza has arrived. The * method is responsible for recognizing the stanza type and, depending on * the current state, delivering it to the right event handler and wait for * a response. The response will be another Jingle stanza that will be sent * to the other end point. * * @param iq * the stanza received * @throws XMPPException * @throws SmackException * @throws InterruptedException */ public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException, SmackException, InterruptedException { List responses = new ArrayList<>(); String responseId; LOGGER.fine("Packet: " + iq.toXML(null)); try { // Dispatch the packet to the JingleNegotiators and get back a list of the results. responses.addAll(dispatchIncomingPacket(iq, null)); if (iq != null) { responseId = iq.getStanzaId(); // Send the IQ to each of the content negotiators for further processing. // Each content negotiator may pass back a list of JingleContent for addition to the response packet. // CHECKSTYLE:OFF for (ContentNegotiator contentNegotiator : contentNegotiators) { // If at this point the content negotiator isn't started, it's because we sent a session-init jingle // packet from startOutgoing() and we're waiting for the other side to let us know they're ready // to take jingle packets. (This packet might be a session-terminate, but that will get handled // later. if (!contentNegotiator.isStarted()) { contentNegotiator.start(); } responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId)); } // CHECKSTYLE:ON } // Acknowledge the IQ reception // Not anymore. The state machine generates an appropriate response IQ that // gets sent back at the end of this routine. // sendAck(iq); } catch (JingleException e) { // Send an error message, if present JingleError error = e.getError(); if (error != null) { responses.add(createJingleError(iq, error)); } // Notify the session end and close everything... triggerSessionClosedOnError(e); } // // If the response is anything other than a RESULT then send it now. // if ((response != null) && (!response.getType().equals(IQ.Type.result))) { // getConnection().sendStanza(response); // } // Loop through all of the responses and send them. for (IQ response : responses) { sendStanza(response); } } /** * Dispatch an incoming packet. The method is responsible for recognizing * the stanza type and, depending on the current state, delivering the * stanza to the right event handler and wait for a response. * * @param iq * the stanza received * @return the new Jingle stanza to send. * @throws XMPPException * @throws SmackException * @throws InterruptedException */ @Override public List dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException { List responses = new ArrayList<>(); IQ response = null; if (iq != null) { if (iq.getType().equals(IQ.Type.error)) { // Process errors // TODO getState().eventError(iq); } else if (iq.getType().equals(IQ.Type.result)) { // Process ACKs if (isExpectedId(iq.getStanzaId())) { // The other side provisionally accepted our session-initiate. // Kick off some negotiators. if (iq.getStanzaId().equals(sessionInitPacketID)) { startNegotiators(); } removeExpectedId(iq.getStanzaId()); } } else if (iq instanceof Jingle) { // It is not an error: it is a Jingle packet... Jingle jin = (Jingle) iq; JingleActionEnum action = jin.getAction(); // Depending on the state we're in we'll get different processing actions. // (See Design Patterns AKA GoF State behavioral pattern.) response = getSessionState().processJingle(this, jin, action); } } if (response != null) { // Save the packet id, for recognizing ACKs... addExpectedId(response.getStanzaId()); responses.add(response); } return responses; } /** * Add a new content negotiator on behalf of a <content/> section received. */ public void addContentNegotiator(ContentNegotiator inContentNegotiator) { contentNegotiators.add(inContentNegotiator); } // ---------------------------------------------------------------------------------------------------------- // Send section // ---------------------------------------------------------------------------------------------------------- public void sendStanza(IQ iq) throws NotConnectedException, InterruptedException { if (iq instanceof Jingle) { sendFormattedJingle((Jingle) iq); } else { getConnection().sendStanza(iq); } } /** * Complete and send a packet. Complete all the null fields in a Jingle * reponse, using the session information we have. * * @param jout * the Jingle stanza we want to complete and send * @throws NotConnectedException * @throws InterruptedException */ public Jingle sendFormattedJingle(Jingle jout) throws NotConnectedException, InterruptedException { return sendFormattedJingle(null, jout); } /** * Complete and send a packet. Complete all the null fields in a Jingle * reponse, using the session information we have or some info from the * incoming packet. * * @param iq * The Jingle stanza we are responding to * @param jout * the Jingle stanza we want to complete and send * @throws NotConnectedException * @throws InterruptedException */ public Jingle sendFormattedJingle(IQ iq, Jingle jout) throws NotConnectedException, InterruptedException { if (jout != null) { if (jout.getInitiator() == null) { jout.setInitiator(getInitiator()); } if (jout.getResponder() == null) { jout.setResponder(getResponder()); } if (jout.getSid() == null) { jout.setSid(getSid()); } Jid me = getConnection().getUser(); Jid other = getResponder().equals(me) ? getInitiator() : getResponder(); if (jout.getTo() == null) { if (iq != null) { jout.setTo(iq.getFrom()); } else { jout.setTo(other); } } if (jout.getFrom() == null) { if (iq != null) { jout.setFrom(iq.getTo()); } else { jout.setFrom(me); } } // The the packet. // CHECKSTYLE:OFF if ((getConnection() != null) && getConnection().isConnected()) getConnection().sendStanza(jout); // CHECKSTYLE:ON } return jout; } /** * @param inJingle * @param inAction */ // private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) { // // if (inAction == JingleActionEnum.SESSION_INITIATE) { // // Prepare to receive and act on response packets. // updatePacketListener(); // // // Send the actual packet. // sendStanza(inJingle); // // // Change to the PENDING state. // setSessionState(JingleSessionStateEnum.PENDING); // } else { // throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state."); // } // } /** * Acknowledge a IQ packet. * * @param iq * The IQ to acknowledge */ public IQ createAck(IQ iq) { IQ result = null; if (iq != null) { // Don't acknowledge ACKs, errors... if (iq.getType().equals(IQ.Type.set)) { IQ ack = IQ.createResultIQ(iq); // No! Don't send it. Let it flow to the normal way IQ results get processed and sent. // getConnection().sendStanza(ack); result = ack; } } return result; } /** * Send a content info message. */ // public synchronized void sendContentInfo(ContentInfo ci) { // sendStanza(new Jingle(new JingleContentInfo(ci))); // } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return Jingle.getSessionHash(getSid(), getInitiator()); } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final JingleSession other = (JingleSession) obj; if (initiator == null) { if (other.initiator != null) { return false; } } else if (!initiator.equals(other.initiator)) { // Todo check behavior // return false; } if (responder == null) { if (other.responder != null) { return false; } } else if (!responder.equals(other.responder)) { return false; } if (sid == null) { if (other.sid != null) { return false; } } else if (!sid.equals(other.sid)) { return false; } return true; } // Instances management /** * Clean a session from the list. * * @param connection * The connection to clean up */ private void unregisterInstanceFor(XMPPConnection connection) { synchronized (sessions) { sessions.remove(connection); } } /** * Register this instance. */ private void registerInstance() { synchronized (sessions) { sessions.put(getConnection(), this); } } /** * Returns the JingleSession related to a particular connection. * * @param con * A XMPP connection * @return a Jingle session */ public static JingleSession getInstanceFor(XMPPConnection con) { if (con == null) { throw new IllegalArgumentException("XMPPConnection cannot be null"); } JingleSession result = null; synchronized (sessions) { if (sessions.containsKey(con)) { result = sessions.get(con); } } return result; } /** * Configure a session, setting some action listeners... * * @param connection * The connection to set up */ private void installConnectionListeners(final XMPPConnection connection) { if (connection != null) { connectionListener = new AbstractConnectionClosedListener() { @Override public void connectionTerminated() { unregisterInstanceFor(connection); } }; connection.addConnectionListener(connectionListener); } } private void removeConnectionListener() { // CHECKSTYLE:OFF if (connectionListener != null) { getConnection().removeConnectionListener(connectionListener); LOGGER.fine("JINGLE SESSION: REMOVE CONNECTION LISTENER"); } // CHECKSTYLE:ON } /** * Remove the stanza listener used for processing packet. */ protected void removeAsyncPacketListener() { if (packetListener != null) { getConnection().removeAsyncStanzaListener(packetListener); LOGGER.fine("JINGLE SESSION: REMOVE PACKET LISTENER"); } } /** * Install the stanza listener. The listener is responsible for responding * to any stanza that we receive... */ protected void updatePacketListener() { removeAsyncPacketListener(); LOGGER.fine("UpdatePacketListener"); packetListener = new StanzaListener() { @Override public void processStanza(Stanza packet) { try { receivePacketAndRespond((IQ) packet); } catch (Exception e) { LOGGER.log(Level.WARNING, "exception", e); } } }; packetFilter = new StanzaFilter() { @Override public boolean accept(Stanza packet) { if (packet instanceof IQ) { IQ iq = (IQ) packet; Jid me = getConnection().getUser(); if (!iq.getTo().equals(me)) { return false; } Jid other = getResponder().equals(me) ? getInitiator() : getResponder(); if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) { return false; } if (iq instanceof Jingle) { Jingle jin = (Jingle) iq; String sid = jin.getSid(); if (sid == null || !sid.equals(getSid())) { LOGGER.fine("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML(null)); return false; } Jid ini = jin.getInitiator(); if (!ini.equals(getInitiator())) { LOGGER.fine("Ignored Jingle(INI): " + iq.toXML(null)); return false; } } else { // We accept some non-Jingle IQ packets: ERRORs and ACKs if (iq.getType().equals(IQ.Type.set)) { LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML(null)); return false; } else if (iq.getType().equals(IQ.Type.get)) { LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML(null)); return false; } } return true; } return false; } }; getConnection().addAsyncStanzaListener(packetListener, packetFilter); } // Listeners /** * Add a listener for jmf negotiation events. * * @param li * The listener */ public void addMediaListener(JingleMediaListener li) { for (ContentNegotiator contentNegotiator : contentNegotiators) { if (contentNegotiator.getMediaNegotiator() != null) { contentNegotiator.getMediaNegotiator().addListener(li); } } } /** * Remove a listener for jmf negotiation events. * * @param li * The listener */ public void removeMediaListener(JingleMediaListener li) { for (ContentNegotiator contentNegotiator : contentNegotiators) { if (contentNegotiator.getMediaNegotiator() != null) { contentNegotiator.getMediaNegotiator().removeListener(li); } } } /** * Add a listener for transport negotiation events. * * @param li * The listener */ public void addTransportListener(JingleTransportListener li) { for (ContentNegotiator contentNegotiator : contentNegotiators) { if (contentNegotiator.getTransportNegotiator() != null) { contentNegotiator.getTransportNegotiator().addListener(li); } } } /** * Remove a listener for transport negotiation events. * * @param li * The listener */ public void removeTransportListener(JingleTransportListener li) { for (ContentNegotiator contentNegotiator : contentNegotiators) { if (contentNegotiator.getTransportNegotiator() != null) { contentNegotiator.getTransportNegotiator().removeListener(li); } } } /** * Setup the listeners that act on events coming from the lower level negotiators. */ public void setupListeners() { JingleMediaListener jingleMediaListener = new JingleMediaListener() { @Override public void mediaClosed(PayloadType cand) { } @Override public void mediaEstablished(PayloadType pt) throws NotConnectedException, InterruptedException { if (isFullyEstablished()) { Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT); // Build up a response packet from each media manager. for (ContentNegotiator contentNegotiator : contentNegotiators) { if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) jout.addContent(contentNegotiator.getJingleContent()); } // Send the "accept" and wait for the ACK addExpectedId(jout.getStanzaId()); sendStanza(jout); // triggerSessionEstablished(); } } }; JingleTransportListener jingleTransportListener = new JingleTransportListener() { @Override public void transportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException { if (isFullyEstablished()) { // CHECKSTYLE:OFF // Indicate that this session is active. setSessionState(JingleSessionStateActive.getInstance()); for (ContentNegotiator contentNegotiator : contentNegotiators) { // CHECKSTYLE:ON if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) contentNegotiator.triggerContentEstablished(); } if (getSessionState().equals(JingleSessionStatePending.getInstance())) { Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT); // Build up a response packet from each media manager. for (ContentNegotiator contentNegotiator : contentNegotiators) { if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) jout.addContent(contentNegotiator.getJingleContent()); } // Send the "accept" and wait for the ACK addExpectedId(jout.getStanzaId()); sendStanza(jout); } } } @Override public void transportClosed(TransportCandidate cand) { } @Override public void transportClosedOnError(XMPPException e) { } }; addMediaListener(jingleMediaListener); addTransportListener(jingleTransportListener); } // Triggers /** * Trigger a session closed event. */ protected void triggerSessionClosed(String reason) { // for (ContentNegotiator contentNegotiator : contentNegotiators) { // // contentNegotiator.stopJingleMediaSession(); // // for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) // candidate.removeCandidateEcho(); // } List listeners = getListenersList(); for (JingleListener li : listeners) { if (li instanceof JingleSessionListener) { JingleSessionListener sli = (JingleSessionListener) li; sli.sessionClosed(reason, this); } } close(); } /** * Trigger a session closed event due to an error. */ protected void triggerSessionClosedOnError(XMPPException exc) { for (ContentNegotiator contentNegotiator : contentNegotiators) { contentNegotiator.stopJingleMediaSession(); for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) candidate.removeCandidateEcho(); } List listeners = getListenersList(); for (JingleListener li : listeners) { if (li instanceof JingleSessionListener) { JingleSessionListener sli = (JingleSessionListener) li; sli.sessionClosedOnError(exc, this); } } close(); } /** * Trigger a session established event. */ // protected void triggerSessionEstablished() { // List listeners = getListenersList(); // for (JingleListener li : listeners) { // if (li instanceof JingleSessionListener) { // JingleSessionListener sli = (JingleSessionListener) li; // sli.sessionEstablished(this); // } // } // } /** * Trigger a media received event. */ protected void triggerMediaReceived(String participant) { List listeners = getListenersList(); for (JingleListener li : listeners) { if (li instanceof JingleSessionListener) { JingleSessionListener sli = (JingleSessionListener) li; sli.sessionMediaReceived(this, participant); } } } /** * Trigger a session redirect event. */ // protected void triggerSessionRedirect(String arg) { // List listeners = getListenersList(); // for (JingleListener li : listeners) { // if (li instanceof JingleSessionListener) { // JingleSessionListener sli = (JingleSessionListener) li; // sli.sessionRedirected(arg, this); // } // } // } /** * Trigger a session decline event. */ // protected void triggerSessionDeclined(String reason) { // List listeners = getListenersList(); // for (JingleListener li : listeners) { // if (li instanceof JingleSessionListener) { // JingleSessionListener sli = (JingleSessionListener) li; // sli.sessionDeclined(reason, this); // } // } // for (ContentNegotiator contentNegotiator : contentNegotiators) { // for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) // candidate.removeCandidateEcho(); // } // } /** * Terminates the session with default reason. * * @throws XMPPException * @throws NotConnectedException * @throws InterruptedException */ public void terminate() throws XMPPException, NotConnectedException, InterruptedException { terminate("Closed Locally"); } /** * Terminates the session with a custom reason. * * @throws XMPPException * @throws NotConnectedException * @throws InterruptedException */ public void terminate(String reason) throws XMPPException, NotConnectedException, InterruptedException { if (isClosed()) return; LOGGER.fine("Terminate " + reason); Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE); jout.setType(IQ.Type.set); sendStanza(jout); triggerSessionClosed(reason); } /** * Terminate negotiations. */ @Override public void close() { if (isClosed()) return; // Set the session state to ENDED. setSessionState(JingleSessionStateEnded.getInstance()); for (ContentNegotiator contentNegotiator : contentNegotiators) { contentNegotiator.stopJingleMediaSession(); for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) candidate.removeCandidateEcho(); contentNegotiator.close(); } removeAsyncPacketListener(); removeConnectionListener(); getConnection().removeConnectionListener(connectionListener); LOGGER.fine("Negotiation Closed: " + getConnection().getUser() + " " + sid); super.close(); } public boolean isClosed() { return getSessionState().equals(JingleSessionStateEnded.getInstance()); } // Packet and error creation /** * Complete and send an error. Complete all the null fields in an IQ error * response, using the session information we have or some info from the * incoming packet. * * @param iq * The Jingle stanza we are responding to * @param jingleError * the IQ stanza we want to complete and send */ public IQ createJingleError(IQ iq, JingleError jingleError) { IQ errorPacket = null; if (jingleError != null) { // TODO This is wrong according to XEP-166 ยง 10, but this jingle implementation is deprecated anyways StanzaError.Builder builder = StanzaError.getBuilder(StanzaError.Condition.undefined_condition); builder.addExtension(jingleError); errorPacket = IQ.createErrorResponse(iq, builder); // errorPacket.addExtension(jingleError); // NO! Let the normal state machinery do all of the sending. // getConnection().sendStanza(perror); LOGGER.severe("Error sent: " + errorPacket.toXML(null)); } return errorPacket; } /** * Called when new Media is received. */ @Override public void mediaReceived(String participant) { triggerMediaReceived(participant); } /** * This is the starting point for intitiating a new session. * * @throws IllegalStateException * @throws SmackException * @throws InterruptedException */ public void startOutgoing() throws IllegalStateException, SmackException, InterruptedException { updatePacketListener(); setSessionState(JingleSessionStatePending.getInstance()); Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE); // Create a content negotiator for each media manager on the session. for (JingleMediaManager mediaManager : getMediaManagers()) { ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName()); // Create the media negotiator for this content description. contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg)); JingleTransportManager transportManager = mediaManager.getTransportManager(); TransportResolver resolver = null; try { resolver = transportManager.getResolver(this); } catch (XMPPException e) { LOGGER.log(Level.WARNING, "exception", e); } if (resolver.getType().equals(TransportResolver.Type.rawupd)) { contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg)); } if (resolver.getType().equals(TransportResolver.Type.ice)) { contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg)); } addContentNegotiator(contentNeg); } // Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet. for (ContentNegotiator contentNegotiator : contentNegotiators) { jingle.addContent(contentNegotiator.getJingleContent()); } // Save the session-initiate packet ID, so that we can respond to it. sessionInitPacketID = jingle.getStanzaId(); sendStanza(jingle); // Now setup to track the media negotiators, so that we know when (if) to send a session-accept. setupListeners(); // Give each of the content negotiators a chance to start // and return a portion of the structure to make the Jingle packet. // Don't do this anymore. The problem is that the other side might not be ready. // Later when we receive our first jingle packet from the other side we'll fire-up the negotiators // before processing it. (See receivePacketAndRespond() above. // for (ContentNegotiator contentNegotiator : contentNegotiators) { // contentNegotiator.start(); // } } /** * This is the starting point for responding to a new session. */ public void startIncoming() { // updatePacketListener(); } @Override protected void doStart() { } /** * When we initiate a session we need to start a bunch of negotiators right after we receive the result * stanza for our session-initiate. This is where we start them. * */ private void startNegotiators() { for (ContentNegotiator contentNegotiator : contentNegotiators) { TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator(); transNeg.start(); } } /** * The jingle session may have one or more media managers that are trying to establish media sessions. * When the media manager succeeds in creating a media session is registers it with the session by the * media manager's static name. This routine is where the media manager does the registering. */ public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) { mediaSessionMap.put(mediaManagerName, mediaSession); } /** * The jingle session may have one or more media managers that are trying to establish media sessions. * When the media manager succeeds in creating a media session is registers it with the session by the * media manager's static name. This routine is where other objects can access the registered media sessions. * NB: If the media manager has not succeeded in establishing a media session then this could return null. */ public JingleMediaSession getMediaSession(String mediaManagerName) { return mediaSessionMap.get(mediaManagerName); } }