/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.impl.protocol.jabber;
import java.util.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.jabber.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.provider.*;
import org.jivesoftware.smack.util.*;
import org.jivesoftware.smackx.packet.*;
/**
* Implements all call management logic and exports basic telephony support by
* implementing <tt>OperationSetBasicTelephony</tt>.
*
* @author Emil Ivov
* @author Symphorien Wanko
* @author Lyubomir Marinov
* @author Sebastien Vincent
* @author Boris Grozev
*/
public class OperationSetBasicTelephonyJabberImpl
extends AbstractOperationSetBasicTelephony<ProtocolProviderServiceJabberImpl>
implements RegistrationStateChangeListener,
PacketListener,
PacketFilter,
OperationSetSecureSDesTelephony,
OperationSetSecureZrtpTelephony,
OperationSetAdvancedTelephony<ProtocolProviderServiceJabberImpl>
{
/**
* The <tt>Logger</tt> used by the
* <tt>OperationSetBasicTelephonyJabberImpl</tt> class and its instances for
* logging output.
*/
private static final Logger logger
= Logger.getLogger(OperationSetBasicTelephonyJabberImpl.class);
/**
* A reference to the <tt>ProtocolProviderServiceJabberImpl</tt> instance
* that created us.
*/
private final ProtocolProviderServiceJabberImpl protocolProvider;
/**
* Contains references for all currently active (non ended) calls.
*/
private ActiveCallsRepositoryJabberGTalkImpl
<CallJabberImpl, CallPeerJabberImpl> activeCallsRepository
= new ActiveCallsRepositoryJabberGTalkImpl
<CallJabberImpl, CallPeerJabberImpl>(this);
/**
* Google Voice domain.
*/
private static final String GOOGLE_VOICE_DOMAIN = "voice.google.com";
/**
* Creates a new instance.
*
* @param protocolProvider a reference to the
* <tt>ProtocolProviderServiceJabberImpl</tt> instance that created us.
*/
public OperationSetBasicTelephonyJabberImpl(
ProtocolProviderServiceJabberImpl protocolProvider)
{
this.protocolProvider = protocolProvider;
this.protocolProvider.addRegistrationStateChangeListener(this);
}
/**
* Implementation of method <tt>registrationStateChange</tt> from
* interface RegistrationStateChangeListener for setting up (or down)
* our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available
*
* @param evt the event received
*/
public void registrationStateChanged(RegistrationStateChangeEvent evt)
{
RegistrationState registrationState = evt.getNewState();
if (registrationState == RegistrationState.REGISTERING)
{
ProviderManager.getInstance().addIQProvider(
JingleIQ.ELEMENT_NAME,
JingleIQ.NAMESPACE,
new JingleIQProvider());
subscribeForJinglePackets();
if (logger.isInfoEnabled())
logger.info("Jingle : ON ");
}
else if (registrationState == RegistrationState.UNREGISTERED)
{
unsubscribeForJinglePackets();
if (logger.isInfoEnabled())
logger.info("Jingle : OFF ");
}
}
/**
* Creates a new <tt>Call</tt> and invites a specific <tt>CallPeer</tt> to
* it given by her <tt>String</tt> URI.
*
* @param callee the address of the callee who we should invite to a new
* <tt>Call</tt>
* @param conference the <tt>CallConference</tt> in which the newly-created
* <tt>Call</tt> is to participate
* @return a newly created <tt>Call</tt>. The specified <tt>callee</tt> is
* available in the <tt>Call</tt> as a <tt>CallPeer</tt>
* @throws OperationFailedException with the corresponding code if we fail
* to create the call
* @see OperationSetBasicTelephony#createCall(String)
*/
public Call createCall(String callee, CallConference conference)
throws OperationFailedException
{
CallJabberImpl call = new CallJabberImpl(this);
if (conference != null)
call.setConference(conference);
CallPeer callPeer = createOutgoingCall(call, callee);
if (callPeer == null)
{
throw new OperationFailedException(
"Failed to create outgoing call"
+ " because no peer was created",
OperationFailedException.INTERNAL_ERROR);
}
Call callOfCallPeer = callPeer.getCall();
// We may have a Google Talk call here.
if ((callOfCallPeer != call) && (conference != null))
callOfCallPeer.setConference(conference);
return callOfCallPeer;
}
/**
* {@inheritDoc}
*
* Creates a new <tt>CallJabberImpl</tt> and initiates a jingle session
* to the JID obtained from the <tt>uri</tt> of <tt>cd</tt>.
*
* If <tt>cd</tt> contains a <tt>callid</tt>, adds the "callid" element as
* an extension to the session-initiate IQ.
*
* Uses the supported transports of <tt>cd</tt>
*/
@Override public CallJabberImpl
createCall(ConferenceDescription cd, final ChatRoom chatRoom)
throws OperationFailedException
{
final CallJabberImpl call = new CallJabberImpl(this);
((ChatRoomJabberImpl) chatRoom).addConferenceCall(call);
call.addCallChangeListener(
new CallChangeListener()
{
@Override
public void callPeerAdded(CallPeerEvent ev) {}
@Override
public void callPeerRemoved(CallPeerEvent ev) {}
@Override
public void callStateChanged(CallChangeEvent ev)
{
if (CallState.CALL_ENDED.equals(ev.getNewValue()))
{
((ChatRoomJabberImpl) chatRoom)
.removeConferenceCall(call);
}
}
});
String remoteJid = cd.getUri();
if (remoteJid.startsWith("xmpp:"))
remoteJid = remoteJid.substring(5, remoteJid.length());
List<PacketExtension> sessionInitiateExtensions
= new ArrayList<PacketExtension>(2);
String callid = cd.getCallId();
if (callid != null)
{
sessionInitiateExtensions.add(new CallIdPacketExtension(callid));
}
//String password = cd.getPassword();
//if (password != null)
// extensions.add(new PasswordPacketExtension(password));
call.initiateSession(
remoteJid,
null,
sessionInitiateExtensions,
cd.getSupportedTransports());
return call;
}
/**
* Init and establish the specified call.
*
* @param call the <tt>CallJabberImpl</tt> that will be used
* to initiate the call
* @param calleeAddress the address of the callee that we'd like to connect
* with.
*
* @return the <tt>CallPeer</tt> that represented by the specified uri. All
* following state change events will be delivered through that call peer.
* The <tt>Call</tt> that this peer is a member of could be retrieved from
* the <tt>CallPeer</tt> instance with the use of the corresponding method.
*
* @throws OperationFailedException with the corresponding code if we fail
* to create the call.
*/
AbstractCallPeer<?, ?> createOutgoingCall(
CallJabberImpl call,
String calleeAddress)
throws OperationFailedException
{
return createOutgoingCall(call, calleeAddress, null);
}
/**
* Init and establish the specified call.
*
* @param call the <tt>CallJabberImpl</tt> that will be used
* to initiate the call
* @param calleeAddress the address of the callee that we'd like to connect
* with.
* @param sessionInitiateExtensions a collection of additional and optional
* <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
* {@link JingleIQ} which is to init the specified <tt>call</tt>
*
* @return the <tt>CallPeer</tt> that represented by the specified uri. All
* following state change events will be delivered through that call peer.
* The <tt>Call</tt> that this peer is a member of could be retrieved from
* the <tt>CallPeer</tt> instance with the use of the corresponding method.
*
* @throws OperationFailedException with the corresponding code if we fail
* to create the call.
*/
AbstractCallPeer<?, ?> createOutgoingCall(
CallJabberImpl call,
String calleeAddress,
Iterable<PacketExtension> sessionInitiateExtensions)
throws OperationFailedException
{
if (calleeAddress.contains("/"))
return createOutgoingCall(call, calleeAddress, calleeAddress, null);
else
return createOutgoingCall(call, calleeAddress, null, null);
}
/**
* Init and establish the specified call.
*
* @param call the <tt>CallJabberImpl</tt> that will be used
* to initiate the call
* @param calleeAddress the address of the callee that we'd like to connect
* with.
* @param fullCalleeURI the full Jid address, which if specified would
* explicitly initiate a call to this full address
* @param sessionInitiateExtensions a collection of additional and optional
* <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
* {@link JingleIQ} which is to init the specified <tt>call</tt>
*
* @return the <tt>CallPeer</tt> that represented by the specified uri. All
* following state change events will be delivered through that call peer.
* The <tt>Call</tt> that this peer is a member of could be retrieved from
* the <tt>CallPeer</tt> instance with the use of the corresponding method.
*
* @throws OperationFailedException with the corresponding code if we fail
* to create the call.
*/
AbstractCallPeer<?, ?> createOutgoingCall(
CallJabberImpl call,
String calleeAddress,
String fullCalleeURI,
Iterable<PacketExtension> sessionInitiateExtensions)
throws OperationFailedException
{
if (logger.isInfoEnabled())
logger.info("Creating outgoing call to " + calleeAddress);
if (protocolProvider.getConnection() == null || call == null)
{
throw new OperationFailedException(
"Failed to create OutgoingJingleSession."
+ " We don't have a valid XMPPConnection.",
OperationFailedException.INTERNAL_ERROR);
}
boolean isGoogle = protocolProvider.isGmailOrGoogleAppsAccount();
boolean isGoogleVoice = false;
if (isGoogle)
{
if (!calleeAddress.contains("@"))
{
calleeAddress += "@" + GOOGLE_VOICE_DOMAIN;
isGoogleVoice = true;
}
else if(calleeAddress.endsWith(GOOGLE_VOICE_DOMAIN))
{
isGoogleVoice = true;
}
}
// if address is not suffixed by @domain, add the default domain
// corresponding to account domain or via the OVERRIDE_PHONE_SUFFIX
// property if defined
AccountID accountID = getProtocolProvider().getAccountID();
if (calleeAddress.indexOf('@') == -1)
{
String phoneSuffix
= accountID.getAccountPropertyString("OVERRIDE_PHONE_SUFFIX");
String serviceName = null;
if ((phoneSuffix == null) || (phoneSuffix.length() == 0))
serviceName = StringUtils.parseServer(accountID.getUserID());
else
serviceName = phoneSuffix;
calleeAddress = calleeAddress + "@" + serviceName;
}
String bypassDomain = accountID.getAccountPropertyString(
JabberAccountID.TELEPHONY_BYPASS_GTALK_CAPS);
boolean alwaysCallGtalk
= ((bypassDomain != null)
&& bypassDomain.equals(
calleeAddress.substring(
calleeAddress.indexOf('@') + 1)))
|| isGoogleVoice;
boolean isPrivateMessagingContact = false;
OperationSetMultiUserChat mucOpSet = getProtocolProvider()
.getOperationSet(OperationSetMultiUserChat.class);
if(mucOpSet != null)
isPrivateMessagingContact
= mucOpSet.isPrivateMessagingContact(calleeAddress);
if((!getProtocolProvider().getConnection().getRoster().contains(
StringUtils.parseBareAddress(calleeAddress)) &&
!isPrivateMessagingContact) && !alwaysCallGtalk)
{
throw new OperationFailedException(
calleeAddress + " does not belong to our contact list",
OperationFailedException.NOT_FOUND);
}
// If there's no fullCalleeURI specified we'll discover the most
// connected one with highest priority.
if (fullCalleeURI == null)
fullCalleeURI =
discoverFullJid(calleeAddress);
if (fullCalleeURI == null)
throw new OperationFailedException(
"Failed to create outgoing call to " + calleeAddress
+ ". Could not find a resource which supports " +
"Jingle",
OperationFailedException.INTERNAL_ERROR);
DiscoverInfo di = null;
try
{
// check if the remote client supports telephony.
di = protocolProvider.getDiscoveryManager().discoverInfo(
fullCalleeURI);
}
catch (XMPPException ex)
{
logger.warn("could not retrieve info for " + fullCalleeURI, ex);
}
if(di != null)
{
if (logger.isInfoEnabled())
logger.info(fullCalleeURI + ": jingle supported ");
}
else
{
if (logger.isInfoEnabled())
logger.info(fullCalleeURI + ": jingle not supported?");
throw new OperationFailedException(
"Failed to create an outgoing call.\n"
+ fullCalleeURI + " does not support jingle",
OperationFailedException.INTERNAL_ERROR);
}
/* in case we figure that calling people without a resource id is
impossible, we'll have to uncomment the following lines. keep in mind
that this would mean - no calls to pstn though
if (fullCalleeURI.indexOf('/') < 0)
{
throw new OperationFailedException(
"Failed to create OutgoingJingleSession.\n"
+ "User " + calleeAddress + " is unknown to us."
, OperationFailedException.INTERNAL_ERROR);
}
*/
AbstractCallPeer<?, ?> peer = null;
// initiate call
try
{
if (di != null)
{
peer
= call.initiateSession(
fullCalleeURI,
di,
sessionInitiateExtensions,
null);
}
}
catch (Throwable t)
{
/*
* The Javadoc on ThreadDeath says: If ThreadDeath is caught by a
* method, it is important that it be rethrown so that the thread
* actually dies.
*/
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
else
{
ProtocolProviderServiceJabberImpl.throwOperationFailedException(
"Failed to create a call to " + fullCalleeURI,
OperationFailedException.INTERNAL_ERROR,
t,
logger);
}
}
return peer;
}
/**
* Discovers the resource for <tt>calleeAddress</tt> with the highest
* priority which supports either Jingle or Gtalk. Returns the full JID.
*
* @param calleeAddress the address of the callee
*
* @return the full callee URI
*/
private String discoverFullJid(String calleeAddress)
{
String fullCalleeURI = null;
DiscoverInfo discoverInfo = null;
int bestPriority = -1;
PresenceStatus jabberStatus = null;
String calleeURI = null;
Iterator<Presence> it
= getProtocolProvider().getConnection().getRoster().getPresences(
calleeAddress);
while(it.hasNext())
{
Presence presence = it.next();
int priority
= (presence.getPriority() == Integer.MIN_VALUE)
? 0
: presence.getPriority();
calleeURI = presence.getFrom();
try
{
// check if the remote client supports telephony.
discoverInfo
= protocolProvider.getDiscoveryManager().discoverInfo(
calleeURI);
}
catch (XMPPException ex)
{
logger.warn("could not retrieve info for " + fullCalleeURI, ex);
}
if (discoverInfo != null && discoverInfo.containsFeature(
ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE))
{
if(priority > bestPriority)
{
bestPriority = priority;
fullCalleeURI = calleeURI;
jabberStatus = OperationSetPersistentPresenceJabberImpl
.jabberStatusToPresenceStatus(
presence, protocolProvider);
}
else if(priority == bestPriority && jabberStatus != null)
{
PresenceStatus tempStatus =
OperationSetPersistentPresenceJabberImpl
.jabberStatusToPresenceStatus(
presence, protocolProvider);
if(tempStatus.compareTo(jabberStatus) > 0)
{
fullCalleeURI = calleeURI;
jabberStatus = tempStatus;
}
}
}
}
if(logger.isInfoEnabled())
logger.info("Full JID for outgoing call: " + fullCalleeURI
+ ", priority " + bestPriority);
return fullCalleeURI;
}
/**
* Gets the full callee URI for a specific callee address.
*
* @param calleeAddress the callee address to get the full callee URI for
* @return the full callee URI for the specified <tt>calleeAddress</tt>
*/
String getFullCalleeURI(String calleeAddress)
{
return
(calleeAddress.indexOf('/') > 0)
? calleeAddress
: protocolProvider
.getConnection()
.getRoster()
.getPresence(calleeAddress)
.getFrom();
}
/**
* Returns an iterator over all currently active calls.
*
* @return an iterator over all currently active calls.
*/
public Iterator<CallJabberImpl> getActiveCalls()
{
return activeCallsRepository.getActiveCalls();
}
/**
* Returns the active call peer corresponding to the given sid.
*
* @param sid the Jingle session ID of the active <tt>Call</tt> between the
* local peer and the callee in the case of attended transfer; <tt>null</tt>
* in the case of unattended transfer
*
* @return The active call peer corresponding to the given sid. "null" if
* there is no such call.
*/
public CallPeerJabberImpl getActiveCallPeer(String sid)
{
return activeCallsRepository.findCallPeer(sid);
}
/**
* Resumes communication with a call peer previously put on hold.
*
* @param peer the call peer to put on hold.
*
* @throws OperationFailedException if we fail to send the "hold" message.
*/
public synchronized void putOffHold(CallPeer peer)
throws OperationFailedException
{
putOnHold(peer, false);
}
/**
* Puts the specified CallPeer "on hold".
*
* @param peer the peer that we'd like to put on hold.
*
* @throws OperationFailedException if we fail to send the "hold" message.
*/
public synchronized void putOnHold(CallPeer peer)
throws OperationFailedException
{
putOnHold(peer, true);
}
/**
* Puts the specified <tt>CallPeer</tt> on or off hold.
*
* @param peer the <tt>CallPeer</tt> to be put on or off hold
* @param on <tt>true</tt> to have the specified <tt>CallPeer</tt>
* put on hold; <tt>false</tt>, otherwise
*
* @throws OperationFailedException if we fail to send the "hold" message.
*/
private void putOnHold(CallPeer peer, boolean on)
throws OperationFailedException
{
if(peer instanceof CallPeerJabberImpl)
((CallPeerJabberImpl) peer).putOnHold(on);
}
/**
* Ends the call with the specified <tt>peer</tt>.
*
* @param peer the peer that we'd like to hang up on.
*
* @throws ClassCastException if peer is not an instance of this
* CallPeerSipImpl.
* @throws OperationFailedException if we fail to terminate the call.
*/
public synchronized void hangupCallPeer(CallPeer peer)
throws ClassCastException,
OperationFailedException
{
hangupCallPeer(peer, HANGUP_REASON_NORMAL_CLEARING, null);
}
/**
* Ends the call with the specified <tt>peer</tt>.
*
* @param peer the peer that we'd like to hang up on.
* @param reasonCode indicates if the hangup is following to a call failure
* or simply a disconnect indicate by the reason.
* @param reasonText the reason of the hangup. If the hangup is due to a
* call failure, then this string could indicate the reason of the failure
*
* @throws OperationFailedException if we fail to terminate the call.
*/
public void hangupCallPeer(CallPeer peer,
int reasonCode,
String reasonText)
{
boolean failed = (reasonCode != HANGUP_REASON_NORMAL_CLEARING);
// if we are failing a peer and have a reason, add the reason packet
// extension
ReasonPacketExtension reasonPacketExt = null;
if (failed && (reasonText != null))
{
Reason reason = convertReasonCodeToSIPCode(reasonCode);
if (reason != null)
{
reasonPacketExt
= new ReasonPacketExtension(reason, reasonText, null);
}
}
// XXX maybe add answer/hangup abstract method to MediaAwareCallPeer
if(peer instanceof CallPeerJabberImpl)
{
((CallPeerJabberImpl) peer).hangup(
failed,
reasonText,
reasonPacketExt);
}
}
/**
* Converts the codes for hangup from OperationSetBasicTelephony one
* to the jabber reasons.
* @param reasonCode the reason code.
* @return the jabber Response.
*/
private static Reason convertReasonCodeToSIPCode(int reasonCode)
{
switch(reasonCode)
{
case HANGUP_REASON_NORMAL_CLEARING :
return Reason.SUCCESS;
case HANGUP_REASON_ENCRYPTION_REQUIRED :
return Reason.SECURITY_ERROR;
case HANGUP_REASON_TIMEOUT :
return Reason.TIMEOUT;
case HANGUP_REASON_BUSY_HERE :
return Reason.BUSY;
default : return null;
}
}
/**
* Implements method <tt>answerCallPeer</tt>
* from <tt>OperationSetBasicTelephony</tt>.
*
* @param peer the call peer that we want to answer
* @throws OperationFailedException if we fails to answer
*/
public void answerCallPeer(CallPeer peer)
throws OperationFailedException
{
// XXX maybe add answer/hangup abstract method to MediaAwareCallPeer
if(peer instanceof CallPeerJabberImpl)
((CallPeerJabberImpl) peer).answer();
}
/**
* Closes all active calls. And releases resources.
*/
public void shutdown()
{
if (logger.isTraceEnabled())
logger.trace("Ending all active calls. ");
Iterator<CallJabberImpl> activeCalls
= this.activeCallsRepository.getActiveCalls();
// this is fast, but events aren't triggered ...
//jingleManager.disconnectAllSessions();
//go through all active calls.
while(activeCalls.hasNext())
{
CallJabberImpl call = activeCalls.next();
Iterator<CallPeerJabberImpl> callPeers = call.getCallPeers();
//go through all call peers and say bye to every one.
while (callPeers.hasNext())
{
CallPeer peer = callPeers.next();
try
{
hangupCallPeer(peer);
}
catch (Exception ex)
{
logger.warn("Failed to properly hangup peer " + peer, ex);
}
}
}
}
/**
* Subscribes us to notifications about incoming jingle packets.
*/
private void subscribeForJinglePackets()
{
protocolProvider.getConnection().addPacketListener(this, this);
}
/**
* Unsubscribes us from notifications about incoming jingle packets.
*/
private void unsubscribeForJinglePackets()
{
Connection connection = protocolProvider.getConnection();
if(connection != null)
connection.removePacketListener(this);
}
/**
* Tests whether or not the specified packet should be handled by this
* operation set. This method is called by smack prior to packet delivery
* and it would only accept <tt>JingleIQ</tt>s that are either session
* initiations with RTP content or belong to sessions that are already
* handled by this operation set.
*
* @param packet the packet to test.
* @return true if and only if <tt>packet</tt> passes the filter.
*/
public boolean accept(Packet packet)
{
// We handle JingleIQ and SessionIQ.
if(!(packet instanceof JingleIQ))
{
String packetID = packet.getPacketID();
AbstractCallPeer<?, ?> callPeer
= activeCallsRepository.findCallPeerBySessInitPacketID(
packetID);
if(callPeer != null)
{
/* packet is a response to a Jingle call but is not a JingleIQ
* so it is for sure an error (peer does not support Jingle or
* does not belong to our roster)
*/
XMPPError error = packet.getError();
if (error != null)
{
String errorMessage = error.getMessage();
logger.error(
"Received an error: code=" + error.getCode()
+ " message=" + errorMessage);
String message;
if (errorMessage == null)
{
Roster roster
= getProtocolProvider().getConnection().getRoster();
String packetFrom = packet.getFrom();
message = "Service unavailable";
if(!roster.contains(packetFrom))
{
message
+= ": try adding the contact " + packetFrom
+ " to your contact list first.";
}
}
else
message = errorMessage;
callPeer.setState(CallPeerState.FAILED, message);
}
}
return false;
}
if(packet instanceof JingleIQ)
{
JingleIQ jingleIQ = (JingleIQ)packet;
if( jingleIQ.getAction() == JingleAction.SESSION_INITIATE)
{
//we only accept session-initiate-s dealing RTP
return
jingleIQ.containsContentChildOfType(
RtpDescriptionPacketExtension.class);
}
String sid = jingleIQ.getSID();
//if this is not a session-initiate we'll only take it if we've
//already seen its session ID.
return (activeCallsRepository.findSID(sid) != null);
}
return false;
}
/**
* Handles incoming jingle packets and passes them to the corresponding
* method based on their action.
*
* @param packet the packet to process.
*/
public void processPacket(Packet packet)
{
IQ iq = (IQ) packet;
/*
* To prevent hijacking sessions from other Jingle-based features such
* as file transfer, we should send the ack only if this is a
* session-initiate with RTP content or if we are the owners of the
* packet's SID.
*/
//first ack all "set" requests.
if(iq.getType() == IQ.Type.SET)
{
IQ ack = IQ.createResultIQ(iq);
protocolProvider.getConnection().sendPacket(ack);
}
try
{
if (iq instanceof JingleIQ)
processJingleIQ((JingleIQ) iq);
}
catch(Throwable t)
{
if (logger.isInfoEnabled())
{
String packetClass;
if (iq instanceof JingleIQ)
packetClass = "Jingle";
else
packetClass = packet.getClass().getSimpleName();
logger.info(
"Error while handling incoming " + packetClass
+ " packet: ",
t);
}
/*
* The Javadoc on ThreadDeath says: If ThreadDeath is caught by
* a method, it is important that it be rethrown so that the
* thread actually dies.
*/
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
}
}
/**
* Analyzes the <tt>jingleIQ</tt>'s action and passes it to the
* corresponding handler.
*
* @param jingleIQ the {@link JingleIQ} packet we need to be analyzing.
*/
private void processJingleIQ(final JingleIQ jingleIQ)
{
//let's first see whether we have a peer that's concerned by this IQ
CallPeerJabberImpl callPeer
= activeCallsRepository.findCallPeer(jingleIQ.getSID());
IQ.Type type = jingleIQ.getType();
if (type == Type.ERROR)
{
logger.error("Received error");
XMPPError error = jingleIQ.getError();
String message = "Remote party returned an error!";
if(error != null)
{
String errorStr
= "code=" + error.getCode()
+ " message=" + error.getMessage();
message += "\n" + errorStr;
logger.error(" " + errorStr);
}
if (callPeer != null)
callPeer.setState(CallPeerState.FAILED, message);
return;
}
JingleAction action = jingleIQ.getAction();
if(action == JingleAction.SESSION_INITIATE)
{
TransferPacketExtension transfer
= (TransferPacketExtension)
jingleIQ.getExtension(
TransferPacketExtension.ELEMENT_NAME,
TransferPacketExtension.NAMESPACE);
CallIdPacketExtension callidExt
= (CallIdPacketExtension)
jingleIQ.getExtension(
ConferenceDescriptionPacketExtension.CALLID_ELEM_NAME,
ConferenceDescriptionPacketExtension.NAMESPACE);
CallJabberImpl call = null;
if (transfer != null)
{
String sid = transfer.getSID();
if (sid != null)
{
CallJabberImpl attendantCall
= getActiveCallsRepository().findSID(sid);
if (attendantCall != null)
{
CallPeerJabberImpl attendant
= attendantCall.getPeer(sid);
if ((attendant != null)
&& getFullCalleeURI(attendant.getAddress())
.equals(transfer.getFrom())
&& protocolProvider.getOurJID().equals(
transfer.getTo()))
{
// OK, we are legally involved in the transfer.
call = attendantCall;
}
}
}
}
if (callidExt != null)
{
String callid = callidExt.getText();
if (callid != null)
call = getActiveCallsRepository().findCallId(callid);
}
if (transfer != null && callidExt != null)
logger.warn("Received a session-initiate with both 'transfer'" +
" and 'callid' extensions. Ignored 'transfer' and" +
" used 'callid'.");
if(call == null)
call = new CallJabberImpl(this);
final CallJabberImpl finalCall = call;
new Thread()
{
@Override
public void run()
{
finalCall.processSessionInitiate(jingleIQ);
}
}.start();
return;
}
else if (callPeer == null)
{
if (logger.isDebugEnabled())
logger.debug("Received a stray trying response.");
return;
}
//the rest of these cases deal with existing peers
else if(action == JingleAction.SESSION_TERMINATE)
{
callPeer.processSessionTerminate(jingleIQ);
}
else if(action == JingleAction.SESSION_ACCEPT)
{
callPeer.processSessionAccept(jingleIQ);
}
else if (action == JingleAction.SESSION_INFO)
{
SessionInfoPacketExtension info = jingleIQ.getSessionInfo();
if(info != null)
{
// change status.
callPeer.processSessionInfo(info);
}
else
{
PacketExtension packetExtension
= jingleIQ.getExtension(
TransferPacketExtension.ELEMENT_NAME,
TransferPacketExtension.NAMESPACE);
if (packetExtension instanceof TransferPacketExtension)
{
TransferPacketExtension transfer
= (TransferPacketExtension) packetExtension;
if (transfer.getFrom() == null)
transfer.setFrom(jingleIQ.getFrom());
try
{
callPeer.processTransfer(transfer);
}
catch (OperationFailedException ofe)
{
logger.error(
"Failed to transfer to " + transfer.getTo(),
ofe);
}
}
packetExtension
= jingleIQ.getExtension(
CoinPacketExtension.ELEMENT_NAME,
CoinPacketExtension.NAMESPACE);
if (packetExtension instanceof CoinPacketExtension)
{
CoinPacketExtension coinExt
= (CoinPacketExtension)packetExtension;
callPeer.setConferenceFocus(
Boolean.parseBoolean(
coinExt.getAttributeAsString(
CoinPacketExtension
.ISFOCUS_ATTR_NAME)));
}
}
}
else if (action == JingleAction.CONTENT_ACCEPT)
{
callPeer.processContentAccept(jingleIQ);
}
else if (action == JingleAction.CONTENT_ADD)
{
callPeer.processContentAdd(jingleIQ);
}
else if (action == JingleAction.CONTENT_MODIFY)
{
callPeer.processContentModify(jingleIQ);
}
else if (action == JingleAction.CONTENT_REJECT)
{
callPeer.processContentReject(jingleIQ);
}
else if (action == JingleAction.CONTENT_REMOVE)
{
callPeer.processContentRemove(jingleIQ);
}
else if (action == JingleAction.TRANSPORT_INFO)
{
callPeer.processTransportInfo(jingleIQ);
}
}
/**
* Returns a reference to the {@link ActiveCallsRepositoryJabberGTalkImpl}
* that we are currently using.
*
* @return a reference to the {@link ActiveCallsRepositoryJabberGTalkImpl}
* that we are currently using.
*/
protected ActiveCallsRepositoryJabberGTalkImpl
<CallJabberImpl, CallPeerJabberImpl>
getActiveCallsRepository()
{
return activeCallsRepository;
}
/**
* Returns the protocol provider that this operation set belongs to.
*
* @return a reference to the <tt>ProtocolProviderService</tt> that created
* this operation set.
*/
public ProtocolProviderServiceJabberImpl getProtocolProvider()
{
return protocolProvider;
}
/**
* Gets the secure state of the call session in which a specific peer
* is involved
*
* @param peer the peer for who the call state is required
* @return the call state
*/
public boolean isSecure(CallPeer peer)
{
return ((MediaAwareCallPeer<?, ?, ?>) peer).getMediaHandler().
isSecure();
}
/**
* Transfers (in the sense of call transfer) a specific <tt>CallPeer</tt> to
* a specific callee address which already participates in an active
* <tt>Call</tt>.
* <p>
* The method is suitable for providing the implementation of attended call
* transfer (though no such requirement is imposed).
* </p>
*
* @param peer the <tt>CallPeer</tt> to be transfered to the specified
* callee address
* @param target the address in the form of <tt>CallPeer</tt> of the callee
* to transfer <tt>peer</tt> to
* @throws OperationFailedException if something goes wrong
* @see OperationSetAdvancedTelephony#transfer(CallPeer, CallPeer)
*/
public void transfer(CallPeer peer, CallPeer target)
throws OperationFailedException
{
AbstractCallPeerJabberGTalkImpl<?,?,?> targetJabberGTalkImpl
= (AbstractCallPeerJabberGTalkImpl<?,?,?>) target;
String to = getFullCalleeURI(targetJabberGTalkImpl.getAddress());
/*
* XEP-0251: Jingle Session Transfer says: Before doing
* [attended transfer], the attendant SHOULD verify that the callee
* supports Jingle session transfer.
*/
try
{
DiscoverInfo discoverInfo
= protocolProvider.getDiscoveryManager().discoverInfo(to);
if (!discoverInfo.containsFeature(
ProtocolProviderServiceJabberImpl
.URN_XMPP_JINGLE_TRANSFER_0))
{
throw new OperationFailedException(
"Callee "
+ to
+ " does not support"
+ " XEP-0251: Jingle Session Transfer",
OperationFailedException.INTERNAL_ERROR);
}
}
catch (XMPPException xmppe)
{
logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
}
transfer(
peer,
to, targetJabberGTalkImpl.getSID());
}
/**
* Transfers (in the sense of call transfer) a specific <tt>CallPeer</tt> to
* a specific callee address which may or may not already be participating
* in an active <tt>Call</tt>.
* <p>
* The method is suitable for providing the implementation of unattended
* call transfer (though no such requirement is imposed).
* </p>
*
* @param peer the <tt>CallPeer</tt> to be transfered to the specified
* callee address
* @param target the address of the callee to transfer <tt>peer</tt> to
* @throws OperationFailedException if something goes wrong
* @see OperationSetAdvancedTelephony#transfer(CallPeer, String)
*/
public void transfer(CallPeer peer, String target)
throws OperationFailedException
{
transfer(peer, target, null);
}
/**
* Transfer (in the sense of call transfer) a specific <tt>CallPeer</tt> to
* a specific callee address which may optionally be participating in an
* active <tt>Call</tt>.
*
* @param peer the <tt>CallPeer</tt> to be transfered to the specified
* callee address
* @param to the address of the callee to transfer <tt>peer</tt> to
* @param sid the Jingle session ID of the active <tt>Call</tt> between the
* local peer and the callee in the case of attended transfer; <tt>null</tt>
* in the case of unattended transfer
* @throws OperationFailedException if something goes wrong
*/
private void transfer(CallPeer peer, String to, String sid)
throws OperationFailedException
{
String caller = getFullCalleeURI(peer.getAddress());
try
{
DiscoverInfo discoverInfo
= protocolProvider.getDiscoveryManager().discoverInfo(caller);
if (!discoverInfo.containsFeature(
ProtocolProviderServiceJabberImpl
.URN_XMPP_JINGLE_TRANSFER_0))
{
throw new OperationFailedException(
"Caller "
+ caller
+ " does not support"
+ " XEP-0251: Jingle Session Transfer",
OperationFailedException.INTERNAL_ERROR);
}
}
catch (XMPPException xmppe)
{
logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
}
((CallPeerJabberImpl) peer).transfer(getFullCalleeURI(to), sid);
}
/**
* Transfer authority used for interacting with user for unknown calls
* and the requests for transfer.
* @param authority transfer authority.
*/
public void setTransferAuthority(TransferAuthority authority)
{
}
}