001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.jingle;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Random;
024import java.util.logging.Logger;
025
026import org.jivesoftware.smack.AbstractConnectionListener;
027import org.jivesoftware.smack.ConnectionListener;
028import org.jivesoftware.smack.PacketListener;
029import org.jivesoftware.smack.SmackException;
030import org.jivesoftware.smack.SmackException.NotConnectedException;
031import org.jivesoftware.smack.XMPPConnection;
032import org.jivesoftware.smack.XMPPException;
033import org.jivesoftware.smack.filter.PacketFilter;
034import org.jivesoftware.smack.packet.IQ;
035import org.jivesoftware.smack.packet.Packet;
036import org.jivesoftware.smack.packet.PacketExtension;
037import org.jivesoftware.smack.packet.XMPPError;
038import org.jivesoftware.smackx.jingle.listeners.JingleListener;
039import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
040import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
041import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
042import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
043import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
044import org.jivesoftware.smackx.jingle.media.MediaNegotiator;
045import org.jivesoftware.smackx.jingle.media.MediaReceivedListener;
046import org.jivesoftware.smackx.jingle.media.PayloadType;
047import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
048import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
049import org.jivesoftware.smackx.jingle.nat.TransportNegotiator;
050import org.jivesoftware.smackx.jingle.nat.TransportResolver;
051import org.jivesoftware.smackx.jingle.packet.Jingle;
052import org.jivesoftware.smackx.jingle.packet.JingleError;
053
054/**
055 * An abstract Jingle session. <p/> This class contains some basic properties of
056 * every Jingle session. However, the concrete implementation can be found in
057 * subclasses.
058 * 
059 * @author Alvaro Saurin
060 * @author Jeff Williams
061 */
062public class JingleSession extends JingleNegotiator implements MediaReceivedListener {
063
064        private static final Logger LOGGER = Logger.getLogger(JingleSession.class.getName());
065
066        // static
067    private static final HashMap<XMPPConnection, JingleSession> sessions = new HashMap<XMPPConnection, JingleSession>();
068
069    private static final Random randomGenerator = new Random();
070
071    // non-static
072
073    private String initiator; // Who started the communication
074
075    private String responder; // The other endpoint
076
077    private String sid; // A unique id that identifies this session
078
079    ConnectionListener connectionListener;
080    
081    PacketListener packetListener;
082
083    PacketFilter packetFilter;
084
085    protected List<JingleMediaManager> jingleMediaManagers = null;
086
087    private JingleSessionState sessionState;
088
089    private List<ContentNegotiator> contentNegotiators;
090
091    private XMPPConnection connection;
092
093    private String sessionInitPacketID;
094    
095    private Map<String, JingleMediaSession> mediaSessionMap;
096
097    /**
098     * Full featured JingleSession constructor
099     * 
100     * @param conn
101     *            the XMPPConnection which is used
102     * @param initiator
103     *            the initiator JID
104     * @param responder
105     *            the responder JID
106     * @param sessionid
107     *            the session ID
108     * @param jingleMediaManagers
109     *            the jingleMediaManager
110     */
111    public JingleSession(XMPPConnection conn, String initiator, String responder, String sessionid,
112            List<JingleMediaManager> jingleMediaManagers) {
113        super();
114
115        this.initiator = initiator;
116        this.responder = responder;
117        this.sid = sessionid;
118        this.jingleMediaManagers = jingleMediaManagers;
119        this.setSession(this);
120        this.connection = conn;
121
122        // Initially, we don't known the session state.
123        setSessionState(JingleSessionStateUnknown.getInstance());
124        
125        contentNegotiators = new ArrayList<ContentNegotiator>();
126        mediaSessionMap = new HashMap<String, JingleMediaSession>();
127
128        // Add the session to the list and register the listeneres
129        registerInstance();
130        installConnectionListeners(conn);
131    }
132
133    /**
134     * JingleSession constructor (for an outgoing Jingle session)
135     * 
136     * @param conn
137     *            Connection
138     * @param initiator
139     *            the initiator JID
140     * @param responder
141     *            the responder JID
142     * @param jingleMediaManagers
143     *            the jingleMediaManager
144     */
145    public JingleSession(XMPPConnection conn, JingleSessionRequest request, String initiator, String responder,
146            List<JingleMediaManager> jingleMediaManagers) {
147        this(conn, initiator, responder, generateSessionId(), jingleMediaManagers);
148        //sessionRequest = request; // unused
149    }
150
151    /**
152     * Get the session initiator
153     * 
154     * @return the initiator
155     */
156    public String getInitiator() {
157        return initiator;
158    }
159
160    public XMPPConnection getConnection() {
161        return connection;
162    }
163
164    /**
165     * Set the session initiator
166     * 
167     * @param initiator
168     *            the initiator to set
169     */
170    public void setInitiator(String initiator) {
171        this.initiator = initiator;
172    }
173
174    /**
175     * Get the Media Manager of this Jingle Session
176     * 
177     * @return the JingleMediaManagers
178     */
179    public List<JingleMediaManager> getMediaManagers() {
180        return jingleMediaManagers;
181    }
182
183    /**
184     * Set the Media Manager of this Jingle Session
185     * 
186     * @param jingleMediaManagers
187     */
188    public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) {
189        this.jingleMediaManagers = jingleMediaManagers;
190    }
191
192    /**
193     * Get the session responder
194     * 
195     * @return the responder
196     */
197    public String getResponder() {
198        return responder;
199    }
200
201    /**
202     * Set the session responder.
203     * 
204     * @param responder
205     *            the receptor to set
206     */
207    public void setResponder(String responder) {
208        this.responder = responder;
209    }
210
211    /**
212     * Get the session ID
213     * 
214     * @return the sid
215     */
216    public String getSid() {
217        return sid;
218    }
219
220    /**
221     * Set the session ID
222     * 
223     * @param sessionId
224     *            the sid to set
225     */
226    protected void setSid(String sessionId) {
227        sid = sessionId;
228    }
229
230    /**
231     * Generate a unique session ID.
232     */
233    protected static String generateSessionId() {
234        return String.valueOf(Math.abs(randomGenerator.nextLong()));
235    }
236
237    /**
238     * Validate the state changes.
239     */
240
241    public void setSessionState(JingleSessionState stateIs) {
242
243        LOGGER.fine("Session state change: " + sessionState + "->" + stateIs);
244        stateIs.enter();
245        sessionState = stateIs;
246    }
247
248    public JingleSessionState getSessionState() {
249        return sessionState;
250    }
251
252    /**
253     * Return true if all of the media managers have finished
254     */
255    public boolean isFullyEstablished() {
256        boolean result = true;
257        for (ContentNegotiator contentNegotiator : contentNegotiators) {
258            if (!contentNegotiator.isFullyEstablished())
259                result = false;
260        }
261        return result;
262    }
263
264    // ----------------------------------------------------------------------------------------------------------
265    // Receive section
266    // ----------------------------------------------------------------------------------------------------------
267
268    /**
269     * Process and respond to an incoming packet. <p/> This method is called
270     * from the packet listener dispatcher when a new packet has arrived. The
271     * method is responsible for recognizing the packet type and, depending on
272     * the current state, delivering it to the right event handler and wait for
273     * a response. The response will be another Jingle packet that will be sent
274     * to the other end point.
275     * 
276     * @param iq
277     *            the packet received
278     * @throws XMPPException
279     * @throws SmackException 
280     */
281    public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException, SmackException {
282        List<IQ> responses = new ArrayList<IQ>();
283
284        String responseId = null;
285
286        LOGGER.fine("Packet: " + iq.toXML());
287
288        try {
289
290            // Dispatch the packet to the JingleNegotiators and get back a list of the results.
291            responses.addAll(dispatchIncomingPacket(iq, null));
292
293            if (iq != null) {
294                responseId = iq.getPacketID();
295
296                // Send the IQ to each of the content negotiators for further processing.
297                // Each content negotiator may pass back a list of JingleContent for addition to the response packet.
298
299                for (ContentNegotiator contentNegotiator : contentNegotiators) {
300                        // If at this point the content negotiator isn't started, it's because we sent a session-init jingle
301                        // packet from startOutgoing() and we're waiting for the other side to let us know they're ready
302                        // to take jingle packets.  (This packet might be a session-terminate, but that will get handled
303                        // later.
304                        if (!contentNegotiator.isStarted()) {
305                                contentNegotiator.start();
306                        }
307                    responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId));
308                }
309
310            }
311            // Acknowledge the IQ reception
312            // Not anymore.  The state machine generates an appropriate response IQ that
313            // gets sent back at the end of this routine.
314            //sendAck(iq);
315
316        } catch (JingleException e) {
317            // Send an error message, if present
318            JingleError error = e.getError();
319            if (error != null) {
320                responses.add(createJingleError(iq, error));
321            }
322
323            // Notify the session end and close everything...
324            triggerSessionClosedOnError(e);
325        }
326
327        //        // If the response is anything other than a RESULT then send it now.
328        //        if ((response != null) && (!response.getType().equals(IQ.Type.RESULT))) {
329        //            getConnection().sendPacket(response);
330        //        }
331
332        // Loop through all of the responses and send them.
333        for (IQ response : responses) {
334            sendPacket(response);
335        }
336    }
337
338    /**
339     * Dispatch an incoming packet. The method is responsible for recognizing
340     * the packet type and, depending on the current state, delivering the
341     * packet to the right event handler and wait for a response.
342     * 
343     * @param iq
344     *            the packet received
345     * @return the new Jingle packet to send.
346     * @throws XMPPException
347     * @throws SmackException 
348     */
349    public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException {
350        List<IQ> responses = new ArrayList<IQ>();
351        IQ response = null;
352
353        if (iq != null) {
354            if (iq.getType().equals(IQ.Type.ERROR)) {
355                // Process errors
356                // TODO getState().eventError(iq);
357            } else if (iq.getType().equals(IQ.Type.RESULT)) {
358                // Process ACKs
359                if (isExpectedId(iq.getPacketID())) {
360
361                    // The other side provisionally accepted our session-initiate.
362                    // Kick off some negotiators.
363                    if (iq.getPacketID().equals(sessionInitPacketID)) {
364                        startNegotiators();
365                    }
366                    removeExpectedId(iq.getPacketID());
367                }
368            } else if (iq instanceof Jingle) {
369                // It is not an error: it is a Jingle packet...
370                Jingle jin = (Jingle) iq;
371                JingleActionEnum action = jin.getAction();
372
373                // Depending on the state we're in we'll get different processing actions.
374                // (See Design Patterns AKA GoF State behavioral pattern.)
375                response = getSessionState().processJingle(this, jin, action);
376            }
377        }
378
379        if (response != null) {
380            // Save the packet id, for recognizing ACKs...
381            addExpectedId(response.getPacketID());
382            responses.add(response);
383        }
384
385        return responses;
386    }
387
388    /**
389     * Add a new content negotiator on behalf of a <content> section received.
390     */
391    public void addContentNegotiator(ContentNegotiator inContentNegotiator) {
392        contentNegotiators.add(inContentNegotiator);
393    }
394
395  
396
397     // ----------------------------------------------------------------------------------------------------------
398    // Send section
399    // ----------------------------------------------------------------------------------------------------------
400
401    public void sendPacket(IQ iq) throws NotConnectedException {
402
403        if (iq instanceof Jingle) {
404
405            sendFormattedJingle((Jingle) iq);
406
407        } else {
408
409            getConnection().sendPacket(iq);
410        }
411    }
412
413    /**
414     * Complete and send a packet. Complete all the null fields in a Jingle
415     * reponse, using the session information we have.
416     * 
417     * @param jout
418     *            the Jingle packet we want to complete and send
419     * @throws NotConnectedException 
420     */
421    public Jingle sendFormattedJingle(Jingle jout) throws NotConnectedException {
422        return sendFormattedJingle(null, jout);
423    }
424
425    /**
426     * Complete and send a packet. Complete all the null fields in a Jingle
427     * reponse, using the session information we have or some info from the
428     * incoming packet.
429     * 
430     * @param iq
431     *            The Jingle packet we are responing to
432     * @param jout
433     *            the Jingle packet we want to complete and send
434     * @throws NotConnectedException 
435     */
436    public Jingle sendFormattedJingle(IQ iq, Jingle jout) throws NotConnectedException {
437        if (jout != null) {
438            if (jout.getInitiator() == null) {
439                jout.setInitiator(getInitiator());
440            }
441
442            if (jout.getResponder() == null) {
443                jout.setResponder(getResponder());
444            }
445
446            if (jout.getSid() == null) {
447                jout.setSid(getSid());
448            }
449
450            String me = getConnection().getUser();
451            String other = getResponder().equals(me) ? getInitiator() : getResponder();
452
453            if (jout.getTo() == null) {
454                if (iq != null) {
455                    jout.setTo(iq.getFrom());
456                } else {
457                    jout.setTo(other);
458                }
459            }
460
461            if (jout.getFrom() == null) {
462                if (iq != null) {
463                    jout.setFrom(iq.getTo());
464                } else {
465                    jout.setFrom(me);
466                }
467            }
468
469            // The the packet.
470            if ((getConnection() != null) && (getConnection().isConnected()))
471                getConnection().sendPacket(jout);
472        }
473        return jout;
474    }
475
476    /**
477     *  @param inJingle
478     *  @param inAction
479     */
480    //    private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) {
481    //
482    //        if (inAction == JingleActionEnum.SESSION_INITIATE) {
483    //            // Prepare to receive and act on response packets.
484    //            updatePacketListener();
485    //
486    //            // Send the actual packet.
487    //            sendPacket(inJingle);
488    //
489    //            // Change to the PENDING state.
490    //            setSessionState(JingleSessionStateEnum.PENDING);
491    //        } else {
492    //            throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state.");
493    //        }
494    //    }
495
496    /**
497     * Acknowledge a IQ packet.
498     * 
499     * @param iq
500     *            The IQ to acknowledge
501     */
502    public IQ createAck(IQ iq) {
503        IQ result = null;
504
505        if (iq != null) {
506            // Don't acknowledge ACKs, errors...
507            if (iq.getType().equals(IQ.Type.SET)) {
508                IQ ack = createIQ(iq.getPacketID(), iq.getFrom(), iq.getTo(), IQ.Type.RESULT);
509
510                // No! Don't send it.  Let it flow to the normal way IQ results get processed and sent.
511                // getConnection().sendPacket(ack);
512                result = ack;
513            }
514        }
515        return result;
516    }
517
518    /**
519     * Send a content info message.
520     */
521    //    public synchronized void sendContentInfo(ContentInfo ci) {
522    //        sendPacket(new Jingle(new JingleContentInfo(ci)));
523    //    }
524    /*
525     * (non-Javadoc)
526     * 
527     * @see java.lang.Object#hashCode()
528     */
529    public int hashCode() {
530        return Jingle.getSessionHash(getSid(), getInitiator());
531    }
532
533    /*
534     * (non-Javadoc)
535     * 
536     * @see java.lang.Object#equals(java.lang.Object)
537     */
538    public boolean equals(Object obj) {
539        if (this == obj) {
540            return true;
541        }
542        if (obj == null) {
543            return false;
544        }
545        if (getClass() != obj.getClass()) {
546            return false;
547        }
548
549        final JingleSession other = (JingleSession) obj;
550
551        if (initiator == null) {
552            if (other.initiator != null) {
553                return false;
554            }
555        } else if (!initiator.equals(other.initiator)) {
556            // Todo check behavior
557            // return false;
558        }
559
560        if (responder == null) {
561            if (other.responder != null) {
562                return false;
563            }
564        } else if (!responder.equals(other.responder)) {
565            return false;
566        }
567
568        if (sid == null) {
569            if (other.sid != null) {
570                return false;
571            }
572        } else if (!sid.equals(other.sid)) {
573            return false;
574        }
575
576        return true;
577    }
578
579    // Instances management
580
581    /**
582     * Clean a session from the list.
583     * 
584     * @param connection
585     *            The connection to clean up
586     */
587    private void unregisterInstanceFor(XMPPConnection connection) {
588        synchronized (sessions) {
589            sessions.remove(connection);
590        }
591    }
592
593    /**
594     * Register this instance.
595     */
596    private void registerInstance() {
597        synchronized (sessions) {
598            sessions.put(getConnection(), this);
599        }
600    }
601
602    /**
603     * Returns the JingleSession related to a particular connection.
604     * 
605     * @param con
606     *            A XMPP connection
607     * @return a Jingle session
608     */
609    public static JingleSession getInstanceFor(XMPPConnection con) {
610        if (con == null) {
611            throw new IllegalArgumentException("XMPPConnection cannot be null");
612        }
613
614        JingleSession result = null;
615        synchronized (sessions) {
616            if (sessions.containsKey(con)) {
617                result = sessions.get(con);
618            }
619        }
620
621        return result;
622    }
623
624    /**
625     * Configure a session, setting some action listeners...
626     * 
627     * @param connection
628     *            The connection to set up
629     */
630    private void installConnectionListeners(final XMPPConnection connection) {
631        if (connection != null) {
632            connectionListener = new AbstractConnectionListener() {
633                @Override
634                public void connectionClosed() {
635                    unregisterInstanceFor(connection);
636                }
637
638                @Override
639                public void connectionClosedOnError(java.lang.Exception e) {
640                    unregisterInstanceFor(connection);
641                }
642            };
643            connection.addConnectionListener(connectionListener);
644        }
645    }
646    
647    private void removeConnectionListener() {
648        if (connectionListener != null) {
649                getConnection().removeConnectionListener(connectionListener);
650                
651                LOGGER.fine("JINGLE SESSION: REMOVE CONNECTION LISTENER");
652        }
653    }
654
655    /**
656     * Remove the packet listener used for processing packet.
657     */
658    protected void removePacketListener() {
659        if (packetListener != null) {
660            getConnection().removePacketListener(packetListener);
661
662            LOGGER.fine("JINGLE SESSION: REMOVE PACKET LISTENER");
663        }
664    }
665
666    /**
667     * Install the packet listener. The listener is responsible for responding
668     * to any packet that we receive...
669     */
670    protected void updatePacketListener() {
671        removePacketListener();
672
673        LOGGER.fine("UpdatePacketListener");
674
675        packetListener = new PacketListener() {
676            public void processPacket(Packet packet) {
677                try {
678                    receivePacketAndRespond((IQ) packet);
679                } catch (Exception e) {
680                    e.printStackTrace();
681                }
682            }
683        };
684
685        packetFilter = new PacketFilter() {
686            public boolean accept(Packet packet) {
687
688                if (packet instanceof IQ) {
689                    IQ iq = (IQ) packet;
690
691                    String me = getConnection().getUser();
692
693                    if (!iq.getTo().equals(me)) {
694                        return false;
695                    }
696
697                    String other = getResponder().equals(me) ? getInitiator() : getResponder();
698
699                    if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) {
700                        return false;
701                    }
702
703                    if (iq instanceof Jingle) {
704                        Jingle jin = (Jingle) iq;
705
706                        String sid = jin.getSid();
707                        if (sid == null || !sid.equals(getSid())) {
708                            LOGGER.fine("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML());
709                            return false;
710                        }
711                        String ini = jin.getInitiator();
712                        if (!ini.equals(getInitiator())) {
713                            LOGGER.fine("Ignored Jingle(INI): " + iq.toXML());
714                            return false;
715                        }
716                    } else {
717                        // We accept some non-Jingle IQ packets: ERRORs and ACKs
718                        if (iq.getType().equals(IQ.Type.SET)) {
719                            LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML());
720                            return false;
721                        } else if (iq.getType().equals(IQ.Type.GET)) {
722                            LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML());
723                            return false;
724                        }
725                    }
726                    return true;
727                }
728                return false;
729            }
730        };
731
732        getConnection().addPacketListener(packetListener, packetFilter);
733    }
734
735    // Listeners
736
737    /**
738     * Add a listener for jmf negotiation events
739     * 
740     * @param li
741     *            The listener
742     */
743    public void addMediaListener(JingleMediaListener li) {
744        for (ContentNegotiator contentNegotiator : contentNegotiators) {
745            if (contentNegotiator.getMediaNegotiator() != null) {
746                contentNegotiator.getMediaNegotiator().addListener(li);
747            }
748        }
749
750    }
751
752    /**
753     * Remove a listener for jmf negotiation events
754     * 
755     * @param li
756     *            The listener
757     */
758    public void removeMediaListener(JingleMediaListener li) {
759        for (ContentNegotiator contentNegotiator : contentNegotiators) {
760            if (contentNegotiator.getMediaNegotiator() != null) {
761                contentNegotiator.getMediaNegotiator().removeListener(li);
762            }
763        }
764    }
765
766    /**
767     * Add a listener for transport negotiation events
768     * 
769     * @param li
770     *            The listener
771     */
772    public void addTransportListener(JingleTransportListener li) {
773        for (ContentNegotiator contentNegotiator : contentNegotiators) {
774            if (contentNegotiator.getTransportNegotiator() != null) {
775                contentNegotiator.getTransportNegotiator().addListener(li);
776            }
777        }
778    }
779
780    /**
781     * Remove a listener for transport negotiation events
782     * 
783     * @param li
784     *            The listener
785     */
786    public void removeTransportListener(JingleTransportListener li) {
787        for (ContentNegotiator contentNegotiator : contentNegotiators) {
788            if (contentNegotiator.getTransportNegotiator() != null) {
789                contentNegotiator.getTransportNegotiator().removeListener(li);
790            }
791        }
792    }
793
794    /**
795     * Setup the listeners that act on events coming from the lower level negotiators.
796     */
797
798    public void setupListeners() {
799
800        JingleMediaListener jingleMediaListener = new JingleMediaListener() {
801            public void mediaClosed(PayloadType cand) {
802            }
803
804            public void mediaEstablished(PayloadType pt) throws NotConnectedException {
805                if (isFullyEstablished()) {
806                    Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
807
808                    // Build up a response packet from each media manager.
809                    for (ContentNegotiator contentNegotiator : contentNegotiators) {
810                        if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
811                            jout.addContent(contentNegotiator.getJingleContent());
812                    }
813                    // Send the "accept" and wait for the ACK
814                    addExpectedId(jout.getPacketID());
815                    sendPacket(jout);
816
817                    //triggerSessionEstablished();
818
819                }
820            }
821        };
822
823        JingleTransportListener jingleTransportListener = new JingleTransportListener() {
824
825            public void transportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException {
826                if (isFullyEstablished()) {
827 
828                        // Indicate that this session is active.
829                        setSessionState(JingleSessionStateActive.getInstance());
830                        
831                        for (ContentNegotiator contentNegotiator : contentNegotiators) {
832                        if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
833                            contentNegotiator.triggerContentEstablished();
834                    }
835
836                    if (getSessionState().equals(JingleSessionStatePending.getInstance())) {
837                        
838                        Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
839
840                        // Build up a response packet from each media manager.
841                        for (ContentNegotiator contentNegotiator : contentNegotiators) {
842                            if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
843                                jout.addContent(contentNegotiator.getJingleContent());
844                        }
845                        // Send the "accept" and wait for the ACK
846                        addExpectedId(jout.getPacketID());
847                        sendPacket(jout);
848                    }
849                }
850            }
851
852            public void transportClosed(TransportCandidate cand) {
853            }
854
855            public void transportClosedOnError(XMPPException e) {
856            }
857        };
858
859        addMediaListener(jingleMediaListener);
860        addTransportListener(jingleTransportListener);
861    }
862
863    // Triggers
864
865    /**
866     * Trigger a session closed event.
867     */
868    protected void triggerSessionClosed(String reason) {
869        //        for (ContentNegotiator contentNegotiator : contentNegotiators) {
870        //
871        //            contentNegotiator.stopJingleMediaSession();
872        //
873        //            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
874        //                candidate.removeCandidateEcho();
875        //        }
876
877        List<JingleListener> listeners = getListenersList();
878        for (JingleListener li : listeners) {
879            if (li instanceof JingleSessionListener) {
880                JingleSessionListener sli = (JingleSessionListener) li;
881                sli.sessionClosed(reason, this);
882            }
883        }
884        close();
885    }
886
887    /**
888     * Trigger a session closed event due to an error.
889     */
890    protected void triggerSessionClosedOnError(XMPPException exc) {
891        for (ContentNegotiator contentNegotiator : contentNegotiators) {
892
893            contentNegotiator.stopJingleMediaSession();
894
895            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
896                candidate.removeCandidateEcho();
897        }
898        List<JingleListener> listeners = getListenersList();
899        for (JingleListener li : listeners) {
900            if (li instanceof JingleSessionListener) {
901                JingleSessionListener sli = (JingleSessionListener) li;
902                sli.sessionClosedOnError(exc, this);
903            }
904        }
905        close();
906    }
907
908    /**
909     * Trigger a session established event.
910     */
911    //    protected void triggerSessionEstablished() {
912    //        List<JingleListener> listeners = getListenersList();
913    //        for (JingleListener li : listeners) {
914    //            if (li instanceof JingleSessionListener) {
915    //                JingleSessionListener sli = (JingleSessionListener) li;
916    //                sli.sessionEstablished(this);
917    //            }
918    //        }
919    //    }
920    /**
921     * Trigger a media received event.
922     */
923    protected void triggerMediaReceived(String participant) {
924        List<JingleListener> listeners = getListenersList();
925        for (JingleListener li : listeners) {
926            if (li instanceof JingleSessionListener) {
927                JingleSessionListener sli = (JingleSessionListener) li;
928                sli.sessionMediaReceived(this, participant);
929            }
930        }
931    }
932
933    /**
934     * Trigger a session redirect event.
935     */
936    //    protected void triggerSessionRedirect(String arg) {
937    //        List<JingleListener> listeners = getListenersList();
938    //        for (JingleListener li : listeners) {
939    //            if (li instanceof JingleSessionListener) {
940    //                JingleSessionListener sli = (JingleSessionListener) li;
941    //                sli.sessionRedirected(arg, this);
942    //            }
943    //        }
944    //    }
945    /**
946     * Trigger a session decline event.
947     */
948    //    protected void triggerSessionDeclined(String reason) {
949    //        List<JingleListener> listeners = getListenersList();
950    //        for (JingleListener li : listeners) {
951    //            if (li instanceof JingleSessionListener) {
952    //                JingleSessionListener sli = (JingleSessionListener) li;
953    //                sli.sessionDeclined(reason, this);
954    //            }
955    //        }
956    //        for (ContentNegotiator contentNegotiator : contentNegotiators) {
957    //            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
958    //                candidate.removeCandidateEcho();
959    //        }
960    //    }
961    /**
962     * Terminates the session with default reason.
963     * 
964     * @throws XMPPException
965     * @throws NotConnectedException 
966     */
967    public void terminate() throws XMPPException, NotConnectedException {
968        terminate("Closed Locally");
969    }
970
971    /**
972     * Terminates the session with a custom reason.
973     * 
974     * @throws XMPPException
975     * @throws NotConnectedException 
976     */
977    public void terminate(String reason) throws XMPPException, NotConnectedException {
978        if (isClosed())
979            return;
980        LOGGER.fine("Terminate " + reason);
981        Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE);
982        jout.setType(IQ.Type.SET);
983        sendPacket(jout);
984        triggerSessionClosed(reason);
985    }
986
987    /**
988     * Terminate negotiations.
989     */
990    public void close() {
991        if (isClosed())
992            return;
993
994        // Set the session state to ENDED.
995        setSessionState(JingleSessionStateEnded.getInstance());
996
997        for (ContentNegotiator contentNegotiator : contentNegotiators) {
998
999            contentNegotiator.stopJingleMediaSession();
1000
1001            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
1002                candidate.removeCandidateEcho();
1003
1004            contentNegotiator.close();
1005        }
1006        removePacketListener();
1007        removeConnectionListener();
1008        getConnection().removeConnectionListener(connectionListener);
1009        LOGGER.fine("Negotiation Closed: " + getConnection().getUser() + " " + sid);
1010        super.close();
1011
1012    }
1013
1014    public boolean isClosed() {
1015        return getSessionState().equals(JingleSessionStateEnded.getInstance());
1016    }
1017
1018    // Packet and error creation
1019
1020    /**
1021     * A convience method to create an IQ packet.
1022     * 
1023     * @param ID
1024     *            The packet ID of the
1025     * @param to
1026     *            To whom the packet is addressed.
1027     * @param from
1028     *            From whom the packet is sent.
1029     * @param type
1030     *            The iq type of the packet.
1031     * @return The created IQ packet.
1032     */
1033    public static IQ createIQ(String ID, String to, String from, IQ.Type type) {
1034        IQ iqPacket = new IQ() {
1035            public String getChildElementXML() {
1036                return null;
1037            }
1038        };
1039
1040        iqPacket.setPacketID(ID);
1041        iqPacket.setTo(to);
1042        iqPacket.setFrom(from);
1043        iqPacket.setType(type);
1044
1045        return iqPacket;
1046    }
1047
1048    /**
1049     * A convience method to create an error packet.
1050     * 
1051     * @param ID
1052     *            The packet ID of the
1053     * @param to
1054     *            To whom the packet is addressed.
1055     * @param from
1056     *            From whom the packet is sent.
1057     * @param errCode
1058     *            The error code.
1059     * @param error
1060     *            The XMPPError string.
1061     * @return The created IQ packet.
1062     */
1063    public static IQ createError(String ID, String to, String from, int errCode, XMPPError error) {
1064
1065        IQ iqError = createIQ(ID, to, from, IQ.Type.ERROR);
1066        iqError.setError(error);
1067
1068        LOGGER.fine("Created Error Packet:" + iqError.toXML());
1069
1070        return iqError;
1071    }
1072
1073    /**
1074     * Complete and send an error. Complete all the null fields in an IQ error
1075     * reponse, using the sesssion information we have or some info from the
1076     * incoming packet.
1077     * 
1078     * @param iq
1079     *            The Jingle packet we are responing to
1080     * @param jingleError
1081     *            the IQ packet we want to complete and send
1082     */
1083    public IQ createJingleError(IQ iq, JingleError jingleError) {
1084        IQ errorPacket = null;
1085        if (jingleError != null) {
1086            errorPacket = createIQ(getSid(), iq.getFrom(), iq.getTo(), IQ.Type.ERROR);
1087
1088            List<PacketExtension> extList = new ArrayList<PacketExtension>();
1089            extList.add(jingleError);
1090            XMPPError error = new XMPPError(XMPPError.Type.CANCEL, jingleError.toString(), "", extList);
1091
1092            // Fill in the fields with the info from the Jingle packet
1093            errorPacket.setPacketID(iq.getPacketID());
1094            errorPacket.setError(error);
1095            //            errorPacket.addExtension(jingleError);
1096
1097            // NO! Let the normal state machinery do all of the sending.
1098            // getConnection().sendPacket(perror);
1099            LOGGER.severe("Error sent: " + errorPacket.toXML());
1100        }
1101        return errorPacket;
1102    }
1103
1104    /**
1105     * Called when new Media is received.
1106     */
1107    public void mediaReceived(String participant) {
1108        triggerMediaReceived(participant);
1109    }
1110
1111    /**
1112     * This is the starting point for intitiating a new session.
1113     * 
1114     * @throws IllegalStateException
1115     * @throws SmackException 
1116     */
1117    public void startOutgoing() throws IllegalStateException, SmackException {
1118
1119        updatePacketListener();
1120        setSessionState(JingleSessionStatePending.getInstance());
1121
1122        Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE);
1123
1124        // Create a content negotiator for each media manager on the session.
1125        for (JingleMediaManager mediaManager : getMediaManagers()) {
1126            ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName());
1127
1128            // Create the media negotiator for this content description.
1129            contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg));
1130
1131            JingleTransportManager transportManager = mediaManager.getTransportManager();
1132            TransportResolver resolver = null;
1133            try {
1134                resolver = transportManager.getResolver(this);
1135            } catch (XMPPException e) {
1136                e.printStackTrace();
1137            }
1138
1139            if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
1140                contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg));
1141            }
1142            if (resolver.getType().equals(TransportResolver.Type.ice)) {
1143                contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg));
1144            }
1145
1146            addContentNegotiator(contentNeg);
1147        }
1148
1149        // Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet.
1150        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1151            jingle.addContent(contentNegotiator.getJingleContent());
1152        }
1153
1154        // Save the session-initiate packet ID, so that we can respond to it.
1155        sessionInitPacketID = jingle.getPacketID();
1156
1157        sendPacket(jingle);
1158
1159        // Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
1160        setupListeners();
1161
1162        // Give each of the content negotiators a chance to start 
1163        // and return a portion of the structure to make the Jingle packet.
1164        
1165// Don't do this anymore.  The problem is that the other side might not be ready.
1166// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators
1167// before processing it.  (See receivePacketAndRespond() above.
1168//        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1169//            contentNegotiator.start();
1170//        }
1171    }
1172
1173    /**
1174     *  This is the starting point for responding to a new session.
1175     */
1176    public void startIncoming() {
1177
1178        //updatePacketListener();
1179    }
1180    
1181    protected void doStart() {
1182        
1183    }
1184
1185    /**
1186     * When we initiate a session we need to start a bunch of negotiators right after we receive the result
1187     * packet for our session-initiate.  This is where we start them.
1188     * 
1189     */
1190    private void startNegotiators() {
1191
1192        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1193            TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator();
1194            transNeg.start();
1195        }
1196    }
1197    
1198    /**
1199     * The jingle session may have one or more media managers that are trying to establish media sessions.
1200     * When the media manager succeeds in creating a media session is registers it with the session by the
1201     * media manager's static name.  This routine is where the media manager does the registering.
1202     */
1203    public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) {
1204        mediaSessionMap.put(mediaManagerName, mediaSession);
1205    }
1206    
1207    /**
1208     * The jingle session may have one or more media managers that are trying to establish media sessions.
1209     * When the media manager succeeds in creating a media session is registers it with the session by the
1210     * media manager's static name. This routine is where other objects can access the registered media sessions.
1211     * NB: If the media manager has not succeeded in establishing a media session then this could return null.
1212     */
1213    public JingleMediaSession getMediaSession(String mediaManagerName) {
1214        return mediaSessionMap.get(mediaManagerName);
1215    }
1216}