Package net.java.sip.communicator.impl.protocol.jabber

Source Code of net.java.sip.communicator.impl.protocol.jabber.OperationSetBasicTelephonyJabberImpl

/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.jabber;

import java.util.*;

import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;

import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.packet.IQ.*;
import org.jivesoftware.smack.provider.*;
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
*/
public class OperationSetBasicTelephonyJabberImpl
   extends AbstractOperationSetBasicTelephony<ProtocolProviderServiceJabberImpl>
   implements RegistrationStateChangeListener,
              PacketListener,
              PacketFilter,
              OperationSetSecureTelephony,
              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 ActiveCallsRepositoryJabberImpl activeCallsRepository
        = new ActiveCallsRepositoryJabberImpl(this);

    /**
     * Contains references for all currently active (non ended) Google Talk
     * calls.
     */
    private ActiveCallsRepositoryGTalkImpl activeGTalkCallsRepository
        = new ActiveCallsRepositoryGTalkImpl(this);

    /**
     * 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)
        {
            // plug jingle unregistration
            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>
     * @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)
        throws OperationFailedException
    {
        CallJabberImpl call = new CallJabberImpl(this);
        CallPeer callPeer = null;

        callPeer = createOutgoingCall(call, callee);
        if (callPeer == null)
        {
            throw new OperationFailedException(
                    "Failed to create outgoing call"
                        + " because no peer was created",
                    OperationFailedException.INTERNAL_ERROR);
        }
        if(callPeer.getCall() != call)
        {
            // We may have a Google Talk call here
            return callPeer.getCall();
        }

        return call;
    }

    /**
     * Creates a new <tt>Call</tt> and invites a specific <tt>CallPeer</tt>
     * to it given by her <tt>Contact</tt>.
     *
     * @param callee the address of the callee who we should invite to a new
     * call
     * @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(Contact)
     */
    public Call createCall(Contact callee)
            throws OperationFailedException
    {
        return createCall(callee.getAddress());
    }

    /**
     * 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 (logger.isInfoEnabled())
            logger.info("creating outgoing call...");
        if (protocolProvider.getConnection() == null || call == null)
        {
            throw new OperationFailedException(
                    "Failed to create OutgoingJingleSession."
                        + " We don't have a valid XMPPConnection.",
                    OperationFailedException.INTERNAL_ERROR);
        }

        // we determine on which resource the remote user is connected if the
        // resource isn't already provided
        String fullCalleeURI = null;

        DiscoverInfo di = null;
        int bestPriority = -1;

        Iterator<Presence> it =
            getProtocolProvider().getConnection().getRoster().getPresences(
                calleeAddress);
        String calleeURI = null;
        boolean isGingle = false;
        String gingleURI = null;
        int bestPriorityGTalk = -1;

        // choose the resource that has the highest priority AND support Jingle
        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 discoverInfo =
                    protocolProvider.getDiscoveryManager().
                        discoverInfo(calleeURI);

                if (discoverInfo.containsFeature(
                        ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE))
                {
                    if(priority > bestPriority)
                    {
                        bestPriority = priority;
                        di = discoverInfo;
                        fullCalleeURI = calleeURI;
                    }
                }
                else
                {
                    // test GTALK property
                    if(!protocolProvider.isGTalkTesting())
                    {
                        continue;
                    }

                    /* see if peer supports Google Talk voice */
                    if(getProtocolProvider().isExtFeatureListSupported(
                            calleeURI, ProtocolProviderServiceJabberImpl.
                                CAPS_GTALK_WEB_VOICE))
                    {
                        if(priority > bestPriorityGTalk)
                        {
                            bestPriorityGTalk = priority;
                            isGingle = true;
                            gingleURI = calleeURI;
                        }
                    }
                }
            }
            catch (XMPPException ex)
            {
                logger.warn("could not retrieve info for " + fullCalleeURI, ex);
            }
        }

        /* 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);
        }
        */

        if(isGingle)
        {
            if(logger.isInfoEnabled())
            {
                logger.info(gingleURI + ": Google Talk dialect supported");
            }
            fullCalleeURI = gingleURI;
        }
        else if(di != null)
        {
            if (logger.isInfoEnabled())
                logger.info(fullCalleeURI + ": jingle supported ");
        }
        else
        {
            if (logger.isInfoEnabled())
                logger.info(calleeURI +
                        ": jingle and Google Talk not supported ?");

            throw new OperationFailedException(
                    "Failed to create OutgoingJingleSession.\n"
                        + calleeURI + " does not support jingle or Google Talk",
                    OperationFailedException.INTERNAL_ERROR);
        }

        if(logger.isInfoEnabled())
        {
            logger.info("Choose one is: " + fullCalleeURI + " " + bestPriority);
        }

        AbstractCallPeer<?, ?> peer = null;

        // initiate call
        try
        {
            if(isGingle)
            {
                logger.info("initiate Gingle call");
                CallGTalkImpl callGTalk = new CallGTalkImpl(this);
                MediaUseCase useCase = call.getMediaUseCase();
                boolean isVideo = call.isLocalVideoAllowed(useCase);

                callGTalk.setLocalVideoAllowed(isVideo, useCase);
                peer = callGTalk.initiateGTalkSession(fullCalleeURI,
                       sessionInitiateExtensions);
            }
            else if(di != null)
            {
                peer
                    = call.initiateSession(
                            fullCalleeURI,
                            di,
                            sessionInitiateExtensions);
            }
        }
        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;

            throw new OperationFailedException(
                    "Failed to create a call",
                    OperationFailedException.INTERNAL_ERROR,
                    t);
        }

        return peer;
    }

    /**
     * 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 an iterator over all currently Google Talk active calls.
     *
     * @return an iterator over all currently Google Talk active calls.
     */
    public Iterator<CallGTalkImpl> getGTalkActiveCalls()
    {
        return activeGTalkCallsRepository.getActiveCalls();
    }

    /**
     * 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);
    }

    /**
     * Sets the mute state of the <tt>CallJabberImpl</tt>.
     *
     * @param call the <tt>CallJabberImpl</tt> whose mute state is set
     * @param mute <tt>true</tt> to mute the call streams being sent to
     *            <tt>peers</tt>; otherwise, <tt>false</tt>
     */
    @Override
    public void setMute(Call call, boolean mute)
    {
        ((MediaAwareCall<?, ?, ?>) call).setMute(mute);
    }

    /**
     * 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
    {
        // XXX maybe add answer/hangup abstract method to MediaAwareCallPeer
        if(peer instanceof CallPeerJabberImpl)
        {
            ((CallPeerJabberImpl) peer).hangup(null, null);
        }
        else if(peer instanceof CallPeerGTalkImpl)
        {
            ((CallPeerGTalkImpl) peer).hangup(null, 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();
        }
        else if(peer instanceof CallPeerGTalkImpl)
        {
            ((CallPeerGTalkImpl) 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();
        Iterator<CallGTalkImpl> activeGTalkCalls
            = this.activeGTalkCallsRepository.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
                {
                    this.hangupCallPeer(peer);
                }
                catch (Exception ex)
                {
                    logger.warn("Failed to properly hangup peer " + peer, ex);
                }
            }
        }

        while(activeGTalkCalls.hasNext())
        {
            CallGTalkImpl call = activeGTalkCalls.next();
            Iterator<CallPeerGTalkImpl> callPeers = call.getCallPeers();

            //go through all call peers and say bye to every one.
            while (callPeers.hasNext())
            {
                CallPeer peer = callPeers.next();
                try
                {
                    this.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 to notifications about incoming jingle packets.
     */
    private void unsubscribeForJinglePackets()
    {
        if(protocolProvider.getConnection() != null)
        {
            protocolProvider.getConnection().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)
    {
        String sid = null;

        //we only handle JingleIQ-s
        if( ! (packet instanceof JingleIQ) && !(packet instanceof SessionIQ))
        {
            AbstractCallPeer<?, ?> callPeer =
                activeCallsRepository.findCallPeerBySessInitPacketID(
                    packet.getPacketID());

            if(callPeer == null)
            {
                callPeer = activeGTalkCallsRepository.
                    findCallPeerBySessInitPacketID(packet.getPacketID());
            }

            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)
                {
                    logger.error("Received an error: code=" + error.getCode()
                            + " message=" + error.getMessage());
                    String message = "Service unavailable";
                    Roster roster = getProtocolProvider().getConnection().
                        getRoster();

                    if(!roster.contains(packet.getFrom()))
                    {
                        message += ": try adding the contact to your contact " +
                                "list first.";
                    }

                    if (error.getMessage() != null)
                        message = error.getMessage();

                    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);
            }

            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.findJingleSID(sid) != null);
        }
        else if(packet instanceof SessionIQ)
        {
            SessionIQ sessionIQ = (SessionIQ)packet;

            if(sessionIQ.getGTalkType() == GTalkType.INITIATE)
            {
                return true;
            }

            sid = sessionIQ.getID();

            //if this is not a session's initiate we'll only take it if we've
            //already seen its session ID.
            return (activeGTalkCallsRepository.findSessionID(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)
    {
        //this is not supposed to happen because of the filter ... but still
        if (! (packet instanceof JingleIQ) && !(packet instanceof SessionIQ))
            return;

        // test GTALK property
        if(!protocolProvider.isGTalkTesting() && (packet instanceof SessionIQ))
        {
            return;
        }

        if(packet instanceof JingleIQ)
        {
            JingleIQ jingleIQ = (JingleIQ)packet;

            //to prevent hijacking sessions from other jingle based features
            //like file transfer for example,  we should only send the
            //ack if this is a session-initiate with rtp content or if we are
            //the owners of this packet's sid

            //first ack all "set" requests.
            if(jingleIQ.getType() == IQ.Type.SET)
            {
                IQ ack = IQ.createResultIQ(jingleIQ);
                protocolProvider.getConnection().sendPacket(ack);
            }

            try
            {
                processJingleIQ(jingleIQ);
            }
            catch(Throwable t)
            {
                logger.info("Error while handling incoming Jingle 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;
            }
        }
        else if(packet instanceof SessionIQ)
        {
            SessionIQ sessionIQ = (SessionIQ)packet;

            //first ack all "set" requests.
            if(sessionIQ.getType() == IQ.Type.SET)
            {
                IQ ack = IQ.createResultIQ(sessionIQ);
                protocolProvider.getConnection().sendPacket(ack);
            }

            try
            {
                processSessionIQ(sessionIQ);
            }
            catch(Throwable t)
            {
                logger.info("Error while handling incoming GTalk 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(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)
            {
                logger.error(" code=" + error.getCode()
                                + " message=" + error.getMessage());

                if (error.getMessage() != null)
                    message = error.getMessage();
            }

            if (callPeer != null)
                callPeer.setState(CallPeerState.FAILED, message);

            return;
        }

        JingleAction action = jingleIQ.getAction();

        if(action == JingleAction.SESSION_INITIATE)
        {
            CallJabberImpl call = null;

            TransferPacketExtension transfer
                = (TransferPacketExtension)
                    jingleIQ.getExtension(
                        TransferPacketExtension.ELEMENT_NAME,
                        TransferPacketExtension.NAMESPACE);

            if (transfer != null)
            {
                String sid = transfer.getSID();

                if (sid != null)
                {
                    CallJabberImpl attendantCall
                        =  getActiveCallsRepository().findJingleSID(sid);

                    if (attendantCall != null)
                    {
                        CallPeerJabberImpl attendant
                            = attendantCall.getPeer(sid);

                        if ((attendant != null)
                                && getFullCalleeURI(attendant.getAddress())
                                        .equals(transfer.getFrom())
                                && protocolProvider.getOurJID().equals(
                                        transfer.getTo()))
                        {
                            // OK transfer correspond to us
                            call = attendantCall;
                        }
                    }
                }
            }

            if(call == null)
            {
                call = new CallJabberImpl(this);
            }

            call.processSessionInitiate(jingleIQ);
            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);
        }
    }

    /**
     * Analyzes the <tt>sessionIQ</tt>'s action and passes it to the
     * corresponding handler.
     *
     * @param sessionIQ the {@link SessionIQ} packet we need to be analyzing.
     */
    private void processSessionIQ(SessionIQ sessionIQ)
    {
        //let's first see whether we have a peer that's concerned by this IQ
        CallPeerGTalkImpl callPeer =
            activeGTalkCallsRepository.findCallPeer(sessionIQ.getID());
        IQ.Type type = sessionIQ.getType();

        if(type == Type.RESULT)
        {
            return;
        }

        if (type == Type.ERROR)
        {
            logger.error("Received error");

            XMPPError error = sessionIQ.getError();
            String message = "Remote party returned an error!";

            if(error != null)
            {
                logger.error(" code=" + error.getCode()
                                + " message=" + error.getMessage());

                if (error.getMessage() != null)
                    message = error.getMessage();
            }

            if (callPeer != null)
                callPeer.setState(CallPeerState.FAILED, message);

            return;
        }

        GTalkType action = sessionIQ.getGTalkType();

        if(action == GTalkType.INITIATE)
        {
            CallGTalkImpl call = null;

            if(call == null)
            {
                call = new CallGTalkImpl(this);
            }

            call.processGTalkInitiate(sessionIQ);
            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 == GTalkType.CANDIDATES)
        {
            callPeer.processCandidates(sessionIQ);
        }
        else if(action == GTalkType.REJECT)
        {
            callPeer.processSessionReject(sessionIQ);
        }
        else if(action == GTalkType.TERMINATE)
        {
            callPeer.processSessionTerminate(sessionIQ);
        }
        else if(action == GTalkType.ACCEPT)
        {
            callPeer.processSessionAccept(sessionIQ);
        }
    }

    /**
     * Returns a reference to the {@link ActiveCallsRepositoryJabberImpl} that
     * we are currently using.
     *
     * @return a reference to the {@link ActiveCallsRepositoryJabberImpl} that
     * we are currently using.
     */
    protected ActiveCallsRepositoryJabberImpl getActiveCallsRepository()
    {
        return activeCallsRepository;
    }

    /**
     * Returns a reference to the {@link ActiveCallsRepositoryGTalkImpl} that
     * we are currently using.
     *
     * @return a reference to the {@link ActiveCallsRepositoryGTalkImpl} that
     * we are currently using.
     */
    protected ActiveCallsRepositoryGTalkImpl getGTalkActiveCallsRepository()
    {
        return activeGTalkCallsRepository;
    }

    /**
     * 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();
    }

    /**
     * Sets the SAS verifications state of the call session in which a specific
     * peer is involved
     *
     * @param peer the peer who toggled (or for whom is remotely
     *        toggled) the SAS verified flag
     * @param verified the new SAS verification status
     */
    public void setSasVerified(CallPeer peer, boolean verified)
    {
        ((MediaAwareCallPeer<?, ?, ?>) peer).getMediaHandler().setSasVerified(
                verified);
    }

    /**
     * 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
    {
        CallPeerJabberImpl targetJabberImpl = (CallPeerJabberImpl) target;
        String to = getFullCalleeURI(targetJabberImpl.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, targetJabberImpl.getJingleSID());
    }

    /**
     * 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)
    {
    }
}
TOP

Related Classes of net.java.sip.communicator.impl.protocol.jabber.OperationSetBasicTelephonyJabberImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.