/* * Jitsi, 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.coin.*; 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.media.*; import net.java.sip.communicator.util.*; import org.jitsi.service.neomedia.*; import org.jivesoftware.smack.*; import org.jivesoftware.smack.filter.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smackx.packet.*; /** * Implements OperationSetTelephonyConferencing for Jabber. * * @author Lyubomir Marinov * @author Sebastien Vincent */ public class OperationSetTelephonyConferencingJabberImpl extends AbstractOperationSetTelephonyConferencing< ProtocolProviderServiceJabberImpl, OperationSetBasicTelephonyJabberImpl, CallJabberImpl, CallPeerJabberImpl, String> implements RegistrationStateChangeListener, PacketListener, PacketFilter { /** * The Logger used by the * OperationSetTelephonyConferencingJabberImpl class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class); /** * The value of the version attribute to be specified in the * outgoing conference-info root XML elements. */ private int version = 1; /** * Synchronization object. */ private final Object objSync = new Object(); /** * Initializes a new OperationSetTelephonyConferencingJabberImpl * instance which is to provide telephony conferencing services for the * specified Jabber ProtocolProviderService implementation. * * @param parentProvider the Jabber ProtocolProviderService * implementation which has requested the creation of the new instance and * for which the new instance is to provide telephony conferencing services */ public OperationSetTelephonyConferencingJabberImpl( ProtocolProviderServiceJabberImpl parentProvider) { super(parentProvider); } /** * Notifies all CallPeers associated with a specific Call * about changes in the telephony conference-related information. In * contrast, {@link #notifyAll()} notifies all CallPeers associated * with the telephony conference in which a specific Call is * participating. * * @param call the Call whose CallPeers are to be notified * about changes in the telephony conference-related information */ protected void notifyCallPeers(Call call) { if (call.isConferenceFocus()) { synchronized (objSync) { // send conference-info to all CallPeers of the call. Iterator callPeerIter = call.getCallPeers(); while (callPeerIter.hasNext()) notify(callPeerIter.next()); version++; } } } /** * Notifies all CallPeer associated with and established in a * specific call has occurred * * @param callPeer the CallPeer */ private void notify(CallPeer callPeer) { if(!(callPeer instanceof CallPeerJabberImpl)) return; // check that callPeer supports COIN before sending him a // conference-info String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress()); try { DiscoverInfo discoverInfo = parentProvider.getDiscoveryManager().discoverInfo(to); if (!discoverInfo.containsFeature( ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN)) { logger.info(callPeer.getAddress() + " does not support COIN"); return; } } catch (XMPPException xmppe) { logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe); } IQ iq = getConferenceInfo((CallPeerJabberImpl)callPeer, version); if (iq != null) parentProvider.getConnection().sendPacket(iq); } /** * Get media packet extension for the specified CallPeerJabberImpl. * * @param callPeer CallPeer * @param remote if the callPeer is remote or local * @return list of media packet extension */ private List getMedia( MediaAwareCallPeer callPeer, boolean remote) { CallPeerMediaHandler mediaHandler = callPeer.getMediaHandler(); List ret = new ArrayList(); long i = 1; for(MediaType mediaType : MediaType.values()) { MediaStream stream = mediaHandler.getStream(mediaType); if (stream != null) { MediaPacketExtension ext = new MediaPacketExtension(Long.toString(i)); long srcId = remote ? getRemoteSourceID(callPeer, mediaType) : stream.getLocalSourceID(); if (srcId != -1) ext.setSrcID(Long.toString(srcId)); ext.setType(mediaType.toString()); MediaDirection direction = remote ? getRemoteDirection(callPeer, mediaType) : stream.getDirection(); if (direction == null) direction = MediaDirection.INACTIVE; ext.setStatus(direction.toString()); ret.add(ext); i++; } } return ret; } /** * Get user packet extension for the specified CallPeerJabberImpl. * * @param callPeer CallPeer * @return user packet extension */ private UserPacketExtension getUser(CallPeer callPeer) { UserPacketExtension ext = new UserPacketExtension(callPeer.getAddress()); ext.setDisplayText(callPeer.getDisplayName()); EndpointPacketExtension endpoint = new EndpointPacketExtension(callPeer.getURI()); endpoint.setStatus(getEndpointStatus(callPeer)); if (callPeer instanceof MediaAwareCallPeer) { List medias = getMedia((MediaAwareCallPeer) callPeer, true); if(medias != null) { for(MediaPacketExtension media : medias) endpoint.addChildExtension(media); } } ext.addChildExtension(endpoint); return ext; } /** * Generates the text content to be put in the status XML element * of an endpoint XML element and which describes the state of a * specific CallPeer. * * @param callPeer the CallPeer which is to get its state described * in a status XML element of an endpoint XML element * @return the text content to be put in the status XML element of * an endpoint XML element and which describes the state of the * specified callPeer */ private EndpointStatusType getEndpointStatus(CallPeer callPeer) { CallPeerState callPeerState = callPeer.getState(); if (CallPeerState.ALERTING_REMOTE_SIDE.equals(callPeerState)) return EndpointStatusType.alerting; if (CallPeerState.CONNECTING.equals(callPeerState) || CallPeerState .CONNECTING_WITH_EARLY_MEDIA.equals(callPeerState)) return EndpointStatusType.pending; if (CallPeerState.DISCONNECTED.equals(callPeerState)) return EndpointStatusType.disconnected; if (CallPeerState.INCOMING_CALL.equals(callPeerState)) return EndpointStatusType.dialing_in; if (CallPeerState.INITIATING_CALL.equals(callPeerState)) return EndpointStatusType.dialing_out; /* * he/she is neither "hearing" the conference mix nor is his/her media * being mixed in the conference */ if (CallPeerState.ON_HOLD_LOCALLY.equals(callPeerState) || CallPeerState.ON_HOLD_MUTUALLY.equals(callPeerState)) return EndpointStatusType.on_hold; if (CallPeerState.CONNECTED.equals(callPeerState)) return EndpointStatusType.connected; return null; } /** * Generates the conference-info IQ to be sent to a specific * CallPeer in order to notify it of the current state of the * conference managed by the local peer. * * @param callPeer the CallPeer to generate conference-info XML for * @param version the value of the version attribute of the * conference-info root element of the conference-info XML to be * generated * @return the conference-info IQ to be sent to the specified * callPeer in order to notify it of the current state of the * conference managed by the local peer */ private IQ getConferenceInfo(CallPeerJabberImpl callPeer, int version) { String callPeerSID = callPeer.getSID(); if (callPeerSID == null) return null; CoinIQ iq = new CoinIQ(); CallJabberImpl call = callPeer.getCall(); iq.setFrom(call.getProtocolProvider().getOurJID()); iq.setTo(callPeer.getAddress()); iq.setType(Type.SET); iq.setEntity(getBasicTelephony().getProtocolProvider().getOurJID()); iq.setVersion(version); iq.setState(StateType.full); iq.setSID(callPeerSID); // conference-description iq.addExtension(new DescriptionPacketExtension()); // conference-state StatePacketExtension state = new StatePacketExtension(); List conferenceCallPeers = CallConference.getCallPeers(call); state.setUserCount( 1 /* the local peer/user */ + conferenceCallPeers.size()); iq.addExtension(state); // users UsersPacketExtension users = new UsersPacketExtension(); // user UserPacketExtension user = new UserPacketExtension("xmpp:" + parentProvider.getOurJID()); // endpoint EndpointPacketExtension endpoint = new EndpointPacketExtension( "xmpp:" + parentProvider.getOurJID()); endpoint.setStatus(EndpointStatusType.connected); // media List medias = getMedia(callPeer, false); for(MediaPacketExtension media : medias) endpoint.addChildExtension(media); user.addChildExtension(endpoint); users.addChildExtension(user); // other users for (CallPeer conferenceCallPeer : conferenceCallPeers) users.addChildExtension(getUser(conferenceCallPeer)); iq.addExtension(users); return iq; } /** * Implementation of method registrationStateChange from * interface RegistrationStateChangeListener for setting up (or down) * our JingleManager when an XMPPConnection is available * * @param evt the event received */ @Override public void registrationStateChanged(RegistrationStateChangeEvent evt) { super.registrationStateChanged(evt); RegistrationState registrationState = evt.getNewState(); if (RegistrationState.REGISTERED.equals(registrationState)) { if(logger.isDebugEnabled()) logger.debug("Subscribes to Coin packets"); subscribeForCoinPackets(); } else if (RegistrationState.UNREGISTERED.equals(registrationState)) { if(logger.isDebugEnabled()) logger.debug("Unsubscribes to Coin packets"); unsubscribeForCoinPackets(); } } /** * Creates a new outgoing Call into which conference callees are to * be invited by this OperationSetTelephonyConferencing. * * @return a new outgoing Call into which conference callees are to * be invited by this OperationSetTelephonyConferencing * @throws OperationFailedException if anything goes wrong */ protected CallJabberImpl createOutgoingCall() throws OperationFailedException { return new CallJabberImpl(getBasicTelephony()); } /** * {@inheritDoc} * * Implements the protocol-dependent part of the logic of inviting a callee * to a Call. The protocol-independent part of that logic is * implemented by * {@link AbstractOperationSetTelephonyConferencing#inviteCalleToCall(String,Call)}. */ protected CallPeer doInviteCalleeToCall( String calleeAddress, CallJabberImpl call) throws OperationFailedException { return getBasicTelephony().createOutgoingCall( call, calleeAddress, Arrays.asList( new PacketExtension[] { new CoinPacketExtension(true) })); } /** * Parses a String value which represents a callee address * specified by the user into an object which is to actually represent the * callee during the invitation to a conference Call. * * @param calleeAddressString a String value which represents a * callee address to be parsed into an object which is to actually represent * the callee during the invitation to a conference Call * @return an object which is to actually represent the specified * calleeAddressString during the invitation to a conference * Call * @throws OperationFailedException if parsing the specified * calleeAddressString fails */ protected String parseAddressString(String calleeAddressString) throws OperationFailedException { return getBasicTelephony().getFullCalleeURI(calleeAddressString); } /** * Subscribes us to notifications about incoming Coin packets. */ private void subscribeForCoinPackets() { parentProvider.getConnection().addPacketListener(this, this); } /** * Unsubscribes us from notifications about incoming Coin packets. */ private void unsubscribeForCoinPackets() { XMPPConnection connection = parentProvider.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 CoinIQs. * * @param packet the packet to test. * @return true if and only if packet passes the filter. */ public boolean accept(Packet packet) { return (packet instanceof CoinIQ); } /** * 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) { CoinIQ coinIQ = (CoinIQ) packet; //first ack all "set" requests. if (coinIQ.getType() == IQ.Type.SET) { IQ ack = IQ.createResultIQ(coinIQ); parentProvider.getConnection().sendPacket(ack); } String sid = coinIQ.getSID(); if (sid != null) { CallPeerJabberImpl callPeer = getBasicTelephony().getActiveCallsRepository().findCallPeer( sid); if (callPeer != null) handleCoin(callPeer, coinIQ); } } /** * Handles a specific CoinIQ sent from a specific * CallPeer. * * @param callPeer the CallPeer from which the specified * CoinIQ was sent * @param coinIQ the CoinIQ which was sent from the specified * callPeer */ private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ) { setConferenceInfoXML(callPeer, -1, coinIQ.getChildElementXML()); } }