diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar index d19093ac6..f899d9d93 100644 Binary files a/lib/installer-exclude/libjitsi.jar and b/lib/installer-exclude/libjitsi.jar differ diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java index 188482b10..917c47be8 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerJabberGTalkImpl.java @@ -6,6 +6,8 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +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.*; @@ -17,14 +19,12 @@ * of Jabber and Gtalk protocols. * * @author Vincent Lucas + * @author Lyubomir Marinov */ public abstract class AbstractCallPeerJabberGTalkImpl , - U extends AbstractCallPeerMediaHandlerJabberGTalkImpl> - extends MediaAwareCallPeer< - T, - U, - ProtocolProviderServiceJabberImpl> + U extends AbstractCallPeerMediaHandlerJabberGTalkImpl> + extends MediaAwareCallPeer { /** * The Logger used by the AbstractCallPeerJabberGTalkImpl @@ -34,14 +34,20 @@ public abstract class AbstractCallPeerJabberGTalkImpl = Logger.getLogger(AbstractCallPeerJabberGTalkImpl.class); /** - * The jabber address of this peer + * Any discovery information that we have for this peer. */ - protected String peerJID = null; + private DiscoverInfo discoverInfo; /** - * Any discovery information that we have for this peer. + * The indicator which determines whether this peer was initiated the + * session. */ - private DiscoverInfo discoverInfo; + protected boolean initiator = false; + + /** + * The jabber address of this peer + */ + protected String peerJID; /** * Creates a new call peer with address peerAddress. @@ -50,9 +56,7 @@ public abstract class AbstractCallPeerJabberGTalkImpl * peer. * @param owningCall the call that contains this call peer. */ - protected AbstractCallPeerJabberGTalkImpl( - String peerAddress, - T owningCall) + protected AbstractCallPeerJabberGTalkImpl(String peerAddress, T owningCall) { super(owningCall); @@ -60,14 +64,29 @@ protected AbstractCallPeerJabberGTalkImpl( } /** - * Sets the service discovery information that we have for this peer. + * Returns a String locator for that peer. * - * @param discoverInfo the discovery information that we have obtained for - * this peer. + * @return the peer's address or phone number. */ - public void setDiscoverInfo(DiscoverInfo discoverInfo) + public String getAddress() { - this.discoverInfo = discoverInfo; + return peerJID; + } + + /** + * Returns the contact corresponding to this peer or null if no + * particular contact has been associated. + *

+ * @return the Contact corresponding to this peer or null + * if no particular contact has been associated. + */ + public Contact getContact() + { + ProtocolProviderService pps = getCall().getProtocolProvider(); + OperationSetPresence opSetPresence + = pps.getOperationSet(OperationSetPresence.class); + + return opSetPresence.findContactByID(getAddress()); } /** @@ -81,7 +100,46 @@ public DiscoverInfo getDiscoverInfo() } /** - * Retrives the DiscoverInfo for a given peer identified by its URI. + * Returns a human readable name representing this peer. + * + * @return a String containing a name for that peer. + */ + public String getDisplayName() + { + if (getCall() != null) + { + Contact contact = getContact(); + + if (contact != null) + return contact.getDisplayName(); + } + return peerJID; + } + + /** + * Returns full URI of the address. + * + * @return full URI of the address + */ + public String getURI() + { + return "xmpp:" + peerJID; + } + + /** + * Determines whether this peer initiated the session. Note that if this + * peer is the initiator of the session, then we are the responder! + * + * @return true if this peer initiated the session; false, + * otherwise (i.e. if _we_ initiated the session). + */ + public boolean isInitiator() + { + return initiator; + } + + /** + * Retrieves the DiscoverInfo for a given peer identified by its URI. * * @param calleeURI The URI of the call peer. * @param ppsJabberImpl The call protocol provider service. @@ -105,4 +163,37 @@ protected void retrieveDiscoverInfo(String calleeURI) logger.warn("could not retrieve info for " + calleeURI, ex); } } + + /** + * Specifies the address, phone number, or other protocol specific + * identifier that represents this call peer. This method is to be + * used by service users and MUST NOT be called by the implementation. + * + * @param address The address of this call peer. + */ + public void setAddress(String address) + { + if (!peerJID.equals(address)) + { + String oldAddress = getAddress(); + + peerJID = address; + + fireCallPeerChangeEvent( + CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE, + oldAddress, + address); + } + } + + /** + * Sets the service discovery information that we have for this peer. + * + * @param discoverInfo the discovery information that we have obtained for + * this peer. + */ + public void setDiscoverInfo(DiscoverInfo discoverInfo) + { + this.discoverInfo = discoverInfo; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java index 30e6644c2..a3329c3b6 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerGTalkImpl.java @@ -12,7 +12,6 @@ import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*; 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.util.*; import org.jivesoftware.smack.packet.*; @@ -21,11 +20,11 @@ * Implements a Google Talk CallPeer. * * @author Sebastien Vincent + * @author Lyubomir Marinov */ public class CallPeerGTalkImpl extends AbstractCallPeerJabberGTalkImpl - + { /** * The Logger used by the CallPeerGTalkImpl class and its @@ -35,25 +34,49 @@ public class CallPeerGTalkImpl = Logger.getLogger(CallPeerGTalkImpl.class); /** - * The {@link SessionIQ} that created the session that this call represents. + * Returns whether or not the CallPeer is an Android phone or + * a call pass throught Google Voice or uses Google Talk client. + * + * We base the detection of the JID's resource which in the case of Android + * is android/Vtok/Talk.vXXXXXXX (where XXXXXX is a suite of + * numbers/letters). */ - private SessionIQ sessionInitIQ = null; + private static boolean isAndroidOrVtokOrTalkClient(String fullJID) + { + int idx = fullJID.indexOf('/'); + + if(idx != -1) + { + String res = fullJID.substring(idx + 1); + if(res.startsWith("android") || res.startsWith("Vtok") || + res.startsWith("Talk.v")) + { + return true; + } + } + + if(fullJID.contains( + "@" + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) + return true; + + return false; + } /** - * Indicates whether this peer was the one that initiated the session. + * Temporary variable for handling client like FreeSwitch that sends + * "accept" message before sending candidates. */ - protected boolean isInitiator = false; + private SessionIQ sessAcceptedWithNoCands = null; /** - * Session ID. + * The {@link SessionIQ} that created the session that this call represents. */ - private String sid = null; + private SessionIQ sessionInitIQ = null; /** - * Temporary variable for handling client like FreeSwitch that sends - * "accept" message before sending candidates. + * Session ID. */ - private SessionIQ sessAcceptedWithNoCands = null; + private String sid = null; /** * Creates a new call peer with address peerAddress. @@ -69,131 +92,55 @@ public CallPeerGTalkImpl(String peerAddress, CallGTalkImpl owningCall) } /** - * Returns a String locator for that peer. - * - * @return the peer's address or phone number. - */ - public String getAddress() - { - return peerJID; - } - - /** - * Returns full URI of the address. + * Indicates a user request to answer an incoming call from this + * CallPeer. * - * @return full URI of the address - */ - public String getURI() - { - return "xmpp:" + peerJID; - } - - /** - * Specifies the address, phone number, or other protocol specific - * identifier that represents this call peer. This method is to be - * used by service users and MUST NOT be called by the implementation. + * Sends an OK response to callPeer. Make sure that the call + * peer contains an SDP description when you call this method. * - * @param address The address of this call peer. + * @throws OperationFailedException if we fail to create or send the + * response. */ - public void setAddress(String address) + public synchronized void answer() + throws OperationFailedException { - String oldAddress = getAddress(); - - if(peerJID.equals(address)) - return; - - this.peerJID = address; - //Fire the Event - fireCallPeerChangeEvent( - CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE, - oldAddress, - address); - } + RtpDescriptionPacketExtension answer = null; - /** - * Returns a human readable name representing this peer. - * - * @return a String containing a name for that peer. - */ - public String getDisplayName() - { - if (getCall() != null) + try { - Contact contact = getContact(); - - if (contact != null) - return contact.getDisplayName(); + getMediaHandler().getTransportManager(). + wrapupConnectivityEstablishment(); + answer = getMediaHandler().generateSessionAccept(true); } - return peerJID; - } - - /** - * Determines whether this peer was the one that initiated the session. Note - * that if this peer is the initiator of the session then this means we are - * the responder! - * - * @return true if this peer is the one that initiated the session - * and false otherwise (i.e. if _we_ initiated the session). - */ - public boolean isInitiator() - { - return isInitiator; - } - - /** - * Returns the contact corresponding to this peer or null if no - * particular contact has been associated. - *

- * @return the Contact corresponding to this peer or null - * if no particular contact has been associated. - */ - public Contact getContact() - { - ProtocolProviderService pps = getCall().getProtocolProvider(); - OperationSetPresence opSetPresence - = pps.getOperationSet(OperationSetPresence.class); - - return opSetPresence.findContactByID(getAddress()); - } + catch(IllegalArgumentException e) + { + sessAcceptedWithNoCands = new SessionIQ(); - /** - * Processes the session initiation {@link SessionIQ} that we were created - * with, passing its content to the media handler and then sends either a - * "session-info/ringing" or a "terminate" response. - * - * @param sessionInitIQ The {@link SessionIQ} that created the session that - * we are handling here. - */ - protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) - { - // Do initiate the session. - this.sessionInitIQ = sessionInitIQ; - this.isInitiator = true; + // HACK apparently FreeSwitch need to have accept before + answer = getMediaHandler().generateSessionAccept(false); - RtpDescriptionPacketExtension description = null; + SessionIQ response + = GTalkPacketFactory.createSessionAccept( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + getSessionID(), + answer); - for(PacketExtension ext : sessionInitIQ.getExtensions()) - { - if(ext.getElementName().equals( - RtpDescriptionPacketExtension.ELEMENT_NAME)) - { - description = (RtpDescriptionPacketExtension)ext; - break; - } + getProtocolProvider().getConnection().sendPacket(response); + return; } - - if(description == null) + catch(Exception exc) { - logger.info("No description in incoming session initiate"); + logger.info("Failed to answer an incoming call", exc); - //send an error response; - String reasonText = "Error: no description"; + //send an error response + String reasonText = "Error: " + exc.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.INCOMPATIBLE_PARAMETERS, + Reason.FAILED_APPLICATION, reasonText); setState(CallPeerState.FAILED, reasonText); @@ -201,202 +148,226 @@ protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) return; } + SessionIQ response + = GTalkPacketFactory.createSessionAccept( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + getSessionID(), + 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. + if(sessAcceptedWithNoCands == null) + getProtocolProvider().getConnection().sendPacket(response); + try { - getMediaHandler().processOffer(description); + getMediaHandler().start(); } - catch(Exception ex) + catch(UndeclaredThrowableException e) { - logger.info("Failed to process an incoming session initiate", ex); + Throwable exc = e.getUndeclaredThrowable(); - //send an error response; - String reasonText = "Error: " + ex.getMessage(); + logger.info("Failed to establish a connection", exc); + + //send an error response + String reasonText = "Error: " + exc.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.INCOMPATIBLE_PARAMETERS, + Reason.GENERAL_ERROR, reasonText); + getMediaHandler().getTransportManager().close(); 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.getDiscoverInfo() == null) - { - String calleeURI = sessionInitIQ.getFrom(); - retrieveDiscoverInfo(calleeURI); - } + //tell everyone we are connecting so that the audio notifications would + //stop + setState(CallPeerState.CONNECTED); } /** - * Initiate a Google Talk session {@link SessionIQ}. + * Returns the IQ ID of the Jingle session-initiate packet associated with + * this call. * - * @param sessionInitiateExtensions a collection of additional and optional - * PacketExtensions to be added to the initiate - * {@link SessionIQ} which is to initiate the session with this - * CallPeerGTalkImpl - * @throws OperationFailedException exception + * @return the IQ ID of the Jingle session-initiate packet associated with + * this call. */ - protected synchronized void initiateSession( - Iterable sessionInitiateExtensions) - throws OperationFailedException + public String getSessInitID() { - sid = SessionIQ.generateSID(); - isInitiator = false; - - //Create the media description that we'd like to send to the other side. - RtpDescriptionPacketExtension offer - = getMediaHandler().createDescription(); - - ProtocolProviderServiceJabberImpl protocolProvider - = getProtocolProvider(); - - sessionInitIQ - = GTalkPacketFactory.createSessionInitiate( - protocolProvider.getOurJID(), - this.peerJID, - sid, - offer); - - if (sessionInitiateExtensions != null) - { - for (PacketExtension sessionInitiateExtension - : sessionInitiateExtensions) - { - sessionInitIQ.addExtension(sessionInitiateExtension); - } - } - - protocolProvider.getConnection().sendPacket(sessionInitIQ); - - // for Google Voice JID without resource we do not harvest and send - // candidates - if(getAddress().endsWith( - ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) - { - return; - } - - getMediaHandler().harvestCandidates(offer.getPayloadTypes(), - new CandidatesSender() - { - public void sendCandidates( - Iterable candidates) - { - CallPeerGTalkImpl.this.sendCandidates(candidates); - } - }); + return sessionInitIQ != null ? sessionInitIQ.getPacketID() : null; } /** - * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a - * reason to the user, if there is one. + * Returns the session ID of the Jingle session associated with this call. * - * @param sessionIQ the {@link SessionIQ} that's terminating our session. + * @return the session ID of the Jingle session associated with this call. */ - public void processSessionReject(SessionIQ sessionIQ) + public String getSessionID() { - processSessionTerminate(sessionIQ); + return sessionInitIQ != null ? sessionInitIQ.getID() : sid; } /** - * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a - * reason to the user, if there is one. + * Ends the call with for this CallPeer. 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 sessionIQ the {@link SessionIQ} that's terminating our session. + * @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 + * ReasonPacketExtension as the value of its + * @param reasonOtherExtension the PacketExtension, if any, to be + * set on the ReasonPacketExtension as the value of its + * otherExtension property */ - public void processSessionTerminate(SessionIQ sessionIQ) + public void hangup(boolean failed, + String reasonText, + PacketExtension reasonOtherExtension) { - String reasonStr = "Call ended by remote side."; - ReasonPacketExtension reasonExt = sessionIQ.getReason(); - - if(reasonStr != null && reasonExt != null) + // do nothing if the call is already ended + if (CallPeerState.DISCONNECTED.equals(getState()) + || CallPeerState.FAILED.equals(getState())) { - Reason reason = reasonExt.getReason(); + if (logger.isDebugEnabled()) + logger.debug("Ignoring a request to hangup a call peer " + + "that is already DISCONNECTED"); + return; + } - if(reason != null) - reasonStr += " Reason: " + reason.toString() + "."; + CallPeerState prevPeerState = getState(); + getMediaHandler().getTransportManager().close(); - String text = reasonExt.getText(); + if (failed) + setState(CallPeerState.FAILED, reasonText); + else + setState(CallPeerState.DISCONNECTED, reasonText); - if(text != null) - reasonStr += " " + text; + SessionIQ responseIQ = null; + + if (prevPeerState.equals(CallPeerState.CONNECTED) + || CallPeerState.isOnHold(prevPeerState)) + { + responseIQ = GTalkPacketFactory.createBye( + getProtocolProvider().getOurJID(), peerJID, getSessionID()); + responseIQ.setInitiator(isInitiator() ? getAddress() : + getProtocolProvider().getOurJID()); + } + else if (CallPeerState.CONNECTING.equals(prevPeerState) + || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState) + || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState)) + { + responseIQ = GTalkPacketFactory.createCancel( + getProtocolProvider().getOurJID(), peerJID, getSessionID()); + responseIQ.setInitiator(isInitiator() ? getAddress() : + getProtocolProvider().getOurJID()); + } + else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) + { + responseIQ = GTalkPacketFactory.createBusy( + getProtocolProvider().getOurJID(), peerJID, getSessionID()); + responseIQ.setInitiator(isInitiator() ? getAddress() : + getProtocolProvider().getOurJID()); + } + 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!"); } - getMediaHandler().getTransportManager().close(); - setState(CallPeerState.DISCONNECTED, reasonStr); + if (responseIQ != null) + { + if (reasonOtherExtension != null) + { + ReasonPacketExtension reason + = (ReasonPacketExtension) + responseIQ.getExtension( + ReasonPacketExtension.ELEMENT_NAME, + ReasonPacketExtension.NAMESPACE); + + if (reason != null) + { + reason.setOtherExtension(reasonOtherExtension); + } + else if(reason instanceof ReasonPacketExtension) + { + responseIQ.setReason( + (ReasonPacketExtension)reasonOtherExtension); + } + } + + getProtocolProvider().getConnection().sendPacket(responseIQ); + } } /** - * Processes the session initiation {@link SessionIQ} that we were created - * with, passing its content to the media handler. + * Initiate a Google Talk session {@link SessionIQ}. * - * @param sessionInitIQ The {@link SessionIQ} that created the session that - * we are handling here. + * @param sessionInitiateExtensions a collection of additional and optional + * PacketExtensions to be added to the initiate + * {@link SessionIQ} which is to initiate the session with this + * CallPeerGTalkImpl + * @throws OperationFailedException exception */ - public void processSessionAccept(SessionIQ sessionInitIQ) + protected synchronized void initiateSession( + Iterable sessionInitiateExtensions) + throws OperationFailedException { - this.sessionInitIQ = sessionInitIQ; + sid = SessionIQ.generateSID(); + initiator = false; - CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler(); - Collection extensions = - sessionInitIQ.getExtensions(); - RtpDescriptionPacketExtension answer = null; + //Create the media description that we'd like to send to the other side. + RtpDescriptionPacketExtension offer + = getMediaHandler().createDescription(); - for(PacketExtension ext : extensions) + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + + sessionInitIQ + = GTalkPacketFactory.createSessionInitiate( + protocolProvider.getOurJID(), + this.peerJID, + sid, + offer); + + if (sessionInitiateExtensions != null) { - if(ext.getElementName().equalsIgnoreCase( - RtpDescriptionPacketExtension.ELEMENT_NAME)) + for (PacketExtension sessionInitiateExtension + : sessionInitiateExtensions) { - answer = (RtpDescriptionPacketExtension)ext; - break; + sessionInitIQ.addExtension(sessionInitiateExtension); } } - try - { - mediaHandler.getTransportManager(). - wrapupConnectivityEstablishment(); - mediaHandler.processAnswer(answer); - } - catch(IllegalArgumentException e) - { - // HACK for FreeSwitch that send accept message before sending - // candidates - sessAcceptedWithNoCands = sessionInitIQ; - return; - } - catch(Exception exc) - { - if (logger.isInfoEnabled()) - logger.info("Failed to process a session-accept", exc); - - //send an error response - String reasonText = "Error: " + exc.getMessage(); - SessionIQ errResp - = GTalkPacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), - sessionInitIQ.getFrom(), - sessionInitIQ.getID(), - Reason.GENERAL_ERROR, - reasonText); + protocolProvider.getConnection().sendPacket(sessionInitIQ); - getMediaHandler().getTransportManager().close(); - setState(CallPeerState.FAILED, reasonText); - getProtocolProvider().getConnection().sendPacket(errResp); + // for Google Voice JID without resource we do not harvest and send + // candidates + if(getAddress().endsWith( + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) + { return; } - //tell everyone we are connecting so that the audio notifications would - //stop - setState(CallPeerState.CONNECTED); - - mediaHandler.start(); + getMediaHandler().harvestCandidates(offer.getPayloadTypes(), + new CandidatesSender() + { + public void sendCandidates( + Iterable candidates) + { + CallPeerGTalkImpl.this.sendCandidates(candidates); + } + }); } /** @@ -450,7 +421,18 @@ public void processCandidates(SessionIQ sessionInitIQ) // candidates if(sessAcceptedWithNoCands != null) { - if(!isInitiator) + if(isInitiator()) + { + try + { + answer(); + } + catch(OperationFailedException e) + { + logger.info("Failed to answer call (FreeSwitch hack)"); + } + } + else { final SessionIQ sess = sessAcceptedWithNoCands; sessAcceptedWithNoCands = null; @@ -465,190 +447,115 @@ public void run() } }.start(); } - else - { - try - { - answer(); - } - catch(OperationFailedException e) - { - logger.info("Failed to answer call (FreeSwitch hack)"); - } - } sessAcceptedWithNoCands = null; } } /** - * Returns the session ID of the Jingle session associated with this call. + * Processes the session initiation {@link SessionIQ} that we were created + * with, passing its content to the media handler. * - * @return the session ID of the Jingle session associated with this call. + * @param sessionInitIQ The {@link SessionIQ} that created the session that + * we are handling here. */ - public String getSessionID() + public void processSessionAccept(SessionIQ sessionInitIQ) { - return sessionInitIQ != null ? sessionInitIQ.getID() : sid; - } + this.sessionInitIQ = sessionInitIQ; - /** - * 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 String getSessInitID() - { - return sessionInitIQ != null ? sessionInitIQ.getPacketID() : null; - } + CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler(); + Collection extensions = + sessionInitIQ.getExtensions(); + RtpDescriptionPacketExtension answer = null; - /** - * Ends the call with for this CallPeer. 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 - * ReasonPacketExtension as the value of its - * @param reasonOtherExtension the PacketExtension, if any, to be - * set on the ReasonPacketExtension as the value of its - * otherExtension property - */ - public void hangup(boolean failed, - String reasonText, - PacketExtension reasonOtherExtension) - { - // do nothing if the call is already ended - if (CallPeerState.DISCONNECTED.equals(getState()) - || CallPeerState.FAILED.equals(getState())) + for(PacketExtension ext : extensions) { - if (logger.isDebugEnabled()) - logger.debug("Ignoring a request to hangup a call peer " - + "that is already DISCONNECTED"); - return; + if(ext.getElementName().equalsIgnoreCase( + RtpDescriptionPacketExtension.ELEMENT_NAME)) + { + answer = (RtpDescriptionPacketExtension)ext; + break; + } } - CallPeerState prevPeerState = getState(); - getMediaHandler().getTransportManager().close(); - - if (failed) - setState(CallPeerState.FAILED, reasonText); - else - setState(CallPeerState.DISCONNECTED, reasonText); - - SessionIQ responseIQ = null; - - if (prevPeerState.equals(CallPeerState.CONNECTED) - || CallPeerState.isOnHold(prevPeerState)) - { - responseIQ = GTalkPacketFactory.createBye( - getProtocolProvider().getOurJID(), peerJID, getSessionID()); - responseIQ.setInitiator(isInitiator() ? getAddress() : - getProtocolProvider().getOurJID()); - } - else if (CallPeerState.CONNECTING.equals(prevPeerState) - || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState) - || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState)) - { - responseIQ = GTalkPacketFactory.createCancel( - getProtocolProvider().getOurJID(), peerJID, getSessionID()); - responseIQ.setInitiator(isInitiator() ? getAddress() : - getProtocolProvider().getOurJID()); - } - else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) - { - responseIQ = GTalkPacketFactory.createBusy( - getProtocolProvider().getOurJID(), peerJID, getSessionID()); - responseIQ.setInitiator(isInitiator() ? getAddress() : - getProtocolProvider().getOurJID()); - } - else if (prevPeerState.equals(CallPeerState.BUSY) - || prevPeerState.equals(CallPeerState.FAILED)) + try { - // For FAILED and BUSY we only need to update CALL_STATUS - // as everything else has been done already. + mediaHandler.getTransportManager(). + wrapupConnectivityEstablishment(); + mediaHandler.processAnswer(answer); } - else + catch(IllegalArgumentException e) { - logger.info("Could not determine call peer state!"); + // HACK for FreeSwitch that send accept message before sending + // candidates + sessAcceptedWithNoCands = sessionInitIQ; + return; } - - if (responseIQ != null) + catch(Exception exc) { - if (reasonOtherExtension != null) - { - ReasonPacketExtension reason - = (ReasonPacketExtension) - responseIQ.getExtension( - ReasonPacketExtension.ELEMENT_NAME, - ReasonPacketExtension.NAMESPACE); + if (logger.isInfoEnabled()) + logger.info("Failed to process a session-accept", exc); - if (reason != null) - { - reason.setOtherExtension(reasonOtherExtension); - } - else if(reason instanceof ReasonPacketExtension) - { - responseIQ.setReason( - (ReasonPacketExtension)reasonOtherExtension); - } - } + //send an error response + String reasonText = "Error: " + exc.getMessage(); + SessionIQ errResp + = GTalkPacketFactory.createSessionTerminate( + sessionInitIQ.getTo(), + sessionInitIQ.getFrom(), + sessionInitIQ.getID(), + Reason.GENERAL_ERROR, + reasonText); - getProtocolProvider().getConnection().sendPacket(responseIQ); + getMediaHandler().getTransportManager().close(); + setState(CallPeerState.FAILED, reasonText); + getProtocolProvider().getConnection().sendPacket(errResp); + return; } + + //tell everyone we are connecting so that the audio notifications would + //stop + setState(CallPeerState.CONNECTED); + + mediaHandler.start(); } /** - * Indicates a user request to answer an incoming call from this - * CallPeer. - * - * Sends an OK response to callPeer. Make sure that the call - * peer contains an SDP description when you call this method. + * Processes the session initiation {@link SessionIQ} that we were created + * with, passing its content to the media handler and then sends either a + * "session-info/ringing" or a "terminate" response. * - * @throws OperationFailedException if we fail to create or send the - * response. + * @param sessionInitIQ The {@link SessionIQ} that created the session that + * we are handling here. */ - public synchronized void answer() - throws OperationFailedException + protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) { - RtpDescriptionPacketExtension answer = null; + // Do initiate the session. + this.sessionInitIQ = sessionInitIQ; + this.initiator = true; - try + RtpDescriptionPacketExtension description = null; + + for(PacketExtension ext : sessionInitIQ.getExtensions()) { - getMediaHandler().getTransportManager(). - wrapupConnectivityEstablishment(); - answer = getMediaHandler().generateSessionAccept(true); + if(ext.getElementName().equals( + RtpDescriptionPacketExtension.ELEMENT_NAME)) + { + description = (RtpDescriptionPacketExtension)ext; + break; + } } - catch(IllegalArgumentException e) - { - sessAcceptedWithNoCands = new SessionIQ(); - - // HACK apparently FreeSwitch need to have accept before - answer = getMediaHandler().generateSessionAccept(false); - SessionIQ response - = GTalkPacketFactory.createSessionAccept( - sessionInitIQ.getTo(), - sessionInitIQ.getFrom(), - getSessionID(), - answer); - - getProtocolProvider().getConnection().sendPacket(response); - return; - } - catch(Exception exc) + if(description == null) { - logger.info("Failed to answer an incoming call", exc); + logger.info("No description in incoming session initiate"); - //send an error response - String reasonText = "Error: " + exc.getMessage(); + //send an error response; + String reasonText = "Error: no description"; SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.FAILED_APPLICATION, + Reason.INCOMPATIBLE_PARAMETERS, reasonText); setState(CallPeerState.FAILED, reasonText); @@ -656,76 +563,75 @@ public synchronized void answer() return; } - SessionIQ response - = GTalkPacketFactory.createSessionAccept( - sessionInitIQ.getTo(), - sessionInitIQ.getFrom(), - getSessionID(), - 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. - if(sessAcceptedWithNoCands == null) - getProtocolProvider().getConnection().sendPacket(response); - try { - getMediaHandler().start(); + getMediaHandler().processOffer(description); } - catch(UndeclaredThrowableException e) + catch(Exception ex) { - Throwable exc = e.getUndeclaredThrowable(); - - logger.info("Failed to establish a connection", exc); + logger.info("Failed to process an incoming session initiate", ex); - //send an error response - String reasonText = "Error: " + exc.getMessage(); + //send an error response; + String reasonText = "Error: " + ex.getMessage(); SessionIQ errResp = GTalkPacketFactory.createSessionTerminate( sessionInitIQ.getTo(), sessionInitIQ.getFrom(), sessionInitIQ.getID(), - Reason.GENERAL_ERROR, + Reason.INCOMPATIBLE_PARAMETERS, reasonText); - getMediaHandler().getTransportManager().close(); setState(CallPeerState.FAILED, reasonText); getProtocolProvider().getConnection().sendPacket(errResp); return; } - //tell everyone we are connecting so that the audio notifications would - //stop - setState(CallPeerState.CONNECTED); + // If we do not get the info about the remote peer yet. Get it right + // now. + if(this.getDiscoverInfo() == null) + { + String calleeURI = sessionInitIQ.getFrom(); + retrieveDiscoverInfo(calleeURI); + } } /** - * Returns whether or not the CallPeer is an Android phone or - * a call pass throught Google Voice or uses Google Talk client. + * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a + * reason to the user, if there is one. * - * We base the detection of the JID's resource which in the case of Android - * is android/Vtok/Talk.vXXXXXXX (where XXXXXX is a suite of - * numbers/letters). + * @param sessionIQ the {@link SessionIQ} that's terminating our session. */ - private static boolean isAndroidOrVtokOrTalkClient(String fullJID) + public void processSessionReject(SessionIQ sessionIQ) { - int idx = fullJID.indexOf('/'); + processSessionTerminate(sessionIQ); + } - if(idx != -1) + /** + * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a + * reason to the user, if there is one. + * + * @param sessionIQ the {@link SessionIQ} that's terminating our session. + */ + public void processSessionTerminate(SessionIQ sessionIQ) + { + String reasonStr = "Call ended by remote side."; + ReasonPacketExtension reasonExt = sessionIQ.getReason(); + + if(reasonStr != null && reasonExt != null) { - String res = fullJID.substring(idx + 1); - if(res.startsWith("android") || res.startsWith("Vtok") || - res.startsWith("Talk.v")) - { - return true; - } - } + Reason reason = reasonExt.getReason(); - if(fullJID.contains( - "@" + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) - return true; + if(reason != null) + reasonStr += " Reason: " + reason.toString() + "."; - return false; + String text = reasonExt.getText(); + + if(text != null) + reasonStr += " " + text; + } + + getMediaHandler().getTransportManager().close(); + setState(CallPeerState.DISCONNECTED, reasonStr); } /** 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 e0eefe835..3053bf564 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java @@ -13,7 +13,6 @@ 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.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jitsi.service.neomedia.*; @@ -29,8 +28,7 @@ */ public class CallPeerJabberImpl extends AbstractCallPeerJabberGTalkImpl - + { /** * The Logger used by the CallPeerJabberImpl class and its @@ -40,14 +38,9 @@ public class CallPeerJabberImpl = Logger.getLogger(CallPeerJabberImpl.class); /** - * The {@link JingleIQ} that created the session that this call represents. - */ - private JingleIQ sessionInitIQ; - - /** - * Indicates whether this peer was the one that initiated the session. + * If the call is cancelled before session-initiate is sent. */ - private boolean isInitiator = false; + private boolean cancelled = false; /** * Synchronization object for candidates available. @@ -60,24 +53,24 @@ public class CallPeerJabberImpl private boolean contentAddWithNoCands = false; /** - * Synchronization object for SID. + * If we have processed the session initiate. */ - private final Object sidSyncRoot = new Object(); + private boolean sessionInitiateProcessed = false; /** - * If the call is cancelled before session-initiate is sent. + * Synchronization object. */ - private boolean cancelled = false; + private final Object sessionInitiateSyncRoot = new Object(); /** - * Synchronization object. + * The {@link JingleIQ} that created the session that this call represents. */ - private final Object sessionInitiateSyncRoot = new Object(); + private JingleIQ sessionInitIQ; /** - * If we have processed the session initiate. + * Synchronization object for SID. */ - private boolean sessionInitiateProcessed = false; + private final Object sidSyncRoot = new Object(); /** * Creates a new call peer with address peerAddress. @@ -108,222 +101,6 @@ public CallPeerJabberImpl(String peerAddress, this.sessionInitIQ = sessionIQ; } - /** - * Returns a String locator for that peer. - * - * @return the peer's address or phone number. - */ - public String getAddress() - { - return peerJID; - } - - /** - * Returns full URI of the address. - * - * @return full URI of the address - */ - public String getURI() - { - return "xmpp:" + peerJID; - } - - /** - * Specifies the address, phone number, or other protocol specific - * identifier that represents this call peer. This method is to be - * used by service users and MUST NOT be called by the implementation. - * - * @param address The address of this call peer. - */ - public void setAddress(String address) - { - String oldAddress = getAddress(); - - if(peerJID.equals(address)) - return; - - this.peerJID = address; - //Fire the Event - fireCallPeerChangeEvent( - CallPeerChangeEvent.CALL_PEER_ADDRESS_CHANGE, - oldAddress, - address); - } - - /** - * Returns a human readable name representing this peer. - * - * @return a String containing a name for that peer. - */ - public String getDisplayName() - { - if (getCall() != null) - { - Contact contact = getContact(); - - if (contact != null) - return contact.getDisplayName(); - } - return peerJID; - } - - /** - * Returns the contact corresponding to this peer or null if no - * particular contact has been associated. - *

- * @return the Contact corresponding to this peer or null - * if no particular contact has been associated. - */ - public Contact getContact() - { - ProtocolProviderService pps = getCall().getProtocolProvider(); - OperationSetPresence opSetPresence - = pps.getOperationSet(OperationSetPresence.class); - - return opSetPresence.findContactByID(getAddress()); - } - - /** - * 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.isInitiator = 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 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.getDiscoverInfo() == null) - { - String calleeURI = sessionInitIQ.getFrom(); - retrieveDiscoverInfo(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(); - } - } - - /** - * 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 sessionInitiateExtensions a collection of additional and optional - * PacketExtensions to be added to the session-initiate - * {@link JingleIQ} which is to initiate the session with this - * CallPeerJabberImpl - * @throws OperationFailedException exception - */ - protected synchronized void initiateSession( - Iterable sessionInitiateExtensions) - throws OperationFailedException - { - isInitiator = false; - - //Create the media description that we'd like to send to the other side. - List offer - = getMediaHandler().createContentList(); - - //send a ringing response - if (logger.isTraceEnabled()) - logger.trace("will send ringing response: "); - - 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); - } - /** * Indicates a user request to answer an incoming call from this * CallPeer. @@ -405,6 +182,40 @@ public synchronized void answer() 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. + */ + public String getJingleSID() + { + 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 String getSessInitID() + { + return sessionInitIQ != null ? sessionInitIQ.getPacketID() : 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 for this CallPeer. Depending on the state * of the peer the method would send a CANCEL, BYE, or BUSY_HERE message @@ -509,460 +320,94 @@ else if(reasonOtherExtension instanceof ReasonPacketExtension) } /** - * Returns the session ID of the Jingle session associated with this call. + * 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. * - * @return the session ID of the Jingle session associated with this call. + * @param sessionInitiateExtensions a collection of additional and optional + * PacketExtensions to be added to the session-initiate + * {@link JingleIQ} which is to initiate the session with this + * CallPeerJabberImpl + * @throws OperationFailedException exception */ - public String getJingleSID() + protected synchronized void initiateSession( + Iterable sessionInitiateExtensions) + throws OperationFailedException { - return sessionInitIQ != null ? sessionInitIQ.getSID() : null; - } + initiator = false; - /** - * 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 String getSessInitID() - { - return sessionInitIQ != null ? sessionInitIQ.getPacketID() : null; - } + //Create the media description that we'd like to send to the other side. + List offer + = getMediaHandler().createContentList(); - /** - * 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; - } + //send a ringing response + if (logger.isTraceEnabled()) + logger.trace("will send ringing response: "); - /** - * 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(); + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); - if(reasonStr != null) + synchronized(sidSyncRoot) { - Reason reason = reasonExt.getReason(); - - if(reason != null) - reasonStr += " Reason: " + reason.toString() + "."; + sessionInitIQ + = JinglePacketFactory.createSessionInitiate( + protocolProvider.getOurJID(), + this.peerJID, + JingleIQ.generateSID(), + offer); - String text = reasonExt.getText(); + if(cancelled) + { + // we cancelled the call too early so no need to send the + // session-initiate to peer + getMediaHandler().getTransportManager().close(); + return; + } + } - if(text != null) - reasonStr += " " + text; + if (sessionInitiateExtensions != null) + { + for (PacketExtension sessionInitiateExtension + : sessionInitiateExtensions) + { + sessionInitIQ.addExtension(sessionInitiateExtension); + } } - setState(CallPeerState.DISCONNECTED, reasonStr); + protocolProvider.getConnection().sendPacket(sessionInitIQ); } /** - * 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. + * Processes the content-accept {@link JingleIQ}. * - * @param sessionInitIQ The {@link JingleIQ} that created the session that - * we are handling here. + * @param content The {@link JingleIQ} that contains content that remote + * peer has accepted */ - public void processSessionAccept(JingleIQ sessionInitIQ) + public void processContentAccept(JingleIQ content) { - this.sessionInitIQ = sessionInitIQ; - - CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - List answer = sessionInitIQ.getContentList(); + List contents = content.getContentList(); try { - mediaHandler.getTransportManager(). + getMediaHandler().getTransportManager(). wrapupConnectivityEstablishment(); - mediaHandler.processAnswer(answer); + getMediaHandler().processAnswer(contents); } catch(Exception exc) { - if (logger.isInfoEnabled()) - logger.info("Failed to process a session-accept", exc); - + logger.warn("Failed to process a content-accept", exc); //send an error response; JingleIQ errResp = JinglePacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), sessionInitIQ.getFrom(), - sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, - exc.getClass().getName() + ": " + exc.getMessage()); + sessionInitIQ.getTo(), sessionInitIQ.getFrom(), + sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, + "Error: " + exc.getMessage()); setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); getProtocolProvider().getConnection().sendPacket(errResp); return; } - //tell everyone we are connecting so that the audio notifications would - //stop - setState(CallPeerState.CONNECTED); - - mediaHandler.start(); - } - - /** - * Puts the CallPeer represented by this instance on or off hold. - * - * @param onHold true to have the CallPeer put on hold; - * false, 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, - getJingleSID(), - type); - - getProtocolProvider().getConnection().sendPacket(onHoldIQ); - } - - /** - * Determines whether this peer was the one that initiated the session. Note - * that if this peer is the initiator of the session then this means we are - * the responder! - * - * @return true if this peer is the one that initiated the session - * and false otherwise (i.e. if _we_ initiated the session). - */ - public boolean isInitiator() - { - return isInitiator; - } - - /** - * Handles the specified session info 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 a specific "XEP-0251: Jingle Session Transfer" - * transfer 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 transfer 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); - } - - putOnHold(true); - - 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 })); - - hangup( - false, - ((sid == null) ? "Unattended" : "Attended") + " transfer success", - new TransferredPacketExtension()); - } - - /** - * Send a content-add to add video setup. - */ - private void sendAddVideoContent() - { - List 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, - getJingleSID(), - contents); - - protocolProvider.getConnection().sendPacket(contentIQ); - } - - /** - * Send a content-remove to remove video setup. - */ - private void sendRemoveVideoContent() - { - CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - - ContentPacketExtension content = new ContentPacketExtension(); - ContentPacketExtension remoteContent - = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); - - content.setName(remoteContent.getName()); - content.setCreator(remoteContent.getCreator()); - content.setSenders(remoteContent.getSenders()); - - ProtocolProviderServiceJabberImpl protocolProvider - = getProtocolProvider(); - JingleIQ contentIQ - = JinglePacketFactory.createContentRemove( - protocolProvider.getOurJID(), - this.peerJID, - getJingleSID(), - Arrays.asList(content)); - - protocolProvider.getConnection().sendPacket(contentIQ); - mediaHandler.removeContent(remoteContent.getName()); - } - - /** - * Send a content message to reflect change in audio setup (start, - * stop or conference starts). - * - * @param isConference if the call if now a conference call - */ - public void sendCoinSessionInfo(boolean isConference) - { - JingleIQ sessionInfoIQ = JinglePacketFactory.createSessionInfo( - getProtocolProvider().getOurJID(), - this.peerJID, getJingleSID()); - CoinPacketExtension coinExt = new CoinPacketExtension(isConference); - sessionInfoIQ.addExtension(coinExt); - - getProtocolProvider().getConnection().sendPacket(sessionInfoIQ); - } - - /** - * Send a content message to reflect change in video setup (start - * or stop). - */ - public void sendModifyVideoResolutionContent() - { - ContentPacketExtension remoteContent = getMediaHandler(). - getRemoteContent(MediaType.VIDEO.toString()); - ContentPacketExtension content; - - logger.info("send modify-content to change resolution"); - - // send content-modify with RTP description - SendersEnum senders = remoteContent.getSenders(); - - // create content list with resolution - try - { - content = getMediaHandler().createContentForMedia(MediaType.VIDEO); - } - catch(Exception exc) - { - logger.warn("Failed to gather content for video type", exc); - return; - } - - // if we are only receiving video senders is null - if(senders != null) - content.setSenders(senders); - - JingleIQ contentIQ = JinglePacketFactory - .createContentModify(getProtocolProvider().getOurJID(), - this.peerJID, getJingleSID(), content); - - getProtocolProvider().getConnection().sendPacket(contentIQ); - - try - { - getMediaHandler().reinitContent(remoteContent.getName(), content, - false); - getMediaHandler().start(); - } - catch(Exception e) - { - logger.warn("Exception occurred when media reinitialization", e); - } - } - - /** - * Send a content message to reflect change in video setup (start - * or stop). Message can be content-modify if video content exists, - * content-add if we start video but video is not enabled on the peer or - * content-remove if we stop video and video is not enabled on the peer. - * - * @param allowed if the local video is allowed or not - */ - public void sendModifyVideoContent(boolean allowed) - { - ContentPacketExtension ext = new ContentPacketExtension(); - ContentPacketExtension remoteContent - = getMediaHandler().getRemoteContent(MediaType.VIDEO.toString()); - - if(remoteContent == null) - { - if(allowed) - sendAddVideoContent(); - return; - } - else if(!allowed - && ((!isInitiator - && (remoteContent.getSenders() - == SendersEnum.initiator)) - || (isInitiator - && (remoteContent.getSenders() - == SendersEnum.responder)))) - { - sendRemoveVideoContent(); - return; - } - - SendersEnum senders = remoteContent.getSenders(); - - /* adjust the senders attribute depending on current value and if we - * allowed or not local video streaming - */ - if(allowed) - { - if(senders != SendersEnum.none) - { - senders = SendersEnum.both; - } - else if(senders == SendersEnum.none) - { - senders - = isInitiator - ? SendersEnum.responder - : SendersEnum.initiator; - } - } - else - { - if(senders == SendersEnum.both || senders == null) - { - senders - = isInitiator - ? SendersEnum.initiator - : SendersEnum.responder; - } - else - { - senders = SendersEnum.none; - } - } - - ext.setSenders(senders); - ext.setCreator(remoteContent.getCreator()); - ext.setName(remoteContent.getName()); - - ProtocolProviderServiceJabberImpl protocolProvider - = getProtocolProvider(); - JingleIQ contentIQ - = JinglePacketFactory.createContentModify( - protocolProvider.getOurJID(), - this.peerJID, - getJingleSID(), - ext); - - protocolProvider.getConnection().sendPacket(contentIQ); - - try - { - CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); - - mediaHandler.reinitContent(remoteContent.getName(), ext, false); - mediaHandler.start(); - } - catch(Exception e) - { - logger.warn("Exception occurred during media reinitialization", e); - } + getMediaHandler().start(); } /** @@ -992,13 +437,13 @@ public void processContentAdd(final JingleIQ content) try { if(!contentAddWithNoCands) + { mediaHandler.processOffer(contents); - /* Gingle transport will not put candidate in session-initiate and - * content-add - */ - if(contentAddWithNoCands == false) - { + /* + * Gingle transport will not put candidate in session-initiate + * and content-add. + */ for(ContentPacketExtension c : contents) { if(JingleUtils.getFirstCandidate(c, 1) == null) @@ -1078,55 +523,22 @@ public void run() */ if (oldVideoStream == null) { - MediaStream newVideoStream - = mediaHandler.getStream(MediaType.VIDEO); - - if ((newVideoStream != null) - && mediaHandler.isRTPTranslationEnabled()) - { - try - { - getCall().modifyVideoContent(true); - } - catch (OperationFailedException ofe) - { - logger.error("Failed to enable RTP translation", ofe); - } - } - } - } - - /** - * 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 contents = content.getContentList(); - - try - { - getMediaHandler().getTransportManager(). - wrapupConnectivityEstablishment(); - getMediaHandler().processAnswer(contents); - } - catch(Exception exc) - { - logger.warn("Failed to process a content-accept", exc); - //send an error response; - JingleIQ errResp = JinglePacketFactory.createSessionTerminate( - sessionInitIQ.getTo(), sessionInitIQ.getFrom(), - sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS, - "Error: " + exc.getMessage()); + MediaStream newVideoStream + = mediaHandler.getStream(MediaType.VIDEO); - setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); - getProtocolProvider().getConnection().sendPacket(errResp); - return; + if ((newVideoStream != null) + && mediaHandler.isRTPTranslationEnabled()) + { + try + { + getCall().modifyVideoContent(true); + } + catch (OperationFailedException ofe) + { + logger.error("Failed to enable RTP translation", ofe); + } + } } - - getMediaHandler().start(); } /** @@ -1165,6 +577,27 @@ public void processContentModify(JingleIQ content) } } + /** + * 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}. * @@ -1193,24 +626,241 @@ public void processContentRemove(JingleIQ content) } /** - * Processes the content-reject {@link JingleIQ}. + * 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 content The {@link JingleIQ} + * @param sessionInitIQ The {@link JingleIQ} that created the session that + * we are handling here. */ - public void processContentReject(JingleIQ content) + public void processSessionAccept(JingleIQ sessionInitIQ) { - if(content.getContentList().isEmpty()) + this.sessionInitIQ = sessionInitIQ; + + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + List answer = sessionInitIQ.getContentList(); + + try { + mediaHandler.getTransportManager(). + wrapupConnectivityEstablishment(); + mediaHandler.processAnswer(answer); + } + 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, - "Error: content rejected"); + exc.getClass().getName() + ": " + exc.getMessage()); - setState(CallPeerState.FAILED, "Error: content rejected"); + setState(CallPeerState.FAILED, "Error: " + exc.getMessage()); + getProtocolProvider().getConnection().sendPacket(errResp); + return; + } + + //tell everyone we are connecting so that the audio notifications would + //stop + setState(CallPeerState.CONNECTED); + + mediaHandler.start(); + } + + /** + * Handles the specified session info 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 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.getDiscoverInfo() == null) + { + String calleeURI = sessionInitIQ.getFrom(); + retrieveDiscoverInfo(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(); + } + } + + /** + * 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(reasonStr != 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" + * transfer 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 transfer 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); + } + + putOnHold(true); + + 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 })); + + hangup( + false, + ((sid == null) ? "Unattended" : "Attended") + " transfer success", + new TransferredPacketExtension()); } /** @@ -1226,7 +876,7 @@ public void processTransportInfo(JingleIQ jingleIQ) */ try { - if(isInitiator) + if(isInitiator()) { synchronized(sessionInitiateSyncRoot) { @@ -1272,6 +922,279 @@ public void processTransportInfo(JingleIQ jingleIQ) } } + /** + * Puts the CallPeer represented by this instance on or off hold. + * + * @param onHold true to have the CallPeer put on hold; + * false, 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, + getJingleSID(), + type); + + getProtocolProvider().getConnection().sendPacket(onHoldIQ); + } + + /** + * Send a content-add to add video setup. + */ + private void sendAddVideoContent() + { + List 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, + getJingleSID(), + contents); + + protocolProvider.getConnection().sendPacket(contentIQ); + } + + /** + * Send a content message to reflect change in audio setup (start, + * stop or conference starts). + * + * @param isConference if the call if now a conference call + */ + public void sendCoinSessionInfo(boolean isConference) + { + JingleIQ sessionInfoIQ = JinglePacketFactory.createSessionInfo( + getProtocolProvider().getOurJID(), + this.peerJID, getJingleSID()); + CoinPacketExtension coinExt = new CoinPacketExtension(isConference); + sessionInfoIQ.addExtension(coinExt); + + getProtocolProvider().getConnection().sendPacket(sessionInfoIQ); + } + + /** + * Send a content message to reflect change in video setup (start + * or stop). Message can be content-modify if video content exists, + * content-add if we start video but video is not enabled on the peer or + * content-remove if we stop video and video is not enabled on the peer. + * + * @param allowed if the local video is allowed or not + */ + public void sendModifyVideoContent(boolean allowed) + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + /* + * If the local peer is the focus of a video conference, it should not + * remove content and should rather add/modify content in order to + * perform RTP translation. + */ + if (!allowed && mediaHandler.isRTPTranslationEnabled()) + allowed = true; + + ContentPacketExtension remoteContent + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + + if (remoteContent == null) + { + if (allowed) + sendAddVideoContent(); + return; + } + + SendersEnum senders = remoteContent.getSenders(); + + if (!allowed) + { + boolean initiator = isInitiator(); + + if ((!initiator && (senders == SendersEnum.initiator)) + || (initiator && (senders == SendersEnum.responder))) + { + sendRemoveVideoContent(); + return; + } + } + + /* adjust the senders attribute depending on current value and if we + * allowed or not local video streaming + */ + if (allowed) + { + if (senders == SendersEnum.none) + { + senders + = isInitiator() + ? SendersEnum.responder + : SendersEnum.initiator; + } + else + { + senders = SendersEnum.both; + } + } + else + { + if ((senders == SendersEnum.both) || (senders == null)) + { + senders + = isInitiator() + ? SendersEnum.initiator + : SendersEnum.responder; + } + else + { + senders = SendersEnum.none; + } + } + + ContentPacketExtension ext = new ContentPacketExtension(); + String remoteContentName = remoteContent.getName(); + + ext.setSenders(senders); + ext.setCreator(remoteContent.getCreator()); + ext.setName(remoteContentName); + + ProtocolProviderServiceJabberImpl protocolProvider + = getProtocolProvider(); + JingleIQ contentIQ + = JinglePacketFactory.createContentModify( + protocolProvider.getOurJID(), + this.peerJID, + getJingleSID(), + ext); + + protocolProvider.getConnection().sendPacket(contentIQ); + + try + { + mediaHandler.reinitContent(remoteContentName, ext, false); + mediaHandler.start(); + } + catch(Exception e) + { + logger.warn("Exception occurred during media reinitialization", e); + } + } + + /** + * Send a content 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, + getJingleSID(), + 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 content-remove to remove video setup. + */ + private void sendRemoveVideoContent() + { + CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler(); + + ContentPacketExtension content = new ContentPacketExtension(); + ContentPacketExtension remoteContent + = mediaHandler.getRemoteContent(MediaType.VIDEO.toString()); + 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, + getJingleSID(), + Arrays.asList(content)); + + protocolProvider.getConnection().sendPacket(contentIQ); + mediaHandler.removeContent(remoteContentName); + } + /** * Sends local candidate addresses from the local peer to the remote peer * using the transport-info {@link JingleIQ}. 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 5469e9251..726a883c6 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java @@ -496,8 +496,9 @@ public Iterable generateSessionAccept() //let's now see what was the format we announced as first and //configure the stream with it. + String contentName = ourContent.getName(); ContentPacketExtension theirContent - = this.remoteContentMap.get(ourContent.getName()); + = this.remoteContentMap.get(contentName); RtpDescriptionPacketExtension theirDescription = JingleUtils.getRtpDescription(theirContent); MediaFormat format = null; @@ -560,7 +561,7 @@ public Iterable generateSessionAccept() // create the corresponding stream... initStream( - ourContent.getName(), + contentName, connector, dev, format, diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java index 76f606042..7d06db139 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java @@ -66,7 +66,7 @@ public void setLocalVideoAllowed(Call call, boolean allowed) { super.setLocalVideoAllowed(call, allowed); - ((CallJabberImpl)call).modifyVideoContent(allowed); + ((AbstractCallJabberGTalkImpl) call).modifyVideoContent(allowed); } /** @@ -171,10 +171,10 @@ public void answerVideoCallPeer(CallPeer peer) */ public QualityControl getQualityControl(CallPeer peer) { - if(peer instanceof CallPeerJabberImpl) - return ((CallPeerJabberImpl) peer).getMediaHandler(). - getQualityControl(); - else - return null; + return + (peer instanceof CallPeerJabberImpl) + ? ((CallPeerJabberImpl) peer).getMediaHandler() + .getQualityControl() + : null; } }