getLocalContentList()
- {
- return localContentMap.values();
- }
-
- /**
- * Returns the quality control for video calls if any.
- *
- * @return the implemented quality control.
- */
- public QualityControl getQualityControl()
- {
- if(supportQualityControls)
- {
- return qualityControls;
- }
- else
- {
- // we have detected that its not supported and return null
- // and control ui won't be visible
- return null;
- }
- }
-
- /**
- * Get the remote content of a specific content type (like audio or video).
- *
- * @param contentType content type name
- * @return remote ContentPacketExtension or null if not found
- */
- public ContentPacketExtension getRemoteContent(String contentType)
- {
- for(String key : remoteContentMap.keySet())
- {
- ContentPacketExtension content = remoteContentMap.get(key);
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(content);
-
- if(description.getMedia().equals(contentType))
- return content;
- }
- return null;
- }
-
- /**
- * {@inheritDoc}
- *
- * In the case of a telephony conference organized by the local peer/user
- * via the Jitsi Videobridge server-side technology, returns an SSRC
- * reported by the server as received on the channel allocated by the local
- * peer/user for the purposes of communicating with the CallPeer
- * associated with this instance.
- */
- @Override
- public long getRemoteSSRC(MediaType mediaType)
- {
- int[] ssrcs = getRemoteSSRCs(mediaType);
-
- /*
- * A peer (regardless of whether it is local or remote) may send
- * multiple RTP streams at any time. In such a case, it is not clear
- * which one of their SSRCs is to be returned. Anyway, the super says
- * that the returned is the last known. We will presume that the last
- * known in the list reported by the Jitsi Videobridge server is the
- * last.
- */
- if (ssrcs.length != 0)
- return 0xFFFFFFFFL & ssrcs[ssrcs.length - 1];
-
- /*
- * XXX In the case of Jitsi Videobridge, the super implementation of
- * getRemoteSSRC(MediaType) cannot be trusted because there is a single
- * VideoMediaStream with multiple ReceiveStreams.
- */
- return
- getPeer().isJitsiVideobridge()
- ? SSRC_UNKNOWN
- : super.getRemoteSSRC(mediaType);
- }
-
- /**
- * Gets the SSRCs of RTP streams with a specific MediaType known to
- * be received by a MediaStream associated with this instance.
- *
- * Warning: The method may return only one of the many possible
- * remote SSRCs in the case of no utilization of the Jitsi Videobridge
- * server-side technology because the super implementation does not
- * currently provide support for keeping track of multiple remote SSRCs.
- *
- *
- * @param mediaType the MediaType of the RTP streams the SSRCs of
- * which are to be returned
- * @return an array of int values which represent the SSRCs of RTP
- * streams with the specified mediaType known to be received by a
- * MediaStream associated with this instance
- */
- private int[] getRemoteSSRCs(MediaType mediaType)
- {
- /*
- * If the Jitsi Videobridge server-side technology is utilized, a single
- * MediaStream (per MediaType) is shared among the participating
- * CallPeers and, consequently, the remote SSRCs cannot be associated
- * with the CallPeers from which they are actually being sent. That's
- * why the server will report them to the conference focus.
- */
- ColibriConferenceIQ.Channel channel = getColibriChannel(mediaType);
-
- if (channel != null)
- return channel.getSSRCs();
-
- /*
- * XXX The fallback to the super implementation that follows may lead to
- * unexpected behavior due to the lack of ability to keep track of
- * multiple remote SSRCs.
- */
- long ssrc = super.getRemoteSSRC(mediaType);
-
- return
- (ssrc == SSRC_UNKNOWN)
- ? ColibriConferenceIQ.NO_SSRCS
- : new int[] { (int) ssrc };
- }
-
- /**
- * Gets the TransportManager implementation handling our address
- * management.
- *
- * TODO: this method can and should be simplified.
- *
- * @return the TransportManager implementation handling our address
- * management
- * @see CallPeerMediaHandler#getTransportManager()
- */
- @Override
- protected synchronized TransportManagerJabberImpl getTransportManager()
- {
- if (transportManager == null)
- {
- CallPeerJabberImpl peer = getPeer();
-
- if (peer.isInitiator())
- {
- synchronized(transportManagerSyncRoot)
- {
- try
- {
- transportManagerSyncRoot.wait(5000);
- }
- catch(InterruptedException e)
- {
- }
- }
- if(transportManager == null)
- {
- throw new IllegalStateException(
- "The initiator is expected to specify the transport"
- + " in their offer.");
- }
- else
- return transportManager;
- }
- else
- {
- ProtocolProviderServiceJabberImpl protocolProvider
- = peer.getProtocolProvider();
- ScServiceDiscoveryManager discoveryManager
- = protocolProvider.getDiscoveryManager();
- DiscoverInfo peerDiscoverInfo = peer.getDiscoveryInfo();
-
- /*
- * If this.supportedTransports has been explicitly set, we use
- * it to select the transport manager -- we use the first
- * transport in the list which we recognize (e.g. the first
- * that is either ice or raw-udp
- */
- synchronized (supportedTransportsSyncRoot)
- {
- if (supportedTransports != null
- && supportedTransports.length > 0)
- {
- for (int i = 0; i < supportedTransports.length; i++)
- {
- if (ProtocolProviderServiceJabberImpl.
- URN_XMPP_JINGLE_ICE_UDP_1.
- equals(supportedTransports[i]))
- {
- transportManager
- = new IceUdpTransportManager(peer);
- break;
- }
- else if (ProtocolProviderServiceJabberImpl.
- URN_XMPP_JINGLE_RAW_UDP_0.
- equals(supportedTransports[i]))
- {
- transportManager
- = new RawUdpTransportManager(peer);
- break;
- }
- }
- if (transportManager == null)
- {
- logger.warn(
- "Could not find a supported"
- + " TransportManager in"
- + " supportedTransports. Will try to"
- + " select one based on disco#info.");
- }
- }
- }
-
- if (transportManager == null)
- {
- /*
- * The list of possible transports ordered by decreasing
- * preference.
- */
- String[] transports
- = new String[]
- {
- ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_ICE_UDP_1,
- ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_RAW_UDP_0
- };
-
- /*
- * If Jitsi Videobridge is to be employed, pick up a Jingle
- * transport supported by it.
- */
- if (peer.isJitsiVideobridge())
- {
- CallJabberImpl call = peer.getCall();
-
- if (call != null)
- {
- String jitsiVideobridge
- = peer.getCall().getJitsiVideobridge();
-
- /*
- * Jitsi Videobridge supports the Jingle Raw UDP
- * transport from its inception. But that is not the
- * case with the Jingle ICE-UDP transport.
- */
- if ((jitsiVideobridge != null)
- && !protocolProvider.isFeatureSupported(
- jitsiVideobridge,
- ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_ICE_UDP_1))
- {
- for (int i = transports.length - 1; i >= 0; i--)
- {
- if (ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_ICE_UDP_1
- .equals(transports[i]))
- {
- transports[i] = null;
- }
- }
- }
- }
- }
-
- /*
- * Select the first transport from the list of possible
- * transports ordered by decreasing preference which is
- * supported by the local and the remote peers.
- */
- for (String transport : transports)
- {
- if (transport == null)
- continue;
- if (isFeatureSupported(
- discoveryManager,
- peerDiscoverInfo,
- transport))
- {
- if (ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_ICE_UDP_1
- .equals(transport))
- {
- transportManager
- = new IceUdpTransportManager(peer);
- }
- else if (ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_RAW_UDP_0
- .equals(transport))
- {
- transportManager
- = new RawUdpTransportManager(peer);
- }
-
- if (transportManager != null)
- break;
- }
- }
-
- if ((transportManager == null) && logger.isDebugEnabled())
- {
- logger.debug(
- "No known Jingle transport supported by Jabber"
- + " call peer " + peer);
- }
- }
- }
- }
- return transportManager;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see CallPeerMediaHandler#queryTransportManager()
- */
- @Override
- protected synchronized TransportManagerJabberImpl queryTransportManager()
- {
- return transportManager;
- }
-
- /**
- * {@inheritDoc}
- *
- * In the case of utilization of the Jitsi Videobridge server-side
- * technology, returns the visual Components which display RTP
- * video streams reported by the server to be sent by the remote peer
- * represented by this instance.
- */
- @Override
- public List getVisualComponents()
- {
- /*
- * TODO The super is currently unable to provide the complete set of
- * remote SSRCs (i.e. in the case of no utilization of the Jitsi
- * Videobridge server-side technology) so we have to explicitly check
- * for Jitsi Videobridge instead of just relying on the implementation
- * of the getRemoteSSRCs(MediaType) method to abstract away that detail.
- */
- CallJabberImpl call;
- MediaAwareCallConference conference;
-
- if (((call = getPeer().getCall()) != null)
- && ((conference = call.getConference()) != null)
- && conference.isJitsiVideobridge())
- {
- MediaStream stream = getStream(MediaType.VIDEO);
-
- if (stream == null)
- return Collections.emptyList();
- else
- {
- int[] remoteSSRCs = getRemoteSSRCs(MediaType.VIDEO);
-
- if (remoteSSRCs.length == 0)
- return Collections.emptyList();
- else
- {
- VideoMediaStream videoStream = (VideoMediaStream) stream;
- List visualComponents
- = new LinkedList();
-
- for (int i = 0; i < remoteSSRCs.length; i++)
- {
- int remoteSSRC = remoteSSRCs[i];
- Component visualComponent
- = videoStream.getVisualComponent(
- 0xFFFFFFFFL & remoteSSRC);
-
- if (visualComponent != null)
- visualComponents.add(visualComponent);
- }
- return visualComponents;
- }
- }
- }
-
- return super.getVisualComponents();
- }
-
- /**
- * Gathers local candidate addresses.
- *
- * @param remote the media descriptions received from the remote peer if any
- * or null if local represents an offer from the local
- * peer to be sent to the remote peer
- * @param local the media descriptions sent or to be sent from the local
- * peer to the remote peer. If remote is null,
- * local represents an offer from the local peer to be sent to the
- * remote peer
- * @param transportInfoSender the TransportInfoSender to be used by
- * this TransportManagerJabberImpl to send transport-info
- * JingleIQs from the local peer to the remote peer if this
- * TransportManagerJabberImpl wishes to utilize
- * transport-info
- * @return the media descriptions of the local peer after the local
- * candidate addresses have been gathered as returned by
- * {@link TransportManagerJabberImpl#wrapupCandidateHarvest()}
- * @throws OperationFailedException if anything goes wrong while starting or
- * wrapping up the gathering of local candidate addresses
- */
- private List harvestCandidates(
- List remote,
- List local,
- TransportInfoSender transportInfoSender)
- throws OperationFailedException
- {
- long startCandidateHarvestTime = System.currentTimeMillis();
- TransportManagerJabberImpl transportManager = getTransportManager();
-
- if (remote == null)
- {
- /*
- * We'll be harvesting candidates in order to make an offer so it
- * doesn't make sense to send them in transport-info.
- */
- if (transportInfoSender != null)
- throw new IllegalArgumentException("transportInfoSender");
-
- transportManager.startCandidateHarvest(local, transportInfoSender);
- }
- else
- {
- transportManager.startCandidateHarvest(
- remote,
- local,
- transportInfoSender);
- }
-
- long stopCandidateHarvestTime = System.currentTimeMillis();
-
- if (logger.isInfoEnabled())
- {
- long candidateHarvestTime
- = stopCandidateHarvestTime - startCandidateHarvestTime;
-
- logger.info(
- "End candidate harvest within " + candidateHarvestTime
- + " ms");
- }
-
- setDtlsEncryptionOnTransports(remote, local);
-
- if (transportManager.startConnectivityEstablishmentWithJitsiVideobridge)
- {
- Map map
- = new LinkedHashMap();
-
- for (MediaType mediaType : MediaType.values())
- {
- ColibriConferenceIQ.Channel channel
- = transportManager.getColibriChannel(
- mediaType,
- true /* local */);
-
- if (channel != null)
- {
- IceUdpTransportPacketExtension transport
- = channel.getTransport();
-
- if (transport != null)
- map.put(mediaType.toString(), transport);
- }
- }
- if (!map.isEmpty())
- {
- transportManager
- .startConnectivityEstablishmentWithJitsiVideobridge
- = false;
- transportManager.startConnectivityEstablishment(map);
- }
- }
-
- /*
- * TODO Ideally, we wouldn't wrap up that quickly. We need to revisit
- * this.
- */
- return transportManager.wrapupCandidateHarvest();
- }
-
- /**
- * Creates if necessary, and configures the stream that this
- * MediaHandler is using for the MediaType matching the
- * one of the MediaDevice. This method extends the one already
- * available by adding a stream name, corresponding to a stream's content
- * name.
- *
- * @param streamName the name of the stream as indicated in the XMPP
- * content element.
- * @param connector the MediaConnector that we'd like to bind the
- * newly created stream to.
- * @param device the MediaDevice that we'd like to attach the newly
- * created MediaStream to.
- * @param format the MediaFormat that we'd like the new
- * MediaStream to be set to transmit in.
- * @param target the MediaStreamTarget containing the RTP and RTCP
- * address:port couples that the new stream would be sending packets to.
- * @param direction the MediaDirection that we'd like the new
- * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive).
- * @param rtpExtensions the list of RTPExtensions that should be
- * enabled for this stream.
- * @param masterStream whether the stream to be used as master if secured
- *
- * @return the newly created MediaStream.
- *
- * @throws OperationFailedException if creating the stream fails for any
- * reason (like for example accessing the device or setting the format).
- */
- protected MediaStream initStream(String streamName,
- StreamConnector connector,
- MediaDevice device,
- MediaFormat format,
- MediaStreamTarget target,
- MediaDirection direction,
- List rtpExtensions,
- boolean masterStream)
- throws OperationFailedException
- {
- MediaStream stream
- = super.initStream(
- connector,
- device,
- format,
- target,
- direction,
- rtpExtensions,
- masterStream);
-
- if (stream != null)
- stream.setName(streamName);
-
- return stream;
- }
-
- /**
- * {@inheritDoc}
- *
- * In the case of a telephony conference organized by the local peer/user
- * and utilizing the Jitsi Videobridge server-side technology, a single
- * MediaHandler is shared by multiple
- * CallPeerMediaHandlers in order to have a single
- * AudioMediaStream and a single VideoMediaStream.
- * However, CallPeerMediaHandlerJabberImpl has redefined the
- * reading/getting the remote audio and video SSRCs. Consequently,
- * CallPeerMediaHandlerJabberImpl has to COMPLETELY redefine the
- * writing/setting as well i.e. it has to stop related
- * PropertyChangeEvents fired by the super.
- */
- @Override
- protected void mediaHandlerPropertyChange(PropertyChangeEvent ev)
- {
- String propertyName = ev.getPropertyName();
-
- if ((AUDIO_REMOTE_SSRC.equals(propertyName)
- || VIDEO_REMOTE_SSRC.equals(propertyName))
- && getPeer().isJitsiVideobridge())
- return;
-
- super.mediaHandlerPropertyChange(ev);
- }
-
- /**
- * Handles the specified answer by creating and initializing the
- * corresponding MediaStreams.
- *
- * @param answer the Jingle answer
- *
- * @throws OperationFailedException if we fail to handle answer for
- * reasons like failing to initialize media devices or streams.
- * @throws IllegalArgumentException if there's a problem with the syntax or
- * the semantics of answer. Method is synchronized in order to
- * avoid closing mediaHandler when we are currently in process of
- * initializing, configuring and starting streams and anybody interested
- * in this operation can synchronize to the mediaHandler instance to wait
- * processing to stop (method setState in CallPeer).
- */
- public void processAnswer(List answer)
- throws OperationFailedException,
- IllegalArgumentException
- {
- /*
- * The answer given in session-accept may contain transport-related
- * information compatible with that carried in transport-info.
- */
- processTransportInfo(answer);
-
- boolean masterStreamSet = false;
-
- for (ContentPacketExtension content : answer)
- {
- remoteContentMap.put(content.getName(), content);
-
- boolean masterStream = false;
-
- // if we have more than one stream, let the audio be the master
- if(!masterStreamSet)
- {
- if(answer.size() > 1)
- {
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(content);
-
- if(MediaType.AUDIO.toString().equals(
- description.getMedia()))
- {
- masterStream = true;
- masterStreamSet = true;
- }
- }
- else
- {
- masterStream = true;
- masterStreamSet = true;
- }
- }
-
- processContent(content, false, masterStream);
- }
- }
-
- /**
- * Notifies this instance that a specific ColibriConferenceIQ has
- * been received. This CallPeerMediaHandler uses the part of the
- * information provided in the specified conferenceIQ which
- * concerns it only.
- *
- * @param conferenceIQ the ColibriConferenceIQ which has been
- * received
- */
- void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * This CallPeerMediaHandler stores the media information but it does
- * not store the colibri Channels (which contain both media and transport
- * information). The TransportManager associated with this instance
- * stores the colibri Channels but does not store media information (such
- * as the remote SSRCs). An design/implementation choice has to be made
- * though and the present one is to have this CallPeerMediaHandler
- * transparently (with respect to the TransportManager) store the media
- * information inside the TransportManager.
- */
- TransportManagerJabberImpl transportManager = this.transportManager;
-
- if (transportManager != null)
- {
- long oldAudioRemoteSSRC = getRemoteSSRC(MediaType.AUDIO);
- long oldVideoRemoteSSRC = getRemoteSSRC(MediaType.VIDEO);
-
- for (MediaType mediaType : MediaType.values())
- {
- ColibriConferenceIQ.Channel dst
- = transportManager.getColibriChannel(
- mediaType,
- false /* remote */);
-
- if (dst != null)
- {
- ColibriConferenceIQ.Content content
- = conferenceIQ.getContent(mediaType.toString());
-
- if (content != null)
- {
- ColibriConferenceIQ.Channel src
- = content.getChannel(dst.getID());
-
- if (src != null)
- {
- int[] ssrcs = src.getSSRCs();
- int[] dstSSRCs = dst.getSSRCs();
-
- if (!Arrays.equals(dstSSRCs, ssrcs))
- dst.setSSRCs(ssrcs);
- }
- }
- }
- }
-
- /*
- * Do fire new PropertyChangeEvents for the properties
- * AUDIO_REMOTE_SSRC and VIDEO_REMOTE_SSRC if necessary.
- */
- long newAudioRemoteSSRC = getRemoteSSRC(MediaType.AUDIO);
- long newVideoRemoteSSRC = getRemoteSSRC(MediaType.VIDEO);
-
- if (oldAudioRemoteSSRC != newAudioRemoteSSRC)
- {
- firePropertyChange(
- AUDIO_REMOTE_SSRC,
- oldAudioRemoteSSRC, newAudioRemoteSSRC);
- }
- if (oldVideoRemoteSSRC != newVideoRemoteSSRC)
- {
- firePropertyChange(
- VIDEO_REMOTE_SSRC,
- oldVideoRemoteSSRC, newVideoRemoteSSRC);
- }
- }
- }
-
- /**
- * Process a ContentPacketExtension and initialize its
- * corresponding MediaStream.
- *
- * @param content a ContentPacketExtension
- * @param modify if it correspond to a content-modify for resolution change
- * @param masterStream whether the stream to be used as master
- * @throws OperationFailedException if we fail to handle content
- * for reasons like failing to initialize media devices or streams.
- * @throws IllegalArgumentException if there's a problem with the syntax or
- * the semantics of content. The method is synchronized in order to
- * avoid closing mediaHandler when we are currently in process of
- * initializing, configuring and starting streams and anybody interested
- * in this operation can synchronize to the mediaHandler instance to wait
- * processing to stop (method setState in CallPeer).
- */
- private void processContent(
- ContentPacketExtension content,
- boolean modify,
- boolean masterStream)
- throws OperationFailedException,
- IllegalArgumentException
- {
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(content);
- MediaType mediaType
- = MediaType.parseString(description.getMedia());
-
- //stream target
- TransportManagerJabberImpl transportManager = getTransportManager();
- MediaStreamTarget target = transportManager.getStreamTarget(mediaType);
-
- if (target == null)
- target = JingleUtils.extractDefaultTarget(content);
-
- // no target port - try next media description
- if((target == null) || (target.getDataAddress().getPort() == 0))
- {
- closeStream(mediaType);
- return;
- }
-
- List supportedFormats = JingleUtils.extractFormats(
- description, getDynamicPayloadTypes());
-
- MediaDevice dev = getDefaultDevice(mediaType);
-
- if(!isDeviceActive(dev))
- {
- closeStream(mediaType);
- return;
- }
-
- MediaDirection devDirection
- = (dev == null) ? MediaDirection.INACTIVE : dev.getDirection();
-
- // Take the preference of the user with respect to streaming
- // mediaType into account.
- devDirection
- = devDirection.and(getDirectionUserPreference(mediaType));
-
- if (supportedFormats.isEmpty())
- {
- //remote party must have messed up our Jingle description.
- //throw an exception.
- ProtocolProviderServiceJabberImpl.throwOperationFailedException(
- "Remote party sent an invalid Jingle answer.",
- OperationFailedException.ILLEGAL_ARGUMENT,
- null,
- logger);
- }
-
- CallJabberImpl call = getPeer().getCall();
- CallConference conference
- = (call == null) ? null : call.getConference();
-
- /*
- * Neither SDES nor ZRTP is supported in telephony conferences utilizing
- * the server-side technology Jitsi Videobridge yet.
- */
- if ((conference == null) || !conference.isJitsiVideobridge())
- {
- addZrtpAdvertisedEncryptions(true, description, mediaType);
- addSDesAdvertisedEncryptions(true, description, mediaType);
- }
- addDtlsAdvertisedEncryptions(true, content, mediaType);
-
- StreamConnector connector
- = transportManager.getStreamConnector(mediaType);
-
- //determine the direction that we need to announce.
- MediaDirection remoteDirection
- = JingleUtils.getDirection(content, getPeer().isInitiator());
- /*
- * If we are the focus of a conference, we need to take into account the
- * other participants.
- */
- if ((conference != null) && conference.isConferenceFocus())
- {
- for (CallPeerJabberImpl peer : call.getCallPeerList())
- {
- SendersEnum senders
- = peer.getSenders(mediaType);
- boolean initiator = peer.isInitiator();
- //check if the direction of the jingle session we have with
- //this peer allows us receiving media. If senders is null,
- //assume the default of 'both'
- if ((senders == null)
- || (SendersEnum.both == senders)
- || (initiator && SendersEnum.initiator == senders)
- || (!initiator && SendersEnum.responder == senders))
- {
- remoteDirection
- = remoteDirection.or(MediaDirection.SENDONLY);
- }
- }
- }
-
- MediaDirection direction
- = devDirection.getDirectionForAnswer(remoteDirection);
-
- // update the RTP extensions that we will be exchanging.
- List remoteRTPExtensions
- = JingleUtils.extractRTPExtensions(
- description,
- getRtpExtensionsRegistry());
- List supportedExtensions
- = getExtensionsForType(mediaType);
- List rtpExtensions
- = intersectRTPExtensions(remoteRTPExtensions, supportedExtensions);
-
- Map adv
- = supportedFormats.get(0).getAdvancedAttributes();
-
- if(adv != null)
- {
- for(Map.Entry f : adv.entrySet())
- {
- if(f.getKey().equals("imageattr"))
- supportQualityControls = true;
- }
- }
-
- // check for options from remote party and set them locally
- if(mediaType.equals(MediaType.VIDEO) && modify)
- {
- // update stream
- MediaStream stream = getStream(MediaType.VIDEO);
-
- if(stream != null && dev != null)
- {
- List fmts = supportedFormats;
-
- if(fmts.size() > 0)
- {
- MediaFormat fmt = fmts.get(0);
-
- ((VideoMediaStream)stream).updateQualityControl(
- fmt.getAdvancedAttributes());
- }
- }
-
- if(qualityControls != null)
- {
- QualityPreset receiveQualityPreset
- = qualityControls.getRemoteReceivePreset();
- QualityPreset sendQualityPreset
- = qualityControls.getRemoteSendMaxPreset();
-
- supportedFormats
- = (dev == null)
- ? null
- : intersectFormats(
- supportedFormats,
- getLocallySupportedFormats(
- dev,
- sendQualityPreset,
- receiveQualityPreset));
- }
- }
-
- // create the corresponding stream...
- initStream(
- content.getName(),
- connector,
- dev,
- supportedFormats.get(0),
- target,
- direction,
- rtpExtensions,
- masterStream);
- }
-
- /**
- * Parses and handles the specified offer and returns a content
- * extension representing the current state of this media handler. This
- * method MUST only be called when offer is the first session
- * description that this MediaHandler is seeing.
- *
- * @param offer the offer that we'd like to parse, handle and get an answer
- * for.
- *
- * @throws OperationFailedException if we have a problem satisfying the
- * description received in offer (e.g. failed to open a device or
- * initialize a stream ...).
- * @throws IllegalArgumentException if there's a problem with
- * offer's format or semantics.
- */
- public void processOffer(List offer)
- throws OperationFailedException,
- IllegalArgumentException
- {
- // prepare to generate answers to all the incoming descriptions
- List answer
- = new ArrayList(offer.size());
- boolean atLeastOneValidDescription = false;
-
- for (ContentPacketExtension content : offer)
- {
- remoteContentMap.put(content.getName(), content);
-
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(content);
- MediaType mediaType
- = MediaType.parseString( description.getMedia() );
-
- List remoteFormats
- = JingleUtils.extractFormats(
- description,
- getDynamicPayloadTypes());
-
- MediaDevice dev = getDefaultDevice(mediaType);
-
- MediaDirection devDirection
- = (dev == null) ? MediaDirection.INACTIVE : dev.getDirection();
-
- // Take the preference of the user with respect to streaming
- // mediaType into account.
- devDirection
- = devDirection.and(getDirectionUserPreference(mediaType));
-
- // determine the direction that we need to announce.
- MediaDirection remoteDirection = JingleUtils.getDirection(
- content, getPeer().isInitiator());
- MediaDirection direction
- = devDirection.getDirectionForAnswer(remoteDirection);
-
- // intersect the MediaFormats of our device with remote ones
- List mutuallySupportedFormats
- = intersectFormats(
- remoteFormats,
- getLocallySupportedFormats(dev));
-
- // check whether we will be exchanging any RTP extensions.
- List offeredRTPExtensions
- = JingleUtils.extractRTPExtensions(
- description, this.getRtpExtensionsRegistry());
-
- List supportedExtensions
- = getExtensionsForType(mediaType);
-
- List rtpExtensions = intersectRTPExtensions(
- offeredRTPExtensions, supportedExtensions);
-
- // transport
- /*
- * RawUdpTransportPacketExtension extends
- * IceUdpTransportPacketExtension so getting
- * IceUdpTransportPacketExtension should suffice.
- */
- IceUdpTransportPacketExtension transport
- = content.getFirstChildOfType(
- IceUdpTransportPacketExtension.class);
-
- // stream target
- MediaStreamTarget target = null;
-
- try
- {
- target = JingleUtils.extractDefaultTarget(content);
- }
- catch(IllegalArgumentException e)
- {
- logger.warn("Fail to extract default target", e);
- }
-
- // according to XEP-176, transport element in session-initiate
- // "MAY instead be empty (with each candidate to be sent as the
- // payload of a transport-info message)".
- int targetDataPort
- = (target == null && transport != null)
- ? -1
- : (target != null) ? target.getDataAddress().getPort() : 0;
-
- /*
- * TODO If the offered transport is not supported, attempt to fall
- * back to a supported one using transport-replace.
- */
- setTransportManager(transport.getNamespace());
-
- if (mutuallySupportedFormats.isEmpty()
- || (devDirection == MediaDirection.INACTIVE)
- || (targetDataPort == 0))
- {
- // skip stream and continue. contrary to sip we don't seem to
- // need to send per-stream disabling answer and only one at the
- // end.
-
- //close the stream in case it already exists
- closeStream(mediaType);
- continue;
- }
-
- SendersEnum senders = JingleUtils.getSenders(
- direction,
- !getPeer().isInitiator());
- // create the answer description
- ContentPacketExtension ourContent
- = JingleUtils.createDescription(
- content.getCreator(),
- content.getName(),
- senders,
- mutuallySupportedFormats,
- rtpExtensions,
- getDynamicPayloadTypes(),
- getRtpExtensionsRegistry());
-
- /*
- * Sets ZRTP, SDES or DTLS-SRTP depending on the preferences for
- * this account.
- */
- setAndAddPreferredEncryptionProtocol(
- mediaType,
- ourContent,
- content);
-
- // Got a content which has inputevt. It means that the peer requests
- // a desktop sharing session so tell it we support inputevt.
- if(content.getChildExtensionsOfType(InputEvtPacketExtension.class)
- != null)
- {
- ourContent.addChildExtension(new InputEvtPacketExtension());
- }
-
- answer.add(ourContent);
- localContentMap.put(content.getName(), ourContent);
-
- atLeastOneValidDescription = true;
- }
-
- if (!atLeastOneValidDescription)
- {
- ProtocolProviderServiceJabberImpl.throwOperationFailedException(
- "Offer contained no media formats"
- + " or no valid media descriptions.",
- OperationFailedException.ILLEGAL_ARGUMENT,
- null,
- logger);
- }
-
- /*
- * In order to minimize post-pickup delay, start establishing the
- * connectivity prior to ringing.
- */
- harvestCandidates(
- offer,
- answer,
- new TransportInfoSender()
- {
- public void sendTransportInfo(
- Iterable contents)
- {
- getPeer().sendTransportInfo(contents);
- }
- });
-
- /*
- * While it may sound like we can completely eliminate the post-pickup
- * delay by waiting for the connectivity establishment to finish, it may
- * not be possible in all cases. We are the Jingle session responder so,
- * in the case of the ICE UDP transport, we are not the controlling ICE
- * Agent and we cannot be sure when the controlling ICE Agent will
- * perform the nomination. It could, for example, choose to wait for our
- * session-accept to perform the nomination which will deadlock us if we
- * have chosen to wait for the connectivity establishment to finish
- * before we begin ringing and send session-accept.
- */
- getTransportManager().startConnectivityEstablishment(offer);
- }
-
- /**
- * Processes the transport-related information provided by the remote
- * peer in a specific set of ContentPacketExtensions.
- *
- * @param contents the ContentPacketExtenions provided by the
- * remote peer and containing the transport-related information to
- * be processed
- * @throws OperationFailedException if anything goes wrong while processing
- * the transport-related information provided by the remote peer in
- * the specified set of ContentPacketExtensions
- */
- public void processTransportInfo(Iterable contents)
- throws OperationFailedException
- {
- if (getTransportManager().startConnectivityEstablishment(contents))
- {
- //Emil: why the heck is this here and why is it commented?
- //wrapupConnectivityEstablishment();
- }
- }
-
- /**
- * Reinitialize all media contents.
- *
- * @throws OperationFailedException if we fail to handle content
- * for reasons like failing to initialize media devices or streams.
- * @throws IllegalArgumentException if there's a problem with the syntax or
- * the semantics of content. Method is synchronized in order to
- * avoid closing mediaHandler when we are currently in process of
- * initializing, configuring and starting streams and anybody interested
- * in this operation can synchronize to the mediaHandler instance to wait
- * processing to stop (method setState in CallPeer).
- */
- public void reinitAllContents()
- throws OperationFailedException,
- IllegalArgumentException
- {
- boolean masterStreamSet = false;
- for(String key : remoteContentMap.keySet())
- {
- ContentPacketExtension ext = remoteContentMap.get(key);
-
- boolean masterStream = false;
- // if we have more than one stream, lets the audio be the master
- if(!masterStreamSet)
- {
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(ext);
- MediaType mediaType
- = MediaType.parseString( description.getMedia() );
-
- if(remoteContentMap.size() > 1)
- {
- if(mediaType.equals(MediaType.AUDIO))
- {
- masterStream = true;
- masterStreamSet = true;
- }
- }
- else
- {
- masterStream = true;
- masterStreamSet = true;
- }
- }
-
- if(ext != null)
- processContent(ext, false, masterStream);
- }
- }
-
- /**
- * Reinitialize a media content such as video.
- *
- * @param name name of the Jingle content
- * @param content media content
- * @param modify if it correspond to a content-modify for resolution change
- * @throws OperationFailedException if we fail to handle content
- * for reasons like failing to initialize media devices or streams.
- * @throws IllegalArgumentException if there's a problem with the syntax or
- * the semantics of content. Method is synchronized in order to
- * avoid closing mediaHandler when we are currently in process of
- * initializing, configuring and starting streams and anybody interested
- * in this operation can synchronize to the mediaHandler instance to wait
- * processing to stop (method setState in CallPeer).
- */
- public void reinitContent(
- String name,
- ContentPacketExtension content,
- boolean modify)
- throws OperationFailedException,
- IllegalArgumentException
- {
- ContentPacketExtension ext = remoteContentMap.get(name);
-
- if(ext != null)
- {
- if(modify)
- {
- processContent(content, modify, false);
- remoteContentMap.put(name, content);
- }
- else
- {
- ext.setSenders(content.getSenders());
- processContent(ext, modify, false);
- remoteContentMap.put(name, ext);
- }
- }
- }
-
- /**
- * Removes a media content with a specific name from the session represented
- * by this CallPeerMediaHandlerJabberImpl and closes its associated
- * media stream.
- *
- * @param contentMap the Map in which the specified name
- * has an association with the media content to be removed
- * @param name the name of the media content to be removed from this session
- */
- private void removeContent(
- Map contentMap,
- String name)
- {
- ContentPacketExtension content = contentMap.remove(name);
-
- if (content != null)
- {
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(content);
- String media = description.getMedia();
-
- if (media != null)
- closeStream(MediaType.parseString(media));
- }
- }
-
- /**
- * Removes a media content with a specific name from the session represented
- * by this CallPeerMediaHandlerJabberImpl and closes its associated
- * media stream.
- *
- * @param name the name of the media content to be removed from this session
- */
- public void removeContent(String name)
- {
- removeContent(localContentMap, name);
- removeContent(remoteContentMap, name);
-
- TransportManagerJabberImpl transportManager = queryTransportManager();
-
- if (transportManager != null)
- transportManager.removeContent(name);
- }
-
- /**
- * Acts upon a notification received from the remote party indicating that
- * they've put us on/off hold.
- *
- * @param onHold true if the remote party has put us on hold
- * and false if they've just put us off hold.
- */
- public void setRemotelyOnHold(boolean onHold)
- {
- this.remotelyOnHold = onHold;
-
- for (MediaType mediaType : MediaType.values())
- {
- MediaStream stream = getStream(mediaType);
-
- if (stream == null)
- continue;
-
- if (getPeer().isJitsiVideobridge())
- {
- /*
- * If we are the focus of a Videobridge conference, we need to
- * ask the Videobridge to change the stream direction on our
- * behalf.
- */
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType);
- MediaDirection direction;
-
- if(remotelyOnHold)
- {
- direction = MediaDirection.INACTIVE;
- }
- else
- {
- // TODO Does SENDRECV always make sense?
- direction = MediaDirection.SENDRECV;
- }
- getPeer().getCall().setChannelDirection(
- channel.getID(),
- mediaType,
- direction);
- }
- else //no Videobridge
- {
- if (remotelyOnHold)
- {
- /*
- * In conferences we use INACTIVE to prevent, for example,
- * on-hold music from being played to all the participants.
- */
- MediaDirection newDirection
- = getPeer().getCall().isConferenceFocus()
- ? MediaDirection.INACTIVE
- : stream.getDirection().and(
- MediaDirection.RECVONLY);
-
- stream.setDirection(newDirection);
- }
- else
- {
- stream.setDirection(calculatePostHoldDirection(stream));
- }
- }
- }
- }
-
- /**
- * Sometimes as initing a call with custom preset can set and we force
- * that quality controls is supported.
- *
- * @param value whether quality controls is supported..
- */
- public void setSupportQualityControls(boolean value)
- {
- this.supportQualityControls = value;
- }
-
- /**
- * Sets the TransportManager implementation to handle our address
- * management by Jingle transport XML namespace.
- *
- * @param xmlns the Jingle transport XML namespace specifying the
- * TransportManager implementation type to be set on this instance
- * to handle our address management
- * @throws IllegalArgumentException if the specified xmlns does not
- * specify a (supported) TransportManager implementation type
- */
- private void setTransportManager(String xmlns)
- throws IllegalArgumentException
- {
- // Is this really going to be an actual change?
- if ((transportManager != null)
- && transportManager.getXmlNamespace().equals(xmlns))
- {
- return;
- }
-
- CallPeerJabberImpl peer = getPeer();
-
- if (!peer.getProtocolProvider().getDiscoveryManager().includesFeature(
- xmlns))
- {
- throw new IllegalArgumentException(
- "Unsupported Jingle transport " + xmlns);
- }
-
- /*
- * TODO The transportManager is going to be changed so it may need to be
- * disposed of prior to the change.
- */
-
- if (xmlns.equals(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1))
- {
- transportManager = new IceUdpTransportManager(peer);
- }
- else if (xmlns.equals(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0))
- {
- transportManager = new RawUdpTransportManager(peer);
- }
- else
- {
- throw new IllegalArgumentException(
- "Unsupported Jingle transport " + xmlns);
- }
-
- synchronized(transportManagerSyncRoot)
- {
- transportManagerSyncRoot.notify();
- }
- }
-
- /**
- * Waits for the associated TransportManagerJabberImpl to conclude
- * any started connectivity establishment and then starts this
- * CallPeerMediaHandler.
- *
- * @throws IllegalStateException if no offer or answer has been provided or
- * generated earlier
- */
- @Override
- public void start()
- throws IllegalStateException
- {
- try
- {
- wrapupConnectivityEstablishment();
- }
- catch (OperationFailedException ofe)
- {
- throw new UndeclaredThrowableException(ofe);
- }
-
- super.start();
- }
-
- /**
- * Lets the underlying implementation take note of this error and only
- * then throws it to the using bundles.
- *
- * @param message the message to be logged and then wrapped in a new
- * OperationFailedException
- * @param errorCode the error code to be assigned to the new
- * OperationFailedException
- * @param cause the Throwable that has caused the necessity to log
- * an error and have a new OperationFailedException thrown
- *
- * @throws OperationFailedException the exception that we wanted this method
- * to throw.
- */
- @Override
- protected void throwOperationFailedException(
- String message,
- int errorCode,
- Throwable cause)
- throws OperationFailedException
- {
- ProtocolProviderServiceJabberImpl.throwOperationFailedException(
- message,
- errorCode,
- cause,
- logger);
- }
-
- /**
- * Notifies the associated TransportManagerJabberImpl that it
- * should conclude any connectivity establishment, waits for it to actually
- * do so and sets the connectors and targets of the
- * MediaStreams managed by this CallPeerMediaHandler.
- *
- * @throws OperationFailedException if anything goes wrong while setting the
- * connectors and/or targets of the MediaStreams
- * managed by this CallPeerMediaHandler
- */
- private void wrapupConnectivityEstablishment()
- throws OperationFailedException
- {
- TransportManagerJabberImpl transportManager = getTransportManager();
-
- transportManager.wrapupConnectivityEstablishment();
- for (MediaType mediaType : MediaType.values())
- {
- MediaStream stream = getStream(mediaType);
-
- if (stream != null)
- {
- stream.setConnector(
- transportManager.getStreamConnector(mediaType));
- stream.setTarget(transportManager.getStreamTarget(mediaType));
- }
- }
- }
-
-
-
- /**
- * If Jitsi Videobridge is in use, returns the
- * ColibriConferenceIQ.Channel that this
- * CallPeerMediaHandler uses for media of type mediaType.
- * Otherwise, returns null
- *
- * @param mediaType the MediaType for which to return a
- * ColibriConferenceIQ.Channel
- * @return the ColibriConferenceIQ.Channel that this
- * CallPeerMediaHandler uses for media of type mediaType
- * or null.
- */
- private ColibriConferenceIQ.Channel getColibriChannel(MediaType mediaType)
- {
- ColibriConferenceIQ.Channel channel = null;
-
- if (getPeer().isJitsiVideobridge())
- {
- TransportManagerJabberImpl transportManager = this.transportManager;
-
- if (transportManager != null)
- {
- channel
- = transportManager.getColibriChannel(
- mediaType,
- false /* remote */);
- }
- }
- return channel;
- }
-
- /**
- * {@inheritDoc}
- *
- * The super implementation relies on the direction of the streams and is
- * therefore not accurate when we use a Videobridge.
- */
- @Override
- public boolean isRemotelyOnHold()
- {
- return remotelyOnHold;
- }
-
- /**
- * {@inheritDoc}
- *
- * Handles the case when a Videobridge is in use.
- *
- * @param locallyOnHold true if we are to make our streams
- * stop transmitting and false if we are to start transmitting
- */
- @Override
- public void setLocallyOnHold(boolean locallyOnHold)
- {
- CallPeerJabberImpl peer = getPeer();
-
- if (peer.isJitsiVideobridge())
- {
- this.locallyOnHold = locallyOnHold;
-
- if (locallyOnHold
- || !CallPeerState.ON_HOLD_MUTUALLY.equals(peer.getState()))
- {
- for (MediaType mediaType : MediaType.values())
- {
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType);
-
- if (channel != null)
- {
- MediaDirection direction
- = locallyOnHold
- ? MediaDirection.INACTIVE
- : MediaDirection.SENDRECV;
-
- peer.getCall().setChannelDirection(
- channel.getID(),
- mediaType,
- direction);
- }
- }
- }
- }
- else
- {
- super.setLocallyOnHold(locallyOnHold);
- }
- }
-
- /**
- * Detects and adds DTLS-SRTP available encryption method present in the
- * content (description) given in parameter.
- *
- * @param isInitiator true if the local call instance is the
- * initiator of the call; false, otherwise.
- * @param content The CONTENT element of the JINGLE element which contains
- * the TRANSPORT element
- * @param mediaType The type of media (AUDIO or VIDEO).
- */
- private boolean addDtlsAdvertisedEncryptions(
- boolean isInitiator,
- ContentPacketExtension content,
- MediaType mediaType)
- {
- if (getPeer().isJitsiVideobridge())
- {
- // TODO Auto-generated method stub
- return false;
- }
- else
- {
- IceUdpTransportPacketExtension remoteTransport
- = content.getFirstChildOfType(
- IceUdpTransportPacketExtension.class);
-
- return
- addDtlsAdvertisedEncryptions(
- isInitiator,
- remoteTransport,
- mediaType);
- }
- }
-
- /**
- * Detects and adds DTLS-SRTP available encryption method present in the
- * transport (description) given in parameter.
- *
- * @param isInitiator true if the local call instance is the
- * initiator of the call; false, otherwise.
- * @param remoteTransport the TRANSPORT element
- * @param mediaType The type of media (AUDIO or VIDEO).
- */
- boolean addDtlsAdvertisedEncryptions(
- boolean isInitiator,
- IceUdpTransportPacketExtension remoteTransport,
- MediaType mediaType)
- {
- SrtpControls srtpControls = getSrtpControls();
- boolean b = false;
-
- if (remoteTransport != null)
- {
- List remoteFingerpintPEs
- = remoteTransport.getChildExtensionsOfType(
- DtlsFingerprintPacketExtension.class);
-
- if (!remoteFingerpintPEs.isEmpty())
- {
- AccountID accountID
- = getPeer().getProtocolProvider().getAccountID();
-
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- DtlsControl.PROTO_NAME))
- {
- Map remoteFingerprints
- = new LinkedHashMap();
-
- for (DtlsFingerprintPacketExtension remoteFingerprintPE
- : remoteFingerpintPEs)
- {
- String remoteFingerprint
- = remoteFingerprintPE.getFingerprint();
- String remoteHash = remoteFingerprintPE.getHash();
-
- remoteFingerprints.put(
- remoteHash,
- remoteFingerprint);
- }
-
- DtlsControl dtlsControl;
- DtlsControl.Setup setup;
-
- if (isInitiator)
- {
- dtlsControl
- = (DtlsControl)
- srtpControls.get(
- mediaType,
- SrtpControlType.DTLS_SRTP);
- setup = DtlsControl.Setup.PASSIVE;
- }
- else
- {
- dtlsControl
- = (DtlsControl)
- srtpControls.getOrCreate(
- mediaType,
- SrtpControlType.DTLS_SRTP);
- setup = DtlsControl.Setup.ACTIVE;
- }
- if (dtlsControl != null)
- {
- dtlsControl.setRemoteFingerprints(remoteFingerprints);
- dtlsControl.setSetup(setup);
- removeAndCleanupOtherSrtpControls(
- mediaType,
- SrtpControlType.DTLS_SRTP);
- addAdvertisedEncryptionMethod(
- SrtpControlType.DTLS_SRTP);
- b = true;
- }
- }
- }
- }
- /*
- * If they haven't advertised DTLS-SRTP in their (media) description,
- * then DTLS-SRTP shouldn't be functioning as far as we're concerned.
- */
- if (!b)
- {
- SrtpControl dtlsControl
- = srtpControls.get(mediaType, SrtpControlType.DTLS_SRTP);
-
- if (dtlsControl != null)
- {
- srtpControls.remove(mediaType, SrtpControlType.DTLS_SRTP);
- dtlsControl.cleanup();
- }
- }
- return b;
- }
-
- /**
- * Selects the preferred encryption protocol (only used by the callee).
- *
- * @param mediaType The type of media (AUDIO or VIDEO).
- * @param localContent The element containing the media DESCRIPTION and
- * its encryption.
- * @param remoteContent The element containing the media DESCRIPTION and
- * its encryption for the remote peer; null if the local peer is
- * the initiator of the call.
- */
- private void setAndAddPreferredEncryptionProtocol(
- MediaType mediaType,
- ContentPacketExtension localContent,
- ContentPacketExtension remoteContent)
- {
+/*
+ * 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.awt.*;
+import java.beans.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.List;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+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.media.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.libjitsi.*;
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.device.*;
+import org.jitsi.service.neomedia.format.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * An XMPP specific extension of the generic media handler.
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Hristo Terezov
+ * @author Boris Grozev
+ */
+public class CallPeerMediaHandlerJabberImpl
+ extends AbstractCallPeerMediaHandlerJabberGTalkImpl
+{
+ /**
+ * The Logger used by the CallPeerMediaHandlerJabberImpl
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(CallPeerMediaHandlerJabberImpl.class);
+
+ /**
+ * Determines whether a specific XMPP feature is supported by both a
+ * specific ScServiceDiscoveryManager (may be referred to as the
+ * local peer) and a specific DiscoverInfo (may be thought of as
+ * the remote peer).
+ *
+ * @param discoveryManager the ScServiceDiscoveryManager to be
+ * checked whether it includes the specified feature
+ * @param discoverInfo the DiscoveryInfo which is to be checked
+ * whether it contains the specified feature. If discoverInfo is
+ * null, it is considered to contain the specified feature.
+ * @param feature the feature to be determined whether it is supported by
+ * both the specified discoveryManager and the specified
+ * discoverInfo
+ * @return true if the specified feature is supported by
+ * both the specified discoveryManager and the specified
+ * discoverInfo; otherwise, false
+ */
+ private static boolean isFeatureSupported(
+ ScServiceDiscoveryManager discoveryManager,
+ DiscoverInfo discoverInfo,
+ String feature)
+ {
+ return
+ discoveryManager.includesFeature(feature)
+ && ((discoverInfo == null)
+ || discoverInfo.containsFeature(feature));
+ }
+
+ /**
+ * The current description of the streams that we have going toward the
+ * remote side. We use {@link LinkedHashMap}s to make sure that we preserve
+ * the order of the individual content extensions.
+ */
+ private final Map localContentMap
+ = new LinkedHashMap();
+
+ /**
+ * The QualityControl of this CallPeerMediaHandler.
+ */
+ private final QualityControlWrapper qualityControls;
+
+ /**
+ * The current description of the streams that the remote side has with us.
+ * We use {@link LinkedHashMap}s to make sure that we preserve
+ * the order of the individual content extensions.
+ */
+ private final Map remoteContentMap
+ = new LinkedHashMap();
+
+ /**
+ * Indicates whether the remote party has placed us on hold.
+ */
+ private boolean remotelyOnHold = false;
+
+ /**
+ * Whether other party is able to change video quality settings.
+ * Normally its whether we have detected existence of imageattr in sdp.
+ */
+ private boolean supportQualityControls = false;
+
+ /**
+ * The TransportManager implementation handling our address
+ * management.
+ */
+ private TransportManagerJabberImpl transportManager;
+
+ /**
+ * The Object which is used for synchronization (e.g. wait
+ * and notify) related to {@link #transportManager}.
+ */
+ private final Object transportManagerSyncRoot = new Object();
+
+ /**
+ * The ordered by preference array of the XML namespaces of the jingle
+ * transports that this peer supports. If it is non-null, it will be used
+ * instead of checking disco#info in order to select an appropriate
+ * transport manager.
+ */
+ private String[] supportedTransports = null;
+
+ /**
+ * Object used to synchronize access to supportedTransports
+ */
+ private final Object supportedTransportsSyncRoot = new Object();
+
+ /**
+ * Creates a new handler that will be managing media streams for
+ * peer.
+ *
+ * @param peer that CallPeerJabberImpl instance that we will be
+ * managing media for.
+ */
+ public CallPeerMediaHandlerJabberImpl(CallPeerJabberImpl peer)
+ {
+ super(peer);
+
+ qualityControls = new QualityControlWrapper(peer);
+ }
+
+ /**
+ * Determines the direction that a stream, which has been placed on
+ * hold by the remote party, would need to go back to after being
+ * re-activated. If the stream is not currently on hold (i.e. it is still
+ * sending media), this method simply returns its current direction.
+ *
+ * @param stream the {@link MediaStreamTarget} whose post-hold direction
+ * we'd like to determine.
+ *
+ * @return the {@link MediaDirection} that we need to set on stream
+ * once it is reactivate.
+ */
+ private MediaDirection calculatePostHoldDirection(MediaStream stream)
+ {
+ MediaDirection streamDirection = stream.getDirection();
+
+ if (streamDirection.allowsSending())
+ return streamDirection;
+
+ /*
+ * When calculating a direction we need to take into account 1) what
+ * direction the remote party had asked for before putting us on hold,
+ * 2) what the user preference is for the stream's media type, 3) our
+ * local hold status, 4) the direction supported by the device this
+ * stream is reading from.
+ */
+
+ // 1. what the remote party originally told us (from our perspective)
+ ContentPacketExtension content = remoteContentMap.get(stream.getName());
+ MediaDirection postHoldDir
+ = JingleUtils.getDirection(content, !getPeer().isInitiator());
+
+ // 2. the user preference
+ MediaDevice device = stream.getDevice();
+
+ postHoldDir
+ = postHoldDir.and(
+ getDirectionUserPreference(device.getMediaType()));
+
+ // 3. our local hold status
+ if (isLocallyOnHold())
+ postHoldDir = postHoldDir.and(MediaDirection.SENDONLY);
+
+ // 4. the device direction
+ postHoldDir = postHoldDir.and(device.getDirection());
+
+ return postHoldDir;
+ }
+
+ /**
+ * Closes the CallPeerMediaHandler.
+ */
+ @Override
+ public synchronized void close()
+ {
+ super.close();
+
+ OperationSetDesktopSharingClientJabberImpl client
+ = (OperationSetDesktopSharingClientJabberImpl)
+ getPeer().getProtocolProvider().getOperationSet(
+ OperationSetDesktopSharingClient.class);
+
+ if (client != null)
+ client.fireRemoteControlRevoked(getPeer());
+ }
+
+ /**
+ * Creates a {@link ContentPacketExtension}s of the streams for a
+ * specific MediaDevice.
+ *
+ * @param dev MediaDevice
+ * @return the {@link ContentPacketExtension}s of stream that this
+ * handler is prepared to initiate.
+ * @throws OperationFailedException if we fail to create the descriptions
+ * for reasons like problems with device interaction, allocating ports, etc.
+ */
+ private ContentPacketExtension createContent(MediaDevice dev)
+ throws OperationFailedException
+ {
+ MediaType mediaType = dev.getMediaType();
+ //this is the direction to be used in the jingle session
+ MediaDirection direction = dev.getDirection();
+ CallPeerJabberImpl peer = getPeer();
+
+ /*
+ * In the case of RTP translation performed by the conference focus,
+ * the conference focus is not required to capture media.
+ */
+ if (!(MediaType.VIDEO.equals(mediaType)
+ && isRTPTranslationEnabled(mediaType)))
+ direction = direction.and(getDirectionUserPreference(mediaType));
+
+ /*
+ * Check if we need to announce sending on behalf of other peers
+ */
+ CallJabberImpl call = peer.getCall();
+
+ if (call.isConferenceFocus())
+ {
+ for (CallPeerJabberImpl anotherPeer : call.getCallPeerList())
+ {
+ if ((anotherPeer != peer)
+ && anotherPeer.getDirection(mediaType)
+ .allowsReceiving())
+ {
+ direction = direction.or(MediaDirection.SENDONLY);
+ break;
+ }
+ }
+ }
+
+ if (isLocallyOnHold())
+ direction = direction.and(MediaDirection.SENDONLY);
+
+ QualityPreset sendQualityPreset = null;
+ QualityPreset receiveQualityPreset = null;
+
+ if(qualityControls != null)
+ {
+ // the one we will send is the one the remote has announced as
+ // receive
+ sendQualityPreset = qualityControls.getRemoteReceivePreset();
+ // the one we want to receive is the one the remote can send
+ receiveQualityPreset = qualityControls.getRemoteSendMaxPreset();
+ }
+ if(direction != MediaDirection.INACTIVE)
+ {
+ ContentPacketExtension content
+ = createContentForOffer(
+ getLocallySupportedFormats(
+ dev,
+ sendQualityPreset,
+ receiveQualityPreset),
+ direction,
+ dev.getSupportedExtensions());
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+
+ // DTLS-SRTP
+ setDtlsEncryptionOnContent(mediaType, content, null);
+ /*
+ * Neither SDES nor ZRTP is supported in telephony conferences
+ * utilizing the server-side technology Jitsi Videobridge yet.
+ */
+ if (!call.getConference().isJitsiVideobridge())
+ {
+ // SDES
+ // It is important to set SDES before ZRTP in order to make
+ // GTALK application able to work with SDES.
+ setSDesEncryptionOnDescription(mediaType, description, null);
+ // ZRTP
+ setZrtpEncryptionOnDescription(mediaType, description, null);
+ }
+
+ return content;
+ }
+ return null;
+ }
+
+ /**
+ * Creates a {@link ContentPacketExtension} for a particular stream.
+ *
+ * @param mediaType MediaType of the content
+ * @return a {@link ContentPacketExtension}
+ * @throws OperationFailedException if we fail to create the descriptions
+ * for reasons like - problems with device interaction, allocating ports,
+ * etc.
+ */
+ public ContentPacketExtension createContentForMedia(MediaType mediaType)
+ throws OperationFailedException
+ {
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ if(isDeviceActive(dev))
+ return createContent(dev);
+ return null;
+ }
+
+ /**
+ * Generates an Jingle {@link ContentPacketExtension} for the specified
+ * {@link MediaFormat} list, direction and RTP extensions taking account
+ * the local streaming preference for the corresponding media type.
+ *
+ * @param supportedFormats the list of MediaFormats that we'd
+ * like to advertise.
+ * @param direction the MediaDirection that we'd like to establish
+ * the stream in.
+ * @param supportedExtensions the list of RTPExtensions that we'd
+ * like to advertise in the MediaDescription.
+ *
+ * @return a newly created {@link ContentPacketExtension} representing
+ * streams that we'd be able to handle.
+ */
+ private ContentPacketExtension createContentForOffer(
+ List supportedFormats,
+ MediaDirection direction,
+ List supportedExtensions)
+ {
+ ContentPacketExtension content
+ = JingleUtils.createDescription(
+ ContentPacketExtension.CreatorEnum.initiator,
+ supportedFormats.get(0).getMediaType().toString(),
+ JingleUtils.getSenders(direction, !getPeer().isInitiator()),
+ supportedFormats,
+ supportedExtensions,
+ getDynamicPayloadTypes(),
+ getRtpExtensionsRegistry());
+
+ this.localContentMap.put(content.getName(), content);
+ return content;
+ }
+
+ /**
+ * Creates a List containing the {@link ContentPacketExtension}s of
+ * the streams that this handler is prepared to initiate depending on
+ * available MediaDevices and local on-hold and video transmission
+ * preferences.
+ *
+ * @return a {@link List} containing the {@link ContentPacketExtension}s of
+ * streams that this handler is prepared to initiate.
+ *
+ * @throws OperationFailedException if we fail to create the descriptions
+ * for reasons like problems with device interaction, allocating ports, etc.
+ */
+ public List createContentList()
+ throws OperationFailedException
+ {
+ // Describe the media.
+ List mediaDescs
+ = new ArrayList();
+ boolean jitsiVideobridge
+ = getPeer().getCall().getConference().isJitsiVideobridge();
+
+ for (MediaType mediaType : MediaType.values())
+ {
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ if (isDeviceActive(dev))
+ {
+ MediaDirection direction = dev.getDirection();
+
+ /*
+ * In the case of RTP translation performed by the conference
+ * focus, the conference focus is not required to capture media.
+ */
+ if (!(MediaType.VIDEO.equals(mediaType)
+ && isRTPTranslationEnabled(mediaType)))
+ {
+ direction
+ = direction.and(getDirectionUserPreference(mediaType));
+ }
+ if (isLocallyOnHold())
+ direction = direction.and(MediaDirection.SENDONLY);
+
+ /*
+ * If we're only able to receive, we don't have to offer it at
+ * all. For example, we have to offer audio and no video when we
+ * start an audio call.
+ */
+ if (MediaDirection.RECVONLY.equals(direction))
+ direction = MediaDirection.INACTIVE;
+
+ if (direction != MediaDirection.INACTIVE)
+ {
+ ContentPacketExtension content
+ = createContentForOffer(
+ getLocallySupportedFormats(dev),
+ direction,
+ dev.getSupportedExtensions());
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+
+ // DTLS-SRTP
+ setDtlsEncryptionOnContent(mediaType, content, null);
+ /*
+ * Neither SDES nor ZRTP is supported in telephony
+ * conferences utilizing the server-side technology Jitsi
+ * Videobridge yet.
+ */
+ if (!jitsiVideobridge)
+ {
+ // SDES
+ // It is important to set SDES before ZRTP in order to
+ // make GTALK application able to work with SDES.
+ setSDesEncryptionOnDescription(
+ mediaType,
+ description,
+ null);
+ //ZRTP
+ setZrtpEncryptionOnDescription(
+ mediaType,
+ description,
+ null);
+ }
+
+ // we request a desktop sharing session so add the inputevt
+ // extension in the "video" content
+ if (description.getMedia().equals(
+ MediaType.VIDEO.toString())
+ && getLocalInputEvtAware())
+ {
+ content.addChildExtension(
+ new InputEvtPacketExtension());
+ }
+
+ mediaDescs.add(content);
+ }
+ }
+ }
+
+ // Fail if no media is described (e.g. all devices are inactive).
+ if (mediaDescs.isEmpty())
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "We couldn't find any active Audio/Video devices"
+ + " and couldn't create a call",
+ OperationFailedException.GENERAL_ERROR,
+ null,
+ logger);
+ }
+
+ // Describe the transport(s).
+ return harvestCandidates(null, mediaDescs, null);
+ }
+
+ /**
+ * Creates a List containing the {@link ContentPacketExtension}s of
+ * the streams of a specific MediaType that this handler is
+ * prepared to initiate depending on available MediaDevices and
+ * local on-hold and video transmission preferences.
+ *
+ * @param mediaType MediaType of the content
+ * @return a {@link List} containing the {@link ContentPacketExtension}s of
+ * streams that this handler is prepared to initiate.
+ *
+ * @throws OperationFailedException if we fail to create the descriptions
+ * for reasons like - problems with device interaction, allocating ports,
+ * etc.
+ */
+ public List createContentList(MediaType mediaType)
+ throws OperationFailedException
+ {
+ MediaDevice dev = getDefaultDevice(mediaType);
+ List mediaDescs
+ = new ArrayList();
+
+ if (isDeviceActive(dev))
+ {
+ ContentPacketExtension content = createContent(dev);
+
+ if (content != null)
+ mediaDescs.add(content);
+ }
+
+ // Fail if no media is described (e.g. all devices are inactive).
+ if (mediaDescs.isEmpty())
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "We couldn't find any active Audio/Video devices and "
+ + "couldn't create a call",
+ OperationFailedException.GENERAL_ERROR,
+ null,
+ logger);
+ }
+
+ // Describe the transport(s).
+ return harvestCandidates(null, mediaDescs, null);
+ }
+
+ /**
+ * Overrides to give access to the transport manager to send events
+ * about ICE state changes.
+ *
+ * @param property the name of the property of this
+ * PropertyChangeNotifier which had its value changed
+ * @param oldValue the value of the property with the specified name before
+ * the change
+ * @param newValue the value of the property with the specified name after
+ */
+ @Override
+ protected void firePropertyChange(
+ String property,
+ Object oldValue, Object newValue)
+ {
+ super.firePropertyChange(property, oldValue, newValue);
+ }
+
+ /**
+ * Wraps up any ongoing candidate harvests and returns our response to the
+ * last offer we've received, so that the peer could use it to send a
+ * session-accept.
+ *
+ * @return the last generated list of {@link ContentPacketExtension}s that
+ * the call peer could use to send a session-accept.
+ *
+ * @throws OperationFailedException if we fail to configure the media stream
+ */
+ public Iterable generateSessionAccept()
+ throws OperationFailedException
+ {
+ TransportManagerJabberImpl transportManager = getTransportManager();
+ Iterable sessAccept
+ = transportManager.wrapupCandidateHarvest();
+ CallPeerJabberImpl peer = getPeer();
+
+ //user answered an incoming call so we go through whatever content
+ //entries we are initializing and init their corresponding streams
+
+ // First parse content so we know how many streams and what type of
+ // content we have
+ Map contents
+ = new HashMap();
+
+ for(ContentPacketExtension ourContent : sessAccept)
+ {
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(ourContent);
+
+ contents.put(ourContent, description);
+ }
+
+ boolean masterStreamSet = false;
+
+ for(Map.Entry en
+ : contents.entrySet())
+ {
+ ContentPacketExtension ourContent = en.getKey();
+
+ RtpDescriptionPacketExtension description = en.getValue();
+ MediaType type = MediaType.parseString(description.getMedia());
+
+ // stream connector
+ StreamConnector connector
+ = transportManager.getStreamConnector(type);
+
+ //the device this stream would be reading from and writing to.
+ MediaDevice dev = getDefaultDevice(type);
+
+ if(!isDeviceActive(dev))
+ continue;
+
+ // stream target
+ MediaStreamTarget target = transportManager.getStreamTarget(type);
+
+ //stream direction
+ MediaDirection direction
+ = JingleUtils.getDirection(ourContent, !peer.isInitiator());
+
+ // if we answer with video, tell remote peer that video direction is
+ // sendrecv, and whether video device can capture/send
+ if (MediaType.VIDEO.equals(type)
+ && (isLocalVideoTransmissionEnabled()
+ || isRTPTranslationEnabled(type))
+ && dev.getDirection().allowsSending())
+ {
+ direction = MediaDirection.SENDRECV;
+ ourContent.setSenders(ContentPacketExtension.SendersEnum.both);
+ }
+
+ //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(contentName);
+ RtpDescriptionPacketExtension theirDescription
+ = JingleUtils.getRtpDescription(theirContent);
+ MediaFormat format = null;
+
+ List localFormats = getLocallySupportedFormats(dev);
+ for(PayloadTypePacketExtension payload
+ : theirDescription.getPayloadTypes())
+ {
+ MediaFormat remoteFormat = JingleUtils.payloadTypeToMediaFormat(
+ payload, getDynamicPayloadTypes());
+ if(remoteFormat != null
+ && (format = findMediaFormat(localFormats, remoteFormat))
+ != null)
+ break;
+ }
+
+ if(format == null)
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "No matching codec.",
+ OperationFailedException.ILLEGAL_ARGUMENT,
+ null,
+ logger);
+ }
+
+ //extract the extensions that we are advertising:
+ // check whether we will be exchanging any RTP extensions.
+ List rtpExtensions
+ = JingleUtils.extractRTPExtensions(
+ description,
+ this.getRtpExtensionsRegistry());
+
+ Map adv = format.getAdvancedAttributes();
+ if(adv != null)
+ {
+ for(Map.Entry f : adv.entrySet())
+ {
+ if(f.getKey().equals("imageattr"))
+ supportQualityControls = true;
+ }
+ }
+
+ boolean masterStream = false;
+ // if we have more than one stream, lets the audio be the master
+ if(!masterStreamSet)
+ {
+ if(contents.size() > 1)
+ {
+ if(type.equals(MediaType.AUDIO))
+ {
+ masterStream = true;
+ masterStreamSet = true;
+ }
+ }
+ else
+ {
+ masterStream = true;
+ masterStreamSet = true;
+ }
+ }
+
+ // create the corresponding stream...
+ MediaStream stream = initStream(contentName,
+ connector,
+ dev,
+ format,
+ target,
+ direction,
+ rtpExtensions,
+ masterStream);
+
+ long ourSsrc = stream.getLocalSourceID() & 0xffffffffL;
+ if (direction.allowsSending() && ourSsrc != -1)
+ {
+ description.setSsrc(Long.toString(ourSsrc));
+ addSourceExtension(description, ourSsrc);
+ }
+ }
+ return sessAccept;
+ }
+
+ /**
+ * Adds a SourcePacketExtension as a child element of
+ * description. See XEP-0339.
+ *
+ * @param description the RtpDescriptionPacketExtension to which
+ * a child element will be added.
+ * @param ssrc the SSRC for the SourcePacketExtension to use.
+ */
+ private void addSourceExtension(RtpDescriptionPacketExtension description,
+ long ssrc)
+ {
+ MediaType type = MediaType.parseString(description.getMedia());
+
+ SourcePacketExtension sourcePacketExtension
+ = new SourcePacketExtension();
+
+ sourcePacketExtension.setSSRC(ssrc);
+ sourcePacketExtension.addChildExtension(
+ new ParameterPacketExtension("cname",
+ LibJitsi.getMediaService()
+ .getRtpCname()));
+ sourcePacketExtension.addChildExtension(
+ new ParameterPacketExtension("msid", getMsid(type)));
+ sourcePacketExtension.addChildExtension(
+ new ParameterPacketExtension("mslabel", getMsLabel()));
+ sourcePacketExtension.addChildExtension(
+ new ParameterPacketExtension("label", getLabel(type)));
+
+ description.addChildExtension(sourcePacketExtension);
+ }
+
+ /**
+ * Returns the local content of a specific content type (like audio or
+ * video).
+ *
+ * @param contentType content type name
+ * @return remote ContentPacketExtension or null if not found
+ */
+ public ContentPacketExtension getLocalContent(String contentType)
+ {
+ for(String key : localContentMap.keySet())
+ {
+ ContentPacketExtension content = localContentMap.get(key);
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+
+ if(description.getMedia().equals(contentType))
+ return content;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a complete list of call currently known local content-s.
+ *
+ * @return a list of {@link ContentPacketExtension} null if not found
+ */
+ public Iterable getLocalContentList()
+ {
+ return localContentMap.values();
+ }
+
+ /**
+ * Returns the quality control for video calls if any.
+ *
+ * @return the implemented quality control.
+ */
+ public QualityControl getQualityControl()
+ {
+ if(supportQualityControls)
+ {
+ return qualityControls;
+ }
+ else
+ {
+ // we have detected that its not supported and return null
+ // and control ui won't be visible
+ return null;
+ }
+ }
+
+ /**
+ * Get the remote content of a specific content type (like audio or video).
+ *
+ * @param contentType content type name
+ * @return remote ContentPacketExtension or null if not found
+ */
+ public ContentPacketExtension getRemoteContent(String contentType)
+ {
+ for(String key : remoteContentMap.keySet())
+ {
+ ContentPacketExtension content = remoteContentMap.get(key);
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+
+ if(description.getMedia().equals(contentType))
+ return content;
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * In the case of a telephony conference organized by the local peer/user
+ * via the Jitsi Videobridge server-side technology, returns an SSRC
+ * reported by the server as received on the channel allocated by the local
+ * peer/user for the purposes of communicating with the CallPeer
+ * associated with this instance.
+ */
+ @Override
+ public long getRemoteSSRC(MediaType mediaType)
+ {
+ int[] ssrcs = getRemoteSSRCs(mediaType);
+
+ /*
+ * A peer (regardless of whether it is local or remote) may send
+ * multiple RTP streams at any time. In such a case, it is not clear
+ * which one of their SSRCs is to be returned. Anyway, the super says
+ * that the returned is the last known. We will presume that the last
+ * known in the list reported by the Jitsi Videobridge server is the
+ * last.
+ */
+ if (ssrcs.length != 0)
+ return 0xFFFFFFFFL & ssrcs[ssrcs.length - 1];
+
+ /*
+ * XXX In the case of Jitsi Videobridge, the super implementation of
+ * getRemoteSSRC(MediaType) cannot be trusted because there is a single
+ * VideoMediaStream with multiple ReceiveStreams.
+ */
+ return
+ getPeer().isJitsiVideobridge()
+ ? SSRC_UNKNOWN
+ : super.getRemoteSSRC(mediaType);
+ }
+
+ /**
+ * Gets the SSRCs of RTP streams with a specific MediaType known to
+ * be received by a MediaStream associated with this instance.
+ *
+ * Warning: The method may return only one of the many possible
+ * remote SSRCs in the case of no utilization of the Jitsi Videobridge
+ * server-side technology because the super implementation does not
+ * currently provide support for keeping track of multiple remote SSRCs.
+ *
+ *
+ * @param mediaType the MediaType of the RTP streams the SSRCs of
+ * which are to be returned
+ * @return an array of int values which represent the SSRCs of RTP
+ * streams with the specified mediaType known to be received by a
+ * MediaStream associated with this instance
+ */
+ private int[] getRemoteSSRCs(MediaType mediaType)
+ {
+ /*
+ * If the Jitsi Videobridge server-side technology is utilized, a single
+ * MediaStream (per MediaType) is shared among the participating
+ * CallPeers and, consequently, the remote SSRCs cannot be associated
+ * with the CallPeers from which they are actually being sent. That's
+ * why the server will report them to the conference focus.
+ */
+ ColibriConferenceIQ.Channel channel = getColibriChannel(mediaType);
+
+ if (channel != null)
+ return channel.getSSRCs();
+
+ /*
+ * XXX The fallback to the super implementation that follows may lead to
+ * unexpected behavior due to the lack of ability to keep track of
+ * multiple remote SSRCs.
+ */
+ long ssrc = super.getRemoteSSRC(mediaType);
+
+ return
+ (ssrc == SSRC_UNKNOWN)
+ ? ColibriConferenceIQ.NO_SSRCS
+ : new int[] { (int) ssrc };
+ }
+
+ /**
+ * Gets the TransportManager implementation handling our address
+ * management.
+ *
+ * TODO: this method can and should be simplified.
+ *
+ * @return the TransportManager implementation handling our address
+ * management
+ * @see CallPeerMediaHandler#getTransportManager()
+ */
+ @Override
+ protected synchronized TransportManagerJabberImpl getTransportManager()
+ {
+ if (transportManager == null)
+ {
+ CallPeerJabberImpl peer = getPeer();
+
+ if (peer.isInitiator())
+ {
+ synchronized(transportManagerSyncRoot)
+ {
+ try
+ {
+ transportManagerSyncRoot.wait(5000);
+ }
+ catch(InterruptedException e)
+ {
+ }
+ }
+ if(transportManager == null)
+ {
+ throw new IllegalStateException(
+ "The initiator is expected to specify the transport"
+ + " in their offer.");
+ }
+ else
+ return transportManager;
+ }
+ else
+ {
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = peer.getProtocolProvider();
+ ScServiceDiscoveryManager discoveryManager
+ = protocolProvider.getDiscoveryManager();
+ DiscoverInfo peerDiscoverInfo = peer.getDiscoveryInfo();
+
+ /*
+ * If this.supportedTransports has been explicitly set, we use
+ * it to select the transport manager -- we use the first
+ * transport in the list which we recognize (e.g. the first
+ * that is either ice or raw-udp
+ */
+ synchronized (supportedTransportsSyncRoot)
+ {
+ if (supportedTransports != null
+ && supportedTransports.length > 0)
+ {
+ for (int i = 0; i < supportedTransports.length; i++)
+ {
+ if (ProtocolProviderServiceJabberImpl.
+ URN_XMPP_JINGLE_ICE_UDP_1.
+ equals(supportedTransports[i]))
+ {
+ transportManager
+ = new IceUdpTransportManager(peer);
+ break;
+ }
+ else if (ProtocolProviderServiceJabberImpl.
+ URN_XMPP_JINGLE_RAW_UDP_0.
+ equals(supportedTransports[i]))
+ {
+ transportManager
+ = new RawUdpTransportManager(peer);
+ break;
+ }
+ }
+ if (transportManager == null)
+ {
+ logger.warn(
+ "Could not find a supported"
+ + " TransportManager in"
+ + " supportedTransports. Will try to"
+ + " select one based on disco#info.");
+ }
+ }
+ }
+
+ if (transportManager == null)
+ {
+ /*
+ * The list of possible transports ordered by decreasing
+ * preference.
+ */
+ String[] transports
+ = new String[]
+ {
+ ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_ICE_UDP_1,
+ ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_RAW_UDP_0
+ };
+
+ /*
+ * If Jitsi Videobridge is to be employed, pick up a Jingle
+ * transport supported by it.
+ */
+ if (peer.isJitsiVideobridge())
+ {
+ CallJabberImpl call = peer.getCall();
+
+ if (call != null)
+ {
+ String jitsiVideobridge
+ = peer.getCall().getJitsiVideobridge();
+
+ /*
+ * Jitsi Videobridge supports the Jingle Raw UDP
+ * transport from its inception. But that is not the
+ * case with the Jingle ICE-UDP transport.
+ */
+ if ((jitsiVideobridge != null)
+ && !protocolProvider.isFeatureSupported(
+ jitsiVideobridge,
+ ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_ICE_UDP_1))
+ {
+ for (int i = transports.length - 1; i >= 0; i--)
+ {
+ if (ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_ICE_UDP_1
+ .equals(transports[i]))
+ {
+ transports[i] = null;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Select the first transport from the list of possible
+ * transports ordered by decreasing preference which is
+ * supported by the local and the remote peers.
+ */
+ for (String transport : transports)
+ {
+ if (transport == null)
+ continue;
+ if (isFeatureSupported(
+ discoveryManager,
+ peerDiscoverInfo,
+ transport))
+ {
+ if (ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_ICE_UDP_1
+ .equals(transport))
+ {
+ transportManager
+ = new IceUdpTransportManager(peer);
+ }
+ else if (ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_RAW_UDP_0
+ .equals(transport))
+ {
+ transportManager
+ = new RawUdpTransportManager(peer);
+ }
+
+ if (transportManager != null)
+ break;
+ }
+ }
+
+ if ((transportManager == null) && logger.isDebugEnabled())
+ {
+ logger.debug(
+ "No known Jingle transport supported by Jabber"
+ + " call peer " + peer);
+ }
+ }
+ }
+ }
+ return transportManager;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see CallPeerMediaHandler#queryTransportManager()
+ */
+ @Override
+ protected synchronized TransportManagerJabberImpl queryTransportManager()
+ {
+ return transportManager;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * In the case of utilization of the Jitsi Videobridge server-side
+ * technology, returns the visual Components which display RTP
+ * video streams reported by the server to be sent by the remote peer
+ * represented by this instance.
+ */
+ @Override
+ public List getVisualComponents()
+ {
+ /*
+ * TODO The super is currently unable to provide the complete set of
+ * remote SSRCs (i.e. in the case of no utilization of the Jitsi
+ * Videobridge server-side technology) so we have to explicitly check
+ * for Jitsi Videobridge instead of just relying on the implementation
+ * of the getRemoteSSRCs(MediaType) method to abstract away that detail.
+ */
+ CallJabberImpl call;
+ MediaAwareCallConference conference;
+
+ if (((call = getPeer().getCall()) != null)
+ && ((conference = call.getConference()) != null)
+ && conference.isJitsiVideobridge())
+ {
+ MediaStream stream = getStream(MediaType.VIDEO);
+
+ if (stream == null)
+ return Collections.emptyList();
+ else
+ {
+ int[] remoteSSRCs = getRemoteSSRCs(MediaType.VIDEO);
+
+ if (remoteSSRCs.length == 0)
+ return Collections.emptyList();
+ else
+ {
+ VideoMediaStream videoStream = (VideoMediaStream) stream;
+ List visualComponents
+ = new LinkedList();
+
+ for (int i = 0; i < remoteSSRCs.length; i++)
+ {
+ int remoteSSRC = remoteSSRCs[i];
+ Component visualComponent
+ = videoStream.getVisualComponent(
+ 0xFFFFFFFFL & remoteSSRC);
+
+ if (visualComponent != null)
+ visualComponents.add(visualComponent);
+ }
+ return visualComponents;
+ }
+ }
+ }
+
+ return super.getVisualComponents();
+ }
+
+ /**
+ * Gathers local candidate addresses.
+ *
+ * @param remote the media descriptions received from the remote peer if any
+ * or null if local represents an offer from the local
+ * peer to be sent to the remote peer
+ * @param local the media descriptions sent or to be sent from the local
+ * peer to the remote peer. If remote is null,
+ * local represents an offer from the local peer to be sent to the
+ * remote peer
+ * @param transportInfoSender the TransportInfoSender to be used by
+ * this TransportManagerJabberImpl to send transport-info
+ * JingleIQs from the local peer to the remote peer if this
+ * TransportManagerJabberImpl wishes to utilize
+ * transport-info
+ * @return the media descriptions of the local peer after the local
+ * candidate addresses have been gathered as returned by
+ * {@link TransportManagerJabberImpl#wrapupCandidateHarvest()}
+ * @throws OperationFailedException if anything goes wrong while starting or
+ * wrapping up the gathering of local candidate addresses
+ */
+ private List harvestCandidates(
+ List remote,
+ List local,
+ TransportInfoSender transportInfoSender)
+ throws OperationFailedException
+ {
+ long startCandidateHarvestTime = System.currentTimeMillis();
+ TransportManagerJabberImpl transportManager = getTransportManager();
+
+ if (remote == null)
+ {
+ /*
+ * We'll be harvesting candidates in order to make an offer so it
+ * doesn't make sense to send them in transport-info.
+ */
+ if (transportInfoSender != null)
+ throw new IllegalArgumentException("transportInfoSender");
+
+ transportManager.startCandidateHarvest(local, transportInfoSender);
+ }
+ else
+ {
+ transportManager.startCandidateHarvest(
+ remote,
+ local,
+ transportInfoSender);
+ }
+
+ long stopCandidateHarvestTime = System.currentTimeMillis();
+
+ if (logger.isInfoEnabled())
+ {
+ long candidateHarvestTime
+ = stopCandidateHarvestTime - startCandidateHarvestTime;
+
+ logger.info(
+ "End candidate harvest within " + candidateHarvestTime
+ + " ms");
+ }
+
+ setDtlsEncryptionOnTransports(remote, local);
+
+ if (transportManager.startConnectivityEstablishmentWithJitsiVideobridge)
+ {
+ Map map
+ = new LinkedHashMap();
+
+ for (MediaType mediaType : MediaType.values())
+ {
+ ColibriConferenceIQ.Channel channel
+ = transportManager.getColibriChannel(
+ mediaType,
+ true /* local */);
+
+ if (channel != null)
+ {
+ IceUdpTransportPacketExtension transport
+ = channel.getTransport();
+
+ if (transport != null)
+ map.put(mediaType.toString(), transport);
+ }
+ }
+ if (!map.isEmpty())
+ {
+ transportManager
+ .startConnectivityEstablishmentWithJitsiVideobridge
+ = false;
+ transportManager.startConnectivityEstablishment(map);
+ }
+ }
+
+ /*
+ * TODO Ideally, we wouldn't wrap up that quickly. We need to revisit
+ * this.
+ */
+ return transportManager.wrapupCandidateHarvest();
+ }
+
+ /**
+ * Creates if necessary, and configures the stream that this
+ * MediaHandler is using for the MediaType matching the
+ * one of the MediaDevice. This method extends the one already
+ * available by adding a stream name, corresponding to a stream's content
+ * name.
+ *
+ * @param streamName the name of the stream as indicated in the XMPP
+ * content element.
+ * @param connector the MediaConnector that we'd like to bind the
+ * newly created stream to.
+ * @param device the MediaDevice that we'd like to attach the newly
+ * created MediaStream to.
+ * @param format the MediaFormat that we'd like the new
+ * MediaStream to be set to transmit in.
+ * @param target the MediaStreamTarget containing the RTP and RTCP
+ * address:port couples that the new stream would be sending packets to.
+ * @param direction the MediaDirection that we'd like the new
+ * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive).
+ * @param rtpExtensions the list of RTPExtensions that should be
+ * enabled for this stream.
+ * @param masterStream whether the stream to be used as master if secured
+ *
+ * @return the newly created MediaStream.
+ *
+ * @throws OperationFailedException if creating the stream fails for any
+ * reason (like for example accessing the device or setting the format).
+ */
+ protected MediaStream initStream(String streamName,
+ StreamConnector connector,
+ MediaDevice device,
+ MediaFormat format,
+ MediaStreamTarget target,
+ MediaDirection direction,
+ List rtpExtensions,
+ boolean masterStream)
+ throws OperationFailedException
+ {
+ MediaStream stream
+ = super.initStream(
+ connector,
+ device,
+ format,
+ target,
+ direction,
+ rtpExtensions,
+ masterStream);
+
+ if (stream != null)
+ stream.setName(streamName);
+
+ return stream;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * In the case of a telephony conference organized by the local peer/user
+ * and utilizing the Jitsi Videobridge server-side technology, a single
+ * MediaHandler is shared by multiple
+ * CallPeerMediaHandlers in order to have a single
+ * AudioMediaStream and a single VideoMediaStream.
+ * However, CallPeerMediaHandlerJabberImpl has redefined the
+ * reading/getting the remote audio and video SSRCs. Consequently,
+ * CallPeerMediaHandlerJabberImpl has to COMPLETELY redefine the
+ * writing/setting as well i.e. it has to stop related
+ * PropertyChangeEvents fired by the super.
+ */
+ @Override
+ protected void mediaHandlerPropertyChange(PropertyChangeEvent ev)
+ {
+ String propertyName = ev.getPropertyName();
+
+ if ((AUDIO_REMOTE_SSRC.equals(propertyName)
+ || VIDEO_REMOTE_SSRC.equals(propertyName))
+ && getPeer().isJitsiVideobridge())
+ return;
+
+ super.mediaHandlerPropertyChange(ev);
+ }
+
+ /**
+ * Handles the specified answer by creating and initializing the
+ * corresponding MediaStreams.
+ *
+ * @param answer the Jingle answer
+ *
+ * @throws OperationFailedException if we fail to handle answer for
+ * reasons like failing to initialize media devices or streams.
+ * @throws IllegalArgumentException if there's a problem with the syntax or
+ * the semantics of answer. Method is synchronized in order to
+ * avoid closing mediaHandler when we are currently in process of
+ * initializing, configuring and starting streams and anybody interested
+ * in this operation can synchronize to the mediaHandler instance to wait
+ * processing to stop (method setState in CallPeer).
+ */
+ public void processAnswer(List answer)
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ /*
+ * The answer given in session-accept may contain transport-related
+ * information compatible with that carried in transport-info.
+ */
+ processTransportInfo(answer);
+
+ boolean masterStreamSet = false;
+
+ for (ContentPacketExtension content : answer)
+ {
+ remoteContentMap.put(content.getName(), content);
+
+ boolean masterStream = false;
+
+ // if we have more than one stream, let the audio be the master
+ if(!masterStreamSet)
+ {
+ if(answer.size() > 1)
+ {
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+
+ if(MediaType.AUDIO.toString().equals(
+ description.getMedia()))
+ {
+ masterStream = true;
+ masterStreamSet = true;
+ }
+ }
+ else
+ {
+ masterStream = true;
+ masterStreamSet = true;
+ }
+ }
+
+ processContent(content, false, masterStream);
+ }
+ }
+
+ /**
+ * Notifies this instance that a specific ColibriConferenceIQ has
+ * been received. This CallPeerMediaHandler uses the part of the
+ * information provided in the specified conferenceIQ which
+ * concerns it only.
+ *
+ * @param conferenceIQ the ColibriConferenceIQ which has been
+ * received
+ */
+ void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
+ {
+ /*
+ * This CallPeerMediaHandler stores the media information but it does
+ * not store the colibri Channels (which contain both media and transport
+ * information). The TransportManager associated with this instance
+ * stores the colibri Channels but does not store media information (such
+ * as the remote SSRCs). An design/implementation choice has to be made
+ * though and the present one is to have this CallPeerMediaHandler
+ * transparently (with respect to the TransportManager) store the media
+ * information inside the TransportManager.
+ */
+ TransportManagerJabberImpl transportManager = this.transportManager;
+
+ if (transportManager != null)
+ {
+ long oldAudioRemoteSSRC = getRemoteSSRC(MediaType.AUDIO);
+ long oldVideoRemoteSSRC = getRemoteSSRC(MediaType.VIDEO);
+
+ for (MediaType mediaType : MediaType.values())
+ {
+ ColibriConferenceIQ.Channel dst
+ = transportManager.getColibriChannel(
+ mediaType,
+ false /* remote */);
+
+ if (dst != null)
+ {
+ ColibriConferenceIQ.Content content
+ = conferenceIQ.getContent(mediaType.toString());
+
+ if (content != null)
+ {
+ ColibriConferenceIQ.Channel src
+ = content.getChannel(dst.getID());
+
+ if (src != null)
+ {
+ int[] ssrcs = src.getSSRCs();
+ int[] dstSSRCs = dst.getSSRCs();
+
+ if (!Arrays.equals(dstSSRCs, ssrcs))
+ dst.setSSRCs(ssrcs);
+ }
+ }
+ }
+ }
+
+ /*
+ * Do fire new PropertyChangeEvents for the properties
+ * AUDIO_REMOTE_SSRC and VIDEO_REMOTE_SSRC if necessary.
+ */
+ long newAudioRemoteSSRC = getRemoteSSRC(MediaType.AUDIO);
+ long newVideoRemoteSSRC = getRemoteSSRC(MediaType.VIDEO);
+
+ if (oldAudioRemoteSSRC != newAudioRemoteSSRC)
+ {
+ firePropertyChange(
+ AUDIO_REMOTE_SSRC,
+ oldAudioRemoteSSRC, newAudioRemoteSSRC);
+ }
+ if (oldVideoRemoteSSRC != newVideoRemoteSSRC)
+ {
+ firePropertyChange(
+ VIDEO_REMOTE_SSRC,
+ oldVideoRemoteSSRC, newVideoRemoteSSRC);
+ }
+ }
+ }
+
+ /**
+ * Process a ContentPacketExtension and initialize its
+ * corresponding MediaStream.
+ *
+ * @param content a ContentPacketExtension
+ * @param modify if it correspond to a content-modify for resolution change
+ * @param masterStream whether the stream to be used as master
+ * @throws OperationFailedException if we fail to handle content
+ * for reasons like failing to initialize media devices or streams.
+ * @throws IllegalArgumentException if there's a problem with the syntax or
+ * the semantics of content. The method is synchronized in order to
+ * avoid closing mediaHandler when we are currently in process of
+ * initializing, configuring and starting streams and anybody interested
+ * in this operation can synchronize to the mediaHandler instance to wait
+ * processing to stop (method setState in CallPeer).
+ */
+ private void processContent(
+ ContentPacketExtension content,
+ boolean modify,
+ boolean masterStream)
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+ MediaType mediaType
+ = MediaType.parseString(description.getMedia());
+
+ //stream target
+ TransportManagerJabberImpl transportManager = getTransportManager();
+ MediaStreamTarget target = transportManager.getStreamTarget(mediaType);
+
+ if (target == null)
+ target = JingleUtils.extractDefaultTarget(content);
+
+ // no target port - try next media description
+ if((target == null) || (target.getDataAddress().getPort() == 0))
+ {
+ closeStream(mediaType);
+ return;
+ }
+
+ List supportedFormats = JingleUtils.extractFormats(
+ description, getDynamicPayloadTypes());
+
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ if(!isDeviceActive(dev))
+ {
+ closeStream(mediaType);
+ return;
+ }
+
+ MediaDirection devDirection
+ = (dev == null) ? MediaDirection.INACTIVE : dev.getDirection();
+
+ // Take the preference of the user with respect to streaming
+ // mediaType into account.
+ devDirection
+ = devDirection.and(getDirectionUserPreference(mediaType));
+
+ if (supportedFormats.isEmpty())
+ {
+ //remote party must have messed up our Jingle description.
+ //throw an exception.
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "Remote party sent an invalid Jingle answer.",
+ OperationFailedException.ILLEGAL_ARGUMENT,
+ null,
+ logger);
+ }
+
+ CallJabberImpl call = getPeer().getCall();
+ CallConference conference
+ = (call == null) ? null : call.getConference();
+
+ /*
+ * Neither SDES nor ZRTP is supported in telephony conferences utilizing
+ * the server-side technology Jitsi Videobridge yet.
+ */
+ if ((conference == null) || !conference.isJitsiVideobridge())
+ {
+ addZrtpAdvertisedEncryptions(true, description, mediaType);
+ addSDesAdvertisedEncryptions(true, description, mediaType);
+ }
+ addDtlsAdvertisedEncryptions(true, content, mediaType);
+
+ StreamConnector connector
+ = transportManager.getStreamConnector(mediaType);
+
+ //determine the direction that we need to announce.
+ MediaDirection remoteDirection
+ = JingleUtils.getDirection(content, getPeer().isInitiator());
+ /*
+ * If we are the focus of a conference, we need to take into account the
+ * other participants.
+ */
+ if ((conference != null) && conference.isConferenceFocus())
+ {
+ for (CallPeerJabberImpl peer : call.getCallPeerList())
+ {
+ SendersEnum senders
+ = peer.getSenders(mediaType);
+ boolean initiator = peer.isInitiator();
+ //check if the direction of the jingle session we have with
+ //this peer allows us receiving media. If senders is null,
+ //assume the default of 'both'
+ if ((senders == null)
+ || (SendersEnum.both == senders)
+ || (initiator && SendersEnum.initiator == senders)
+ || (!initiator && SendersEnum.responder == senders))
+ {
+ remoteDirection
+ = remoteDirection.or(MediaDirection.SENDONLY);
+ }
+ }
+ }
+
+ MediaDirection direction
+ = devDirection.getDirectionForAnswer(remoteDirection);
+
+ // update the RTP extensions that we will be exchanging.
+ List remoteRTPExtensions
+ = JingleUtils.extractRTPExtensions(
+ description,
+ getRtpExtensionsRegistry());
+ List supportedExtensions
+ = getExtensionsForType(mediaType);
+ List rtpExtensions
+ = intersectRTPExtensions(remoteRTPExtensions, supportedExtensions);
+
+ Map adv
+ = supportedFormats.get(0).getAdvancedAttributes();
+
+ if(adv != null)
+ {
+ for(Map.Entry f : adv.entrySet())
+ {
+ if(f.getKey().equals("imageattr"))
+ supportQualityControls = true;
+ }
+ }
+
+ // check for options from remote party and set them locally
+ if(mediaType.equals(MediaType.VIDEO) && modify)
+ {
+ // update stream
+ MediaStream stream = getStream(MediaType.VIDEO);
+
+ if(stream != null && dev != null)
+ {
+ List fmts = supportedFormats;
+
+ if(fmts.size() > 0)
+ {
+ MediaFormat fmt = fmts.get(0);
+
+ ((VideoMediaStream)stream).updateQualityControl(
+ fmt.getAdvancedAttributes());
+ }
+ }
+
+ if(qualityControls != null)
+ {
+ QualityPreset receiveQualityPreset
+ = qualityControls.getRemoteReceivePreset();
+ QualityPreset sendQualityPreset
+ = qualityControls.getRemoteSendMaxPreset();
+
+ supportedFormats
+ = (dev == null)
+ ? null
+ : intersectFormats(
+ supportedFormats,
+ getLocallySupportedFormats(
+ dev,
+ sendQualityPreset,
+ receiveQualityPreset));
+ }
+ }
+
+ // create the corresponding stream...
+ initStream(
+ content.getName(),
+ connector,
+ dev,
+ supportedFormats.get(0),
+ target,
+ direction,
+ rtpExtensions,
+ masterStream);
+ }
+
+ /**
+ * Parses and handles the specified offer and returns a content
+ * extension representing the current state of this media handler. This
+ * method MUST only be called when offer is the first session
+ * description that this MediaHandler is seeing.
+ *
+ * @param offer the offer that we'd like to parse, handle and get an answer
+ * for.
+ *
+ * @throws OperationFailedException if we have a problem satisfying the
+ * description received in offer (e.g. failed to open a device or
+ * initialize a stream ...).
+ * @throws IllegalArgumentException if there's a problem with
+ * offer's format or semantics.
+ */
+ public void processOffer(List offer)
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ // prepare to generate answers to all the incoming descriptions
+ List answer
+ = new ArrayList(offer.size());
+ boolean atLeastOneValidDescription = false;
+
+ for (ContentPacketExtension content : offer)
+ {
+ remoteContentMap.put(content.getName(), content);
+
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+ MediaType mediaType
+ = MediaType.parseString( description.getMedia() );
+
+ List remoteFormats
+ = JingleUtils.extractFormats(
+ description,
+ getDynamicPayloadTypes());
+
+ MediaDevice dev = getDefaultDevice(mediaType);
+
+ MediaDirection devDirection
+ = (dev == null) ? MediaDirection.INACTIVE : dev.getDirection();
+
+ // Take the preference of the user with respect to streaming
+ // mediaType into account.
+ devDirection
+ = devDirection.and(getDirectionUserPreference(mediaType));
+
+ // determine the direction that we need to announce.
+ MediaDirection remoteDirection = JingleUtils.getDirection(
+ content, getPeer().isInitiator());
+ MediaDirection direction
+ = devDirection.getDirectionForAnswer(remoteDirection);
+
+ // intersect the MediaFormats of our device with remote ones
+ List mutuallySupportedFormats
+ = intersectFormats(
+ remoteFormats,
+ getLocallySupportedFormats(dev));
+
+ // check whether we will be exchanging any RTP extensions.
+ List offeredRTPExtensions
+ = JingleUtils.extractRTPExtensions(
+ description, this.getRtpExtensionsRegistry());
+
+ List supportedExtensions
+ = getExtensionsForType(mediaType);
+
+ List rtpExtensions = intersectRTPExtensions(
+ offeredRTPExtensions, supportedExtensions);
+
+ // transport
+ /*
+ * RawUdpTransportPacketExtension extends
+ * IceUdpTransportPacketExtension so getting
+ * IceUdpTransportPacketExtension should suffice.
+ */
+ IceUdpTransportPacketExtension transport
+ = content.getFirstChildOfType(
+ IceUdpTransportPacketExtension.class);
+
+ // stream target
+ MediaStreamTarget target = null;
+
+ try
+ {
+ target = JingleUtils.extractDefaultTarget(content);
+ }
+ catch(IllegalArgumentException e)
+ {
+ logger.warn("Fail to extract default target", e);
+ }
+
+ // according to XEP-176, transport element in session-initiate
+ // "MAY instead be empty (with each candidate to be sent as the
+ // payload of a transport-info message)".
+ int targetDataPort
+ = (target == null && transport != null)
+ ? -1
+ : (target != null) ? target.getDataAddress().getPort() : 0;
+
+ /*
+ * TODO If the offered transport is not supported, attempt to fall
+ * back to a supported one using transport-replace.
+ */
+ setTransportManager(transport.getNamespace());
+
+ if (mutuallySupportedFormats.isEmpty()
+ || (devDirection == MediaDirection.INACTIVE)
+ || (targetDataPort == 0))
+ {
+ // skip stream and continue. contrary to sip we don't seem to
+ // need to send per-stream disabling answer and only one at the
+ // end.
+
+ //close the stream in case it already exists
+ closeStream(mediaType);
+ continue;
+ }
+
+ SendersEnum senders = JingleUtils.getSenders(
+ direction,
+ !getPeer().isInitiator());
+ // create the answer description
+ ContentPacketExtension ourContent
+ = JingleUtils.createDescription(
+ content.getCreator(),
+ content.getName(),
+ senders,
+ mutuallySupportedFormats,
+ rtpExtensions,
+ getDynamicPayloadTypes(),
+ getRtpExtensionsRegistry());
+
+ /*
+ * Sets ZRTP, SDES or DTLS-SRTP depending on the preferences for
+ * this account.
+ */
+ setAndAddPreferredEncryptionProtocol(
+ mediaType,
+ ourContent,
+ content);
+
+ // Got a content which has inputevt. It means that the peer requests
+ // a desktop sharing session so tell it we support inputevt.
+ if(content.getChildExtensionsOfType(InputEvtPacketExtension.class)
+ != null)
+ {
+ ourContent.addChildExtension(new InputEvtPacketExtension());
+ }
+
+ answer.add(ourContent);
+ localContentMap.put(content.getName(), ourContent);
+
+ atLeastOneValidDescription = true;
+ }
+
+ if (!atLeastOneValidDescription)
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ "Offer contained no media formats"
+ + " or no valid media descriptions.",
+ OperationFailedException.ILLEGAL_ARGUMENT,
+ null,
+ logger);
+ }
+
+ /*
+ * In order to minimize post-pickup delay, start establishing the
+ * connectivity prior to ringing.
+ */
+ harvestCandidates(
+ offer,
+ answer,
+ new TransportInfoSender()
+ {
+ public void sendTransportInfo(
+ Iterable contents)
+ {
+ getPeer().sendTransportInfo(contents);
+ }
+ });
+
+ /*
+ * While it may sound like we can completely eliminate the post-pickup
+ * delay by waiting for the connectivity establishment to finish, it may
+ * not be possible in all cases. We are the Jingle session responder so,
+ * in the case of the ICE UDP transport, we are not the controlling ICE
+ * Agent and we cannot be sure when the controlling ICE Agent will
+ * perform the nomination. It could, for example, choose to wait for our
+ * session-accept to perform the nomination which will deadlock us if we
+ * have chosen to wait for the connectivity establishment to finish
+ * before we begin ringing and send session-accept.
+ */
+ getTransportManager().startConnectivityEstablishment(offer);
+ }
+
+ /**
+ * Processes the transport-related information provided by the remote
+ * peer in a specific set of ContentPacketExtensions.
+ *
+ * @param contents the ContentPacketExtenions provided by the
+ * remote peer and containing the transport-related information to
+ * be processed
+ * @throws OperationFailedException if anything goes wrong while processing
+ * the transport-related information provided by the remote peer in
+ * the specified set of ContentPacketExtensions
+ */
+ public void processTransportInfo(Iterable contents)
+ throws OperationFailedException
+ {
+ if (getTransportManager().startConnectivityEstablishment(contents))
+ {
+ //Emil: why the heck is this here and why is it commented?
+ //wrapupConnectivityEstablishment();
+ }
+ }
+
+ /**
+ * Reinitialize all media contents.
+ *
+ * @throws OperationFailedException if we fail to handle content
+ * for reasons like failing to initialize media devices or streams.
+ * @throws IllegalArgumentException if there's a problem with the syntax or
+ * the semantics of content. Method is synchronized in order to
+ * avoid closing mediaHandler when we are currently in process of
+ * initializing, configuring and starting streams and anybody interested
+ * in this operation can synchronize to the mediaHandler instance to wait
+ * processing to stop (method setState in CallPeer).
+ */
+ public void reinitAllContents()
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ boolean masterStreamSet = false;
+ for(String key : remoteContentMap.keySet())
+ {
+ ContentPacketExtension ext = remoteContentMap.get(key);
+
+ boolean masterStream = false;
+ // if we have more than one stream, lets the audio be the master
+ if(!masterStreamSet)
+ {
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(ext);
+ MediaType mediaType
+ = MediaType.parseString( description.getMedia() );
+
+ if(remoteContentMap.size() > 1)
+ {
+ if(mediaType.equals(MediaType.AUDIO))
+ {
+ masterStream = true;
+ masterStreamSet = true;
+ }
+ }
+ else
+ {
+ masterStream = true;
+ masterStreamSet = true;
+ }
+ }
+
+ if(ext != null)
+ processContent(ext, false, masterStream);
+ }
+ }
+
+ /**
+ * Reinitialize a media content such as video.
+ *
+ * @param name name of the Jingle content
+ * @param content media content
+ * @param modify if it correspond to a content-modify for resolution change
+ * @throws OperationFailedException if we fail to handle content
+ * for reasons like failing to initialize media devices or streams.
+ * @throws IllegalArgumentException if there's a problem with the syntax or
+ * the semantics of content. Method is synchronized in order to
+ * avoid closing mediaHandler when we are currently in process of
+ * initializing, configuring and starting streams and anybody interested
+ * in this operation can synchronize to the mediaHandler instance to wait
+ * processing to stop (method setState in CallPeer).
+ */
+ public void reinitContent(
+ String name,
+ ContentPacketExtension content,
+ boolean modify)
+ throws OperationFailedException,
+ IllegalArgumentException
+ {
+ ContentPacketExtension ext = remoteContentMap.get(name);
+
+ if(ext != null)
+ {
+ if(modify)
+ {
+ processContent(content, modify, false);
+ remoteContentMap.put(name, content);
+ }
+ else
+ {
+ ext.setSenders(content.getSenders());
+ processContent(ext, modify, false);
+ remoteContentMap.put(name, ext);
+ }
+ }
+ }
+
+ /**
+ * Removes a media content with a specific name from the session represented
+ * by this CallPeerMediaHandlerJabberImpl and closes its associated
+ * media stream.
+ *
+ * @param contentMap the Map in which the specified name
+ * has an association with the media content to be removed
+ * @param name the name of the media content to be removed from this session
+ */
+ private void removeContent(
+ Map contentMap,
+ String name)
+ {
+ ContentPacketExtension content = contentMap.remove(name);
+
+ if (content != null)
+ {
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(content);
+ String media = description.getMedia();
+
+ if (media != null)
+ closeStream(MediaType.parseString(media));
+ }
+ }
+
+ /**
+ * Removes a media content with a specific name from the session represented
+ * by this CallPeerMediaHandlerJabberImpl and closes its associated
+ * media stream.
+ *
+ * @param name the name of the media content to be removed from this session
+ */
+ public void removeContent(String name)
+ {
+ removeContent(localContentMap, name);
+ removeContent(remoteContentMap, name);
+
+ TransportManagerJabberImpl transportManager = queryTransportManager();
+
+ if (transportManager != null)
+ transportManager.removeContent(name);
+ }
+
+ /**
+ * Acts upon a notification received from the remote party indicating that
+ * they've put us on/off hold.
+ *
+ * @param onHold true if the remote party has put us on hold
+ * and false if they've just put us off hold.
+ */
+ public void setRemotelyOnHold(boolean onHold)
+ {
+ this.remotelyOnHold = onHold;
+
+ for (MediaType mediaType : MediaType.values())
+ {
+ MediaStream stream = getStream(mediaType);
+
+ if (stream == null)
+ continue;
+
+ if (getPeer().isJitsiVideobridge())
+ {
+ /*
+ * If we are the focus of a Videobridge conference, we need to
+ * ask the Videobridge to change the stream direction on our
+ * behalf.
+ */
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType);
+ MediaDirection direction;
+
+ if(remotelyOnHold)
+ {
+ direction = MediaDirection.INACTIVE;
+ }
+ else
+ {
+ // TODO Does SENDRECV always make sense?
+ direction = MediaDirection.SENDRECV;
+ }
+ getPeer().getCall().setChannelDirection(
+ channel.getID(),
+ mediaType,
+ direction);
+ }
+ else //no Videobridge
+ {
+ if (remotelyOnHold)
+ {
+ /*
+ * In conferences we use INACTIVE to prevent, for example,
+ * on-hold music from being played to all the participants.
+ */
+ MediaDirection newDirection
+ = getPeer().getCall().isConferenceFocus()
+ ? MediaDirection.INACTIVE
+ : stream.getDirection().and(
+ MediaDirection.RECVONLY);
+
+ stream.setDirection(newDirection);
+ }
+ else
+ {
+ stream.setDirection(calculatePostHoldDirection(stream));
+ }
+ }
+ }
+ }
+
+ /**
+ * Sometimes as initing a call with custom preset can set and we force
+ * that quality controls is supported.
+ *
+ * @param value whether quality controls is supported..
+ */
+ public void setSupportQualityControls(boolean value)
+ {
+ this.supportQualityControls = value;
+ }
+
+ /**
+ * Sets the TransportManager implementation to handle our address
+ * management by Jingle transport XML namespace.
+ *
+ * @param xmlns the Jingle transport XML namespace specifying the
+ * TransportManager implementation type to be set on this instance
+ * to handle our address management
+ * @throws IllegalArgumentException if the specified xmlns does not
+ * specify a (supported) TransportManager implementation type
+ */
+ private void setTransportManager(String xmlns)
+ throws IllegalArgumentException
+ {
+ // Is this really going to be an actual change?
+ if ((transportManager != null)
+ && transportManager.getXmlNamespace().equals(xmlns))
+ {
+ return;
+ }
+
+ CallPeerJabberImpl peer = getPeer();
+
+ if (!peer.getProtocolProvider().getDiscoveryManager().includesFeature(
+ xmlns))
+ {
+ throw new IllegalArgumentException(
+ "Unsupported Jingle transport " + xmlns);
+ }
+
+ /*
+ * TODO The transportManager is going to be changed so it may need to be
+ * disposed of prior to the change.
+ */
+
+ if (xmlns.equals(
+ ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1))
+ {
+ transportManager = new IceUdpTransportManager(peer);
+ }
+ else if (xmlns.equals(
+ ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0))
+ {
+ transportManager = new RawUdpTransportManager(peer);
+ }
+ else
+ {
+ throw new IllegalArgumentException(
+ "Unsupported Jingle transport " + xmlns);
+ }
+
+ synchronized(transportManagerSyncRoot)
+ {
+ transportManagerSyncRoot.notify();
+ }
+ }
+
+ /**
+ * Waits for the associated TransportManagerJabberImpl to conclude
+ * any started connectivity establishment and then starts this
+ * CallPeerMediaHandler.
+ *
+ * @throws IllegalStateException if no offer or answer has been provided or
+ * generated earlier
+ */
+ @Override
+ public void start()
+ throws IllegalStateException
+ {
+ try
+ {
+ wrapupConnectivityEstablishment();
+ }
+ catch (OperationFailedException ofe)
+ {
+ throw new UndeclaredThrowableException(ofe);
+ }
+
+ super.start();
+ }
+
+ /**
+ * Lets the underlying implementation take note of this error and only
+ * then throws it to the using bundles.
+ *
+ * @param message the message to be logged and then wrapped in a new
+ * OperationFailedException
+ * @param errorCode the error code to be assigned to the new
+ * OperationFailedException
+ * @param cause the Throwable that has caused the necessity to log
+ * an error and have a new OperationFailedException thrown
+ *
+ * @throws OperationFailedException the exception that we wanted this method
+ * to throw.
+ */
+ @Override
+ protected void throwOperationFailedException(
+ String message,
+ int errorCode,
+ Throwable cause)
+ throws OperationFailedException
+ {
+ ProtocolProviderServiceJabberImpl.throwOperationFailedException(
+ message,
+ errorCode,
+ cause,
+ logger);
+ }
+
+ /**
+ * Notifies the associated TransportManagerJabberImpl that it
+ * should conclude any connectivity establishment, waits for it to actually
+ * do so and sets the connectors and targets of the
+ * MediaStreams managed by this CallPeerMediaHandler.
+ *
+ * @throws OperationFailedException if anything goes wrong while setting the
+ * connectors and/or targets of the MediaStreams
+ * managed by this CallPeerMediaHandler
+ */
+ private void wrapupConnectivityEstablishment()
+ throws OperationFailedException
+ {
+ TransportManagerJabberImpl transportManager = getTransportManager();
+
+ transportManager.wrapupConnectivityEstablishment();
+ for (MediaType mediaType : MediaType.values())
+ {
+ MediaStream stream = getStream(mediaType);
+
+ if (stream != null)
+ {
+ stream.setConnector(
+ transportManager.getStreamConnector(mediaType));
+ stream.setTarget(transportManager.getStreamTarget(mediaType));
+ }
+ }
+ }
+
+
+
+ /**
+ * If Jitsi Videobridge is in use, returns the
+ * ColibriConferenceIQ.Channel that this
+ * CallPeerMediaHandler uses for media of type mediaType.
+ * Otherwise, returns null
+ *
+ * @param mediaType the MediaType for which to return a
+ * ColibriConferenceIQ.Channel
+ * @return the ColibriConferenceIQ.Channel that this
+ * CallPeerMediaHandler uses for media of type mediaType
+ * or null.
+ */
+ private ColibriConferenceIQ.Channel getColibriChannel(MediaType mediaType)
+ {
+ ColibriConferenceIQ.Channel channel = null;
+
+ if (getPeer().isJitsiVideobridge())
+ {
+ TransportManagerJabberImpl transportManager = this.transportManager;
+
+ if (transportManager != null)
+ {
+ channel
+ = transportManager.getColibriChannel(
+ mediaType,
+ false /* remote */);
+ }
+ }
+ return channel;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * The super implementation relies on the direction of the streams and is
+ * therefore not accurate when we use a Videobridge.
+ */
+ @Override
+ public boolean isRemotelyOnHold()
+ {
+ return remotelyOnHold;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Handles the case when a Videobridge is in use.
+ *
+ * @param locallyOnHold true if we are to make our streams
+ * stop transmitting and false if we are to start transmitting
+ */
+ @Override
+ public void setLocallyOnHold(boolean locallyOnHold)
+ {
+ CallPeerJabberImpl peer = getPeer();
+
+ if (peer.isJitsiVideobridge())
+ {
+ this.locallyOnHold = locallyOnHold;
+
+ if (locallyOnHold
+ || !CallPeerState.ON_HOLD_MUTUALLY.equals(peer.getState()))
+ {
+ for (MediaType mediaType : MediaType.values())
+ {
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType);
+
+ if (channel != null)
+ {
+ MediaDirection direction
+ = locallyOnHold
+ ? MediaDirection.INACTIVE
+ : MediaDirection.SENDRECV;
+
+ peer.getCall().setChannelDirection(
+ channel.getID(),
+ mediaType,
+ direction);
+ }
+ }
+ }
+ }
+ else
+ {
+ super.setLocallyOnHold(locallyOnHold);
+ }
+ }
+
+ /**
+ * Detects and adds DTLS-SRTP available encryption method present in the
+ * content (description) given in parameter.
+ *
+ * @param isInitiator true if the local call instance is the
+ * initiator of the call; false, otherwise.
+ * @param content The CONTENT element of the JINGLE element which contains
+ * the TRANSPORT element
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ */
+ private boolean addDtlsAdvertisedEncryptions(
+ boolean isInitiator,
+ ContentPacketExtension content,
+ MediaType mediaType)
+ {
+ if (getPeer().isJitsiVideobridge())
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+ else
+ {
+ IceUdpTransportPacketExtension remoteTransport
+ = content.getFirstChildOfType(
+ IceUdpTransportPacketExtension.class);
+
+ return
+ addDtlsAdvertisedEncryptions(
+ isInitiator,
+ remoteTransport,
+ mediaType);
+ }
+ }
+
+ /**
+ * Detects and adds DTLS-SRTP available encryption method present in the
+ * transport (description) given in parameter.
+ *
+ * @param isInitiator true if the local call instance is the
+ * initiator of the call; false, otherwise.
+ * @param remoteTransport the TRANSPORT element
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ */
+ boolean addDtlsAdvertisedEncryptions(
+ boolean isInitiator,
+ IceUdpTransportPacketExtension remoteTransport,
+ MediaType mediaType)
+ {
+ SrtpControls srtpControls = getSrtpControls();
+ boolean b = false;
+
+ if (remoteTransport != null)
+ {
+ List remoteFingerpintPEs
+ = remoteTransport.getChildExtensionsOfType(
+ DtlsFingerprintPacketExtension.class);
+
+ if (!remoteFingerpintPEs.isEmpty())
+ {
+ AccountID accountID
+ = getPeer().getProtocolProvider().getAccountID();
+
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ SrtpControlType.DTLS_SRTP))
+ {
+ Map remoteFingerprints
+ = new LinkedHashMap();
+
+ for (DtlsFingerprintPacketExtension remoteFingerprintPE
+ : remoteFingerpintPEs)
+ {
+ String remoteFingerprint
+ = remoteFingerprintPE.getFingerprint();
+ String remoteHash = remoteFingerprintPE.getHash();
+
+ remoteFingerprints.put(
+ remoteHash,
+ remoteFingerprint);
+ }
+
+ DtlsControl dtlsControl;
+ DtlsControl.Setup setup;
+
+ if (isInitiator)
+ {
+ dtlsControl
+ = (DtlsControl)
+ srtpControls.get(
+ mediaType,
+ SrtpControlType.DTLS_SRTP);
+ setup = DtlsControl.Setup.PASSIVE;
+ }
+ else
+ {
+ dtlsControl
+ = (DtlsControl)
+ srtpControls.getOrCreate(
+ mediaType,
+ SrtpControlType.DTLS_SRTP);
+ setup = DtlsControl.Setup.ACTIVE;
+ }
+ if (dtlsControl != null)
+ {
+ dtlsControl.setRemoteFingerprints(remoteFingerprints);
+ dtlsControl.setSetup(setup);
+ removeAndCleanupOtherSrtpControls(
+ mediaType,
+ SrtpControlType.DTLS_SRTP);
+ addAdvertisedEncryptionMethod(
+ SrtpControlType.DTLS_SRTP);
+ b = true;
+ }
+ }
+ }
+ }
+ /*
+ * If they haven't advertised DTLS-SRTP in their (media) description,
+ * then DTLS-SRTP shouldn't be functioning as far as we're concerned.
+ */
+ if (!b)
+ {
+ SrtpControl dtlsControl
+ = srtpControls.get(mediaType, SrtpControlType.DTLS_SRTP);
+
+ if (dtlsControl != null)
+ {
+ srtpControls.remove(mediaType, SrtpControlType.DTLS_SRTP);
+ dtlsControl.cleanup();
+ }
+ }
+ return b;
+ }
+
+ /**
+ * Selects the preferred encryption protocol (only used by the callee).
+ *
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ * @param localContent The element containing the media DESCRIPTION and
+ * its encryption.
+ * @param remoteContent The element containing the media DESCRIPTION and
+ * its encryption for the remote peer; null if the local peer is
+ * the initiator of the call.
+ */
+ private void setAndAddPreferredEncryptionProtocol(
+ MediaType mediaType,
+ ContentPacketExtension localContent,
+ ContentPacketExtension remoteContent)
+ {
List preferredEncryptionProtocols
- = getPeer()
- .getProtocolProvider()
- .getAccountID()
- .getSortedEnabledEncryptionProtocolList();
-
+ = getPeer()
+ .getProtocolProvider()
+ .getAccountID()
+ .getSortedEnabledEncryptionProtocolList();
+
for (SrtpControlType srtpControlType : preferredEncryptionProtocols)
- {
- // DTLS-SRTP
+ {
+ // DTLS-SRTP
if (srtpControlType == SrtpControlType.DTLS_SRTP)
- {
- addDtlsAdvertisedEncryptions(
- false,
- remoteContent,
- mediaType);
- if (setDtlsEncryptionOnContent(
- mediaType,
- localContent,
- remoteContent))
- {
- // Stop once an encryption advertisement has been chosen.
- return;
- }
- }
- else
- {
- RtpDescriptionPacketExtension localDescription
- = (localContent == null)
- ? null
- : JingleUtils.getRtpDescription(localContent);
- RtpDescriptionPacketExtension remoteDescription
- = (remoteContent == null)
- ? null
- : JingleUtils.getRtpDescription(remoteContent);
-
- if (setAndAddPreferredEncryptionProtocol(
+ {
+ addDtlsAdvertisedEncryptions(
+ false,
+ remoteContent,
+ mediaType);
+ if (setDtlsEncryptionOnContent(
+ mediaType,
+ localContent,
+ remoteContent))
+ {
+ // Stop once an encryption advertisement has been chosen.
+ return;
+ }
+ }
+ else
+ {
+ RtpDescriptionPacketExtension localDescription
+ = (localContent == null)
+ ? null
+ : JingleUtils.getRtpDescription(localContent);
+ RtpDescriptionPacketExtension remoteDescription
+ = (remoteContent == null)
+ ? null
+ : JingleUtils.getRtpDescription(remoteContent);
+
+ if (setAndAddPreferredEncryptionProtocol(
srtpControlType,
- mediaType,
- localDescription,
- remoteDescription))
- {
- // Stop once an encryption advertisement has been chosen.
- return;
- }
- }
- }
- }
-
- /**
- * Sets DTLS-SRTP element(s) to the TRANSPORT element of the CONTENT for a
- * given media.
- *
- * @param mediaType The type of media we are modifying the CONTENT to
- * integrate the DTLS-SRTP element(s).
- * @param localContent The element containing the media CONTENT and its
- * TRANSPORT.
- * @param remoteContent The element containing the media CONTENT and its
- * TRANSPORT for the remote peer. Null, if the local peer is the initiator
- * of the call.
- * @return true if any DTLS-SRTP element has been added to the
- * specified localContent; false, otherwise.
- */
- private boolean setDtlsEncryptionOnContent(
- MediaType mediaType,
- ContentPacketExtension localContent,
- ContentPacketExtension remoteContent)
- {
- CallPeerJabberImpl peer = getPeer();
- boolean b = false;
-
- if (peer.isJitsiVideobridge())
- {
- b
- = setDtlsEncryptionOnTransport(
- mediaType,
- localContent,
- remoteContent);
- return b;
- }
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = peer.getProtocolProvider();
- AccountID accountID = protocolProvider.getAccountID();
- SrtpControls srtpControls = getSrtpControls();
-
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- DtlsControl.PROTO_NAME))
- {
- boolean addFingerprintToLocalTransport;
-
- if (remoteContent == null) // initiator
- {
- addFingerprintToLocalTransport
- = protocolProvider.isFeatureSupported(
- peer.getAddress(),
- ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_DTLS_SRTP);
- }
- else // responder
- {
- addFingerprintToLocalTransport
- = addDtlsAdvertisedEncryptions(
- false,
- remoteContent,
- mediaType);
- }
- if (addFingerprintToLocalTransport)
- {
- DtlsControl dtlsControl
- = (DtlsControl)
- srtpControls.getOrCreate(
- mediaType,
- SrtpControlType.DTLS_SRTP);
-
- if (dtlsControl != null)
- {
- DtlsControl.Setup setup
- = (remoteContent == null)
- ? DtlsControl.Setup.PASSIVE
- : DtlsControl.Setup.ACTIVE;
-
- dtlsControl.setSetup(setup);
- b = true;
-
- setDtlsEncryptionOnTransport(
- mediaType,
- localContent,
- remoteContent);
- }
- }
- }
- /*
- * If we haven't advertised DTLS-SRTP in our (media) description, then
- * DTLS-SRTP shouldn't be functioning as far as we're concerned.
- */
- if (!b)
- {
- SrtpControl dtlsControl
- = srtpControls.get(mediaType, SrtpControlType.DTLS_SRTP);
-
- if (dtlsControl != null)
- {
- srtpControls.remove(mediaType, SrtpControlType.DTLS_SRTP);
- dtlsControl.cleanup();
- }
- }
- return b;
- }
-
- /**
- * Sets DTLS-SRTP element(s) to the TRANSPORT element of the CONTENT for a
- * given media.
- *
- * @param mediaType The type of media we are modifying the CONTENT to
- * integrate the DTLS-SRTP element(s).
- * @param localContent The element containing the media CONTENT and its
- * TRANSPORT.
- */
- private boolean setDtlsEncryptionOnTransport(
- MediaType mediaType,
- ContentPacketExtension localContent,
- ContentPacketExtension remoteContent)
- {
- IceUdpTransportPacketExtension localTransport
- = localContent.getFirstChildOfType(
- IceUdpTransportPacketExtension.class);
- boolean b = false;
-
- if (localTransport == null)
- return b;
-
- CallPeerJabberImpl peer = getPeer();
-
- if (peer.isJitsiVideobridge())
- {
- ProtocolProviderServiceJabberImpl protocolProvider
- = peer.getProtocolProvider();
- AccountID accountID = protocolProvider.getAccountID();
-
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- DtlsControl.PROTO_NAME))
- {
- // Gather the local fingerprints to be sent to the remote peer.
- ColibriConferenceIQ.Channel channel
- = getColibriChannel(mediaType);
- List localFingerprints = null;
-
- if (channel != null)
- {
- IceUdpTransportPacketExtension transport
- = channel.getTransport();
-
- if (transport != null)
- {
- localFingerprints
- = transport.getChildExtensionsOfType(
- DtlsFingerprintPacketExtension.class);
- }
- }
-
- /*
- * Determine whether the local fingerprints are to be sent to
- * the remote peer.
- */
- if ((localFingerprints != null) && !localFingerprints.isEmpty())
- {
- if (remoteContent == null) // initiator
- {
- if (!protocolProvider.isFeatureSupported(
- peer.getAddress(),
- ProtocolProviderServiceJabberImpl
- .URN_XMPP_JINGLE_DTLS_SRTP))
- {
- localFingerprints = null;
- }
- }
- else // responder
- {
- IceUdpTransportPacketExtension transport
- = remoteContent.getFirstChildOfType(
- IceUdpTransportPacketExtension.class);
-
- if (transport == null)
- {
- localFingerprints = null;
- }
- else
- {
- List
- remoteFingerprints
- = transport.getChildExtensionsOfType(
- DtlsFingerprintPacketExtension
- .class);
-
- if (remoteFingerprints.isEmpty())
- localFingerprints = null;
- }
- }
- // Send the local fingerprints to the remote peer.
- if (localFingerprints != null)
- {
- List fingerprintPEs
- = localTransport.getChildExtensionsOfType(
- DtlsFingerprintPacketExtension.class);
-
- if (fingerprintPEs.isEmpty())
- {
- for (DtlsFingerprintPacketExtension localFingerprint
- : localFingerprints)
- {
- DtlsFingerprintPacketExtension fingerprintPE
- = new DtlsFingerprintPacketExtension();
-
- fingerprintPE.setFingerprint(
- localFingerprint.getFingerprint());
- fingerprintPE.setHash(
- localFingerprint.getHash());
- localTransport.addChildExtension(fingerprintPE);
- }
- }
- b = true;
- }
- }
- }
- }
- else
- {
- SrtpControls srtpControls = getSrtpControls();
- DtlsControl dtlsControl
- = (DtlsControl)
- srtpControls.get(mediaType, SrtpControlType.DTLS_SRTP);
-
- if (dtlsControl != null)
- {
- CallJabberImpl.setDtlsEncryptionOnTransport(
- dtlsControl,
- localTransport);
- b = true;
- }
- }
- return b;
- }
-
- /**
- * Sets DTLS-SRTP element(s) to the TRANSPORT element of a specified list of
- * CONTENT elements.
- *
- * @param localContents The elements containing the media CONTENT elements
- * and their respective TRANSPORT elements.
- */
- private void setDtlsEncryptionOnTransports(
- List remoteContents,
- List localContents)
- {
- for (ContentPacketExtension localContent : localContents)
- {
- RtpDescriptionPacketExtension description
- = JingleUtils.getRtpDescription(localContent);
-
- if (description != null)
- {
- MediaType mediaType = JingleUtils.getMediaType(localContent);
-
- if (mediaType != null)
- {
- ContentPacketExtension remoteContent
- = (remoteContents == null)
- ? null
- : TransportManagerJabberImpl.findContentByName(
- remoteContents,
- localContent.getName());
-
- setDtlsEncryptionOnTransport(
- mediaType,
- localContent,
- remoteContent);
- }
- }
- }
- }
-
- /**
- * Sets the jingle transports that this
- * CallPeerMediaHandlerJabberImpl supports. Unknown transports are
- * ignored, and the transports Collection is put into
- * order depending on local preference.
- *
- * Currently only ice and raw-udp are recognized, with ice being preffered
- * over raw-udp
- *
- * @param transports A Collection of XML namespaces of jingle
- * transport elements to be set as the supported jingle transports for this
- * CallPeerMediaHandlerJabberImpl
- */
- public void setSupportedTransports(Collection transports)
- {
- if (transports == null)
- return;
-
- String ice
- = ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1;
- String rawUdp
- = ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0;
-
- int size = 0;
- for(String transport : transports)
- if (ice.equals(transport) || rawUdp.equals(transport))
- size++;
-
- if (size > 0)
- {
- synchronized (supportedTransportsSyncRoot)
- {
- supportedTransports = new String[size];
- int i = 0;
-
- // we prefer ice over raw-udp
- if (transports.contains(ice))
- {
- supportedTransports[i] = ice;
- i++;
- }
-
- if (transports.contains(rawUdp))
- {
- supportedTransports[i] = rawUdp;
- i++;
- }
- }
- }
- }
-}
+ mediaType,
+ localDescription,
+ remoteDescription))
+ {
+ // Stop once an encryption advertisement has been chosen.
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets DTLS-SRTP element(s) to the TRANSPORT element of the CONTENT for a
+ * given media.
+ *
+ * @param mediaType The type of media we are modifying the CONTENT to
+ * integrate the DTLS-SRTP element(s).
+ * @param localContent The element containing the media CONTENT and its
+ * TRANSPORT.
+ * @param remoteContent The element containing the media CONTENT and its
+ * TRANSPORT for the remote peer. Null, if the local peer is the initiator
+ * of the call.
+ * @return true if any DTLS-SRTP element has been added to the
+ * specified localContent; false, otherwise.
+ */
+ private boolean setDtlsEncryptionOnContent(
+ MediaType mediaType,
+ ContentPacketExtension localContent,
+ ContentPacketExtension remoteContent)
+ {
+ CallPeerJabberImpl peer = getPeer();
+ boolean b = false;
+
+ if (peer.isJitsiVideobridge())
+ {
+ b
+ = setDtlsEncryptionOnTransport(
+ mediaType,
+ localContent,
+ remoteContent);
+ return b;
+ }
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = peer.getProtocolProvider();
+ AccountID accountID = protocolProvider.getAccountID();
+ SrtpControls srtpControls = getSrtpControls();
+
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ SrtpControlType.DTLS_SRTP))
+ {
+ boolean addFingerprintToLocalTransport;
+
+ if (remoteContent == null) // initiator
+ {
+ addFingerprintToLocalTransport
+ = protocolProvider.isFeatureSupported(
+ peer.getAddress(),
+ ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_DTLS_SRTP);
+ }
+ else // responder
+ {
+ addFingerprintToLocalTransport
+ = addDtlsAdvertisedEncryptions(
+ false,
+ remoteContent,
+ mediaType);
+ }
+ if (addFingerprintToLocalTransport)
+ {
+ DtlsControl dtlsControl
+ = (DtlsControl)
+ srtpControls.getOrCreate(
+ mediaType,
+ SrtpControlType.DTLS_SRTP);
+
+ if (dtlsControl != null)
+ {
+ DtlsControl.Setup setup
+ = (remoteContent == null)
+ ? DtlsControl.Setup.PASSIVE
+ : DtlsControl.Setup.ACTIVE;
+
+ dtlsControl.setSetup(setup);
+ b = true;
+
+ setDtlsEncryptionOnTransport(
+ mediaType,
+ localContent,
+ remoteContent);
+ }
+ }
+ }
+ /*
+ * If we haven't advertised DTLS-SRTP in our (media) description, then
+ * DTLS-SRTP shouldn't be functioning as far as we're concerned.
+ */
+ if (!b)
+ {
+ SrtpControl dtlsControl
+ = srtpControls.get(mediaType, SrtpControlType.DTLS_SRTP);
+
+ if (dtlsControl != null)
+ {
+ srtpControls.remove(mediaType, SrtpControlType.DTLS_SRTP);
+ dtlsControl.cleanup();
+ }
+ }
+ return b;
+ }
+
+ /**
+ * Sets DTLS-SRTP element(s) to the TRANSPORT element of the CONTENT for a
+ * given media.
+ *
+ * @param mediaType The type of media we are modifying the CONTENT to
+ * integrate the DTLS-SRTP element(s).
+ * @param localContent The element containing the media CONTENT and its
+ * TRANSPORT.
+ */
+ private boolean setDtlsEncryptionOnTransport(
+ MediaType mediaType,
+ ContentPacketExtension localContent,
+ ContentPacketExtension remoteContent)
+ {
+ IceUdpTransportPacketExtension localTransport
+ = localContent.getFirstChildOfType(
+ IceUdpTransportPacketExtension.class);
+ boolean b = false;
+
+ if (localTransport == null)
+ return b;
+
+ CallPeerJabberImpl peer = getPeer();
+
+ if (peer.isJitsiVideobridge())
+ {
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = peer.getProtocolProvider();
+ AccountID accountID = protocolProvider.getAccountID();
+
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ SrtpControlType.DTLS_SRTP))
+ {
+ // Gather the local fingerprints to be sent to the remote peer.
+ ColibriConferenceIQ.Channel channel
+ = getColibriChannel(mediaType);
+ List localFingerprints = null;
+
+ if (channel != null)
+ {
+ IceUdpTransportPacketExtension transport
+ = channel.getTransport();
+
+ if (transport != null)
+ {
+ localFingerprints
+ = transport.getChildExtensionsOfType(
+ DtlsFingerprintPacketExtension.class);
+ }
+ }
+
+ /*
+ * Determine whether the local fingerprints are to be sent to
+ * the remote peer.
+ */
+ if ((localFingerprints != null) && !localFingerprints.isEmpty())
+ {
+ if (remoteContent == null) // initiator
+ {
+ if (!protocolProvider.isFeatureSupported(
+ peer.getAddress(),
+ ProtocolProviderServiceJabberImpl
+ .URN_XMPP_JINGLE_DTLS_SRTP))
+ {
+ localFingerprints = null;
+ }
+ }
+ else // responder
+ {
+ IceUdpTransportPacketExtension transport
+ = remoteContent.getFirstChildOfType(
+ IceUdpTransportPacketExtension.class);
+
+ if (transport == null)
+ {
+ localFingerprints = null;
+ }
+ else
+ {
+ List
+ remoteFingerprints
+ = transport.getChildExtensionsOfType(
+ DtlsFingerprintPacketExtension
+ .class);
+
+ if (remoteFingerprints.isEmpty())
+ localFingerprints = null;
+ }
+ }
+ // Send the local fingerprints to the remote peer.
+ if (localFingerprints != null)
+ {
+ List fingerprintPEs
+ = localTransport.getChildExtensionsOfType(
+ DtlsFingerprintPacketExtension.class);
+
+ if (fingerprintPEs.isEmpty())
+ {
+ for (DtlsFingerprintPacketExtension localFingerprint
+ : localFingerprints)
+ {
+ DtlsFingerprintPacketExtension fingerprintPE
+ = new DtlsFingerprintPacketExtension();
+
+ fingerprintPE.setFingerprint(
+ localFingerprint.getFingerprint());
+ fingerprintPE.setHash(
+ localFingerprint.getHash());
+ localTransport.addChildExtension(fingerprintPE);
+ }
+ }
+ b = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ SrtpControls srtpControls = getSrtpControls();
+ DtlsControl dtlsControl
+ = (DtlsControl)
+ srtpControls.get(mediaType, SrtpControlType.DTLS_SRTP);
+
+ if (dtlsControl != null)
+ {
+ CallJabberImpl.setDtlsEncryptionOnTransport(
+ dtlsControl,
+ localTransport);
+ b = true;
+ }
+ }
+ return b;
+ }
+
+ /**
+ * Sets DTLS-SRTP element(s) to the TRANSPORT element of a specified list of
+ * CONTENT elements.
+ *
+ * @param localContents The elements containing the media CONTENT elements
+ * and their respective TRANSPORT elements.
+ */
+ private void setDtlsEncryptionOnTransports(
+ List remoteContents,
+ List localContents)
+ {
+ for (ContentPacketExtension localContent : localContents)
+ {
+ RtpDescriptionPacketExtension description
+ = JingleUtils.getRtpDescription(localContent);
+
+ if (description != null)
+ {
+ MediaType mediaType = JingleUtils.getMediaType(localContent);
+
+ if (mediaType != null)
+ {
+ ContentPacketExtension remoteContent
+ = (remoteContents == null)
+ ? null
+ : TransportManagerJabberImpl.findContentByName(
+ remoteContents,
+ localContent.getName());
+
+ setDtlsEncryptionOnTransport(
+ mediaType,
+ localContent,
+ remoteContent);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the jingle transports that this
+ * CallPeerMediaHandlerJabberImpl supports. Unknown transports are
+ * ignored, and the transports Collection is put into
+ * order depending on local preference.
+ *
+ * Currently only ice and raw-udp are recognized, with ice being preffered
+ * over raw-udp
+ *
+ * @param transports A Collection of XML namespaces of jingle
+ * transport elements to be set as the supported jingle transports for this
+ * CallPeerMediaHandlerJabberImpl
+ */
+ public void setSupportedTransports(Collection transports)
+ {
+ if (transports == null)
+ return;
+
+ String ice
+ = ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_ICE_UDP_1;
+ String rawUdp
+ = ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0;
+
+ int size = 0;
+ for(String transport : transports)
+ if (ice.equals(transport) || rawUdp.equals(transport))
+ size++;
+
+ if (size > 0)
+ {
+ synchronized (supportedTransportsSyncRoot)
+ {
+ supportedTransports = new String[size];
+ int i = 0;
+
+ // we prefer ice over raw-udp
+ if (transports.contains(ice))
+ {
+ supportedTransports[i] = ice;
+ i++;
+ }
+
+ if (transports.contains(rawUdp))
+ {
+ supportedTransports[i] = rawUdp;
+ i++;
+ }
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
index f9ca61ad1..7e20957ac 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
@@ -1,2950 +1,2950 @@
-/*
- * 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.math.*;
-import java.net.*;
-import java.security.*;
-import java.security.cert.*;
-import java.text.*;
-import java.util.*;
-
-import javax.net.ssl.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.debugger.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.caps.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.carbon.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingleinfo.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*;
-import net.java.sip.communicator.service.certificate.*;
-import net.java.sip.communicator.service.dns.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.jabberconstants.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.util.Logger;
-
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.neomedia.*;
-import org.jitsi.util.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.provider.*;
-import org.jivesoftware.smack.util.StringUtils;
-import org.jivesoftware.smackx.*;
-import org.jivesoftware.smackx.packet.*;
-import org.osgi.framework.*;
-import org.xmpp.jnodes.smack.*;
-
-/**
- * An implementation of the protocol provider service over the Jabber protocol
- *
- * @author Damian Minkov
- * @author Symphorien Wanko
- * @author Lyubomir Marinov
- * @author Yana Stamcheva
- * @author Emil Ivov
- */
-public class ProtocolProviderServiceJabberImpl
- extends AbstractProtocolProviderService
-{
- /**
- * Logger of this class
- */
- private static final Logger logger =
- Logger.getLogger(ProtocolProviderServiceJabberImpl.class);
-
- /**
- * Jingle's Discovery Info common URN.
- */
- public static final String URN_XMPP_JINGLE = JingleIQ.NAMESPACE;
-
- /**
- * Jingle's Discovery Info URN for RTP support.
- */
- public static final String URN_XMPP_JINGLE_RTP
- = RtpDescriptionPacketExtension.NAMESPACE;
-
- /**
- * Jingle's Discovery Info URN for RTP support with audio.
- */
- public static final String URN_XMPP_JINGLE_RTP_AUDIO
- = "urn:xmpp:jingle:apps:rtp:audio";
-
- /**
- * Jingle's Discovery Info URN for RTP support with video.
- */
- public static final String URN_XMPP_JINGLE_RTP_VIDEO
- = "urn:xmpp:jingle:apps:rtp:video";
-
- /**
- * Jingle's Discovery Info URN for ZRTP support with RTP.
- */
- public static final String URN_XMPP_JINGLE_RTP_ZRTP
- = ZrtpHashPacketExtension.NAMESPACE;
-
- /**
- * Jingle's Discovery Info URN for ICE_UDP transport support.
- */
- public static final String URN_XMPP_JINGLE_RAW_UDP_0
- = RawUdpTransportPacketExtension.NAMESPACE;
-
- /**
- * Jingle's Discovery Info URN for ICE_UDP transport support.
- */
- public static final String URN_XMPP_JINGLE_ICE_UDP_1
- = IceUdpTransportPacketExtension.NAMESPACE;
-
- /**
- * Jingle's Discovery Info URN for Jingle Nodes support.
- */
- public static final String URN_XMPP_JINGLE_NODES
- = "http://jabber.org/protocol/jinglenodes";
-
- /**
- * Jingle's Discovery Info URN for "XEP-0251: Jingle Session Transfer"
- * support.
- */
- public static final String URN_XMPP_JINGLE_TRANSFER_0
- = TransferPacketExtension.NAMESPACE;
-
- /**
- * Jingle's Discovery Info URN for "XEP-298 :Delivering Conference
- * Information to Jingle Participants (Coin)" support.
- */
- public static final String URN_XMPP_JINGLE_COIN = "urn:xmpp:coin";
-
- /**
- * Jingle's Discovery Info URN for "XEP-0320: Use of DTLS-SRTP in
- * Jingle Sessions".
- */
- public static final String URN_XMPP_JINGLE_DTLS_SRTP
- = "urn:xmpp:jingle:apps:dtls:0";
-
- /**
- * Discovery Info URN for classic RFC3264-style Offer/Answer negotiation
- * with no support for Trickle ICE and low tolerance to transport/payload
- * separation. Defined in XEP-0176
- */
- public static final String URN_IETF_RFC_3264 = "urn:ietf:rfc:3264";
-
- /**
- * Jingle's Discovery Info URN for "XEP-0294: Jingle RTP Header Extensions
- * Negotiation" support.
- */
- public static final String URN_XMPP_JINGLE_RTP_HDREXT =
- "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
-
- /**
- * Capabilities name for audio call in Google Talk web version.
- */
- public static final String CAPS_GTALK_WEB_VOICE = "voice-v1";
-
- /**
- * Capabilities name for video call (receive side) in Google Talk web
- * version.
- */
- public static final String CAPS_GTALK_WEB_VIDEO = "video-v1";
-
- /**
- * Capabilities name for video call (sender side) in Google Talk web
- * version.
- */
- public static final String CAPS_GTALK_WEB_CAMERA = "camera-v1";
-
- /**
- * URN for Google voice.
- */
- public static final String URN_GOOGLE_VOICE =
- "http://www.google.com/xmpp/protocol/voice/v1";
-
- /**
- * URN for Google camera.
- */
- public static final String URN_GOOGLE_CAMERA =
- "http://www.google.com/xmpp/protocol/camera/v1";
-
- /**
- * URN for Google video.
- */
- public static final String URN_GOOGLE_VIDEO =
- "http://www.google.com/xmpp/protocol/video/v1";
-
- /**
- * URN for XEP-0077 inband registration
- */
- public static final String URN_REGISTER = "jabber:iq:register";
-
- /**
- * The name of the property under which the user may specify if the desktop
- * streaming or sharing should be disabled.
- */
- private static final String IS_DESKTOP_STREAMING_DISABLED
- = "net.java.sip.communicator.impl.protocol.jabber." +
- "DESKTOP_STREAMING_DISABLED";
-
- /**
- * The name of the property under which the user may specify if audio/video
- * calls should be disabled.
- */
- private static final String IS_CALLING_DISABLED
- = "net.java.sip.communicator.impl.protocol.jabber.CALLING_DISABLED";
-
- /**
- * Smack packet reply timeout.
- */
- public static final int SMACK_PACKET_REPLY_TIMEOUT = 45000;
-
- /**
- * Property for vcard reply timeout. Time to wait before
- * we think vcard retrieving has timeouted, default value
- * of smack is 5000 (5 sec.).
- */
- public static final String VCARD_REPLY_TIMEOUT_PROPERTY =
- "net.java.sip.communicator.impl.protocol.jabber.VCARD_REPLY_TIMEOUT";
-
- /**
- * XMPP signaling DSCP configuration property name.
- */
- private static final String XMPP_DSCP_PROPERTY =
- "net.java.sip.communicator.impl.protocol.XMPP_DSCP";
-
- /**
- * Indicates if user search is disabled.
- */
- private static final String IS_USER_SEARCH_ENABLED_PROPERTY
- = "USER_SEARCH_ENABLED";
-
- /**
- * Google voice domain name.
- */
- public static final String GOOGLE_VOICE_DOMAIN = "voice.google.com";
-
- /**
- * Used to connect to a XMPP server.
- */
- private XMPPConnection connection;
-
- /**
- * Indicates whether or not the provider is initialized and ready for use.
- */
- private boolean isInitialized = false;
-
- /**
- * We use this to lock access to initialization.
- */
- private final Object initializationLock = new Object();
-
- /**
- * The identifier of the account that this provider represents.
- */
- private AccountID accountID = null;
-
- /**
- * Used when we need to re-register
- */
- private SecurityAuthority authority = null;
-
- /**
- * The resource we will use when connecting during this run.
- */
- private String resource = null;
-
- /**
- * The icon corresponding to the jabber protocol.
- */
- private ProtocolIconJabberImpl jabberIcon;
-
- /**
- * A set of features supported by our Jabber implementation.
- * In general, we add new feature(s) when we add new operation sets.
- * (see xep-0030 : http://www.xmpp.org/extensions/xep-0030.html#info).
- * Example : to tell the world that we support jingle, we simply have
- * to do :
- * supportedFeatures.add("http://www.xmpp.org/extensions/xep-0166.html#ns");
- * Beware there is no canonical mapping between op set and jabber features
- * (op set is a SC "concept"). This means that one op set in SC can
- * correspond to many jabber features. It is also possible that there is no
- * jabber feature corresponding to a SC op set or again,
- * we can currently support some features wich do not have a specific
- * op set in SC (the mandatory feature :
- * http://jabber.org/protocol/disco#info is one example).
- * We can find features corresponding to op set in the xep(s) related
- * to implemented functionality.
- */
- private final List supportedFeatures = new ArrayList();
-
- /**
- * The ServiceDiscoveryManager is responsible for advertising
- * supportedFeatures when asked by a remote client. It can also
- * be used to query remote clients for supported features.
- */
- private ScServiceDiscoveryManager discoveryManager = null;
-
- /**
- * The OperationSetContactCapabilities of this
- * ProtocolProviderService which is the service-public counterpart
- * of {@link #discoveryManager}.
- */
- private OperationSetContactCapabilitiesJabberImpl opsetContactCapabilities;
-
- /**
- * The statuses.
- */
- private JabberStatusEnum jabberStatusEnum;
-
- /**
- * The service we use to interact with user.
- */
- private CertificateService guiVerification;
-
- /**
- * Used with tls connecting when certificates are not trusted
- * and we ask the user to confirm connection. When some timeout expires
- * connect method returns, and we use abortConnecting to abort further
- * execution cause after user chooses we make further processing from there.
- */
- private boolean abortConnecting = false;
-
- /**
- * Flag indicating are we currently executing connectAndLogin method.
- */
- private boolean inConnectAndLogin = false;
-
- /**
- * Object used to synchronize the flag inConnectAndLogin.
- */
- private final Object connectAndLoginLock = new Object();
-
- /**
- * If an event occurs during login we fire it at the end of the login
- * process (at the end of connectAndLogin method).
- */
- private RegistrationStateChangeEvent eventDuringLogin;
-
- /**
- * Listens for connection closes or errors.
- */
- private JabberConnectionListener connectionListener;
-
- /**
- * The details of the proxy we are using to connect to the server (if any)
- */
- private org.jivesoftware.smack.proxy.ProxyInfo proxy;
-
- /**
- * Our provider manager instances.
- */
- private static ProviderManager providerManager = null;
-
- /**
- * Lock for creating provider.
- */
- private static Object providerCreationLock = new Object();
-
- /**
- * State for connect and login state.
- */
- enum ConnectState
- {
- /**
- * Abort any further connecting.
- */
- ABORT_CONNECTING,
- /**
- * Continue trying with next address.
- */
- CONTINUE_TRYING,
- /**
- * Stop trying we succeeded or just have a final state for
- * the whole connecting procedure.
- */
- STOP_TRYING
- }
-
- /**
- * The debugger who logs packets.
- */
- private SmackPacketDebugger debugger = null;
-
- /**
- * Jingle Nodes service.
- */
- private SmackServiceNode jingleNodesServiceNode = null;
-
- /**
- * Synchronization object to monitore jingle nodes auto discovery.
- */
- private final Object jingleNodesSyncRoot = new Object();
-
- /**
- * Stores user credentials for local use if user hasn't stored
- * its password.
- */
- private UserCredentials userCredentials = null;
-
- /**
- * The currently running keepAliveManager if enabled.
- */
- private KeepAliveManager keepAliveManager = null;
-
- /**
- * The version manager.
- */
- private VersionManager versionManager = null;
-
- // load xmpp manager classes
- static
- {
- if(OSUtils.IS_ANDROID)
- loadJabberServiceClasses();
- }
-
- /**
- * Returns the state of the registration of this protocol provider
- * @return the RegistrationState that this provider is
- * currently in or null in case it is in a unknown state.
- */
- public RegistrationState getRegistrationState()
- {
- if(connection == null)
- return RegistrationState.UNREGISTERED;
- else if(connection.isConnected() && connection.isAuthenticated())
- return RegistrationState.REGISTERED;
- else
- return RegistrationState.UNREGISTERED;
- }
-
- /**
- * Return the certificate verification service impl.
- * @return the CertificateVerification service.
- */
- private CertificateService getCertificateVerificationService()
- {
- if(guiVerification == null)
- {
- ServiceReference guiVerifyReference
- = JabberActivator.getBundleContext().getServiceReference(
- CertificateService.class.getName());
- if(guiVerifyReference != null)
- guiVerification = (CertificateService)
- JabberActivator.getBundleContext().getService(
- guiVerifyReference);
- }
-
- return guiVerification;
- }
-
- /**
- * Starts the registration process. Connection details such as
- * registration server, user name/number are provided through the
- * configuration service through implementation specific properties.
- *
- * @param authority the security authority that will be used for resolving
- * any security challenges that may be returned during the
- * registration or at any moment while we're registered.
- * @throws OperationFailedException with the corresponding code it the
- * registration fails for some reason (e.g. a networking error or an
- * implementation problem).
- */
- public void register(final SecurityAuthority authority)
- throws OperationFailedException
- {
- if(authority == null)
- throw new IllegalArgumentException(
- "The register method needs a valid non-null authority impl "
- + " in order to be able and retrieve passwords.");
-
- this.authority = authority;
-
- try
- {
- // reset states
- abortConnecting = false;
-
- // indicate we started connectAndLogin process
- synchronized(connectAndLoginLock)
- {
- inConnectAndLogin = true;
- }
-
- initializeConnectAndLogin(authority,
- SecurityAuthority.AUTHENTICATION_REQUIRED);
- }
- catch (XMPPException ex)
- {
- logger.error("Error registering", ex);
-
- eventDuringLogin = null;
-
- fireRegistrationStateChanged(ex);
- }
- finally
- {
- synchronized(connectAndLoginLock)
- {
- // Checks if an error has occurred during login, if so we fire
- // it here in order to avoid a deadlock which occurs in
- // reconnect plugin. The deadlock is cause we fired an event
- // during login process and have locked initializationLock and
- // we cannot unregister from reconnect, cause unregister method
- // also needs this lock.
- if(eventDuringLogin != null)
- {
- if(eventDuringLogin.getNewState().equals(
- RegistrationState.CONNECTION_FAILED) ||
- eventDuringLogin.getNewState().equals(
- RegistrationState.UNREGISTERED))
- disconnectAndCleanConnection();
-
- fireRegistrationStateChanged(
- eventDuringLogin.getOldState(),
- eventDuringLogin.getNewState(),
- eventDuringLogin.getReasonCode(),
- eventDuringLogin.getReason());
-
- eventDuringLogin = null;
- inConnectAndLogin = false;
- return;
- }
-
- inConnectAndLogin = false;
- }
- }
- }
-
- /**
- * Connects and logins again to the server.
- *
- * @param authReasonCode indicates the reason of the re-authentication.
- */
- void reregister(int authReasonCode)
- {
- try
- {
- if (logger.isTraceEnabled())
- logger.trace("Trying to reregister us!");
-
- // sets this if any is trying to use us through registration
- // to know we are not registered
- this.unregister(false);
-
- // reset states
- this.abortConnecting = false;
-
- // indicate we started connectAndLogin process
- synchronized(connectAndLoginLock)
- {
- inConnectAndLogin = true;
- }
-
- initializeConnectAndLogin(authority, authReasonCode);
- }
- catch(OperationFailedException ex)
- {
- logger.error("Error ReRegistering", ex);
-
- eventDuringLogin = null;
-
- disconnectAndCleanConnection();
-
- fireRegistrationStateChanged(getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null);
- }
- catch (XMPPException ex)
- {
- logger.error("Error ReRegistering", ex);
-
- eventDuringLogin = null;
-
- fireRegistrationStateChanged(ex);
- }
- finally
- {
- synchronized(connectAndLoginLock)
- {
- // Checks if an error has occurred during login, if so we fire
- // it here in order to avoid a deadlock which occurs in
- // reconnect plugin. The deadlock is cause we fired an event
- // during login process and have locked initializationLock and
- // we cannot unregister from reconnect, cause unregister method
- // also needs this lock.
- if(eventDuringLogin != null)
- {
- if(eventDuringLogin.getNewState().equals(
- RegistrationState.CONNECTION_FAILED) ||
- eventDuringLogin.getNewState().equals(
- RegistrationState.UNREGISTERED))
- disconnectAndCleanConnection();
-
- fireRegistrationStateChanged(
- eventDuringLogin.getOldState(),
- eventDuringLogin.getNewState(),
- eventDuringLogin.getReasonCode(),
- eventDuringLogin.getReason());
-
- eventDuringLogin = null;
- inConnectAndLogin = false;
- return;
- }
-
- inConnectAndLogin = false;
- }
- }
- }
-
- /**
- * Indicates if the XMPP transport channel is using a TLS secured socket.
- *
- * @return True when TLS is used, false otherwise.
- */
- public boolean isSignalingTransportSecure()
- {
- return connection != null && connection.isUsingTLS();
- }
-
- /**
- * Returns the "transport" protocol of this instance used to carry the
- * control channel for the current protocol service.
- *
- * @return The "transport" protocol of this instance: TCP, TLS or UNKNOWN.
- */
- public TransportProtocol getTransportProtocol()
- {
- // Without a connection, there is no transport available.
- if(connection != null && connection.isConnected())
- {
- // Transport using a secure connection.
- if(connection.isUsingTLS())
- {
- return TransportProtocol.TLS;
- }
- // Transport using a unsecure connection.
- return TransportProtocol.TCP;
- }
- return TransportProtocol.UNKNOWN;
- }
-
- /**
- * Connects and logins to the server
- * @param authority SecurityAuthority
- * @param reasonCode the authentication reason code. Indicates the reason of
- * this authentication.
- * @throws XMPPException if we cannot connect to the server - network problem
- * @throws OperationFailedException if login parameters
- * as server port are not correct
- */
- private void initializeConnectAndLogin(SecurityAuthority authority,
- int reasonCode)
- throws XMPPException, OperationFailedException
- {
- synchronized(initializationLock)
- {
- // if a thread is waiting for initializationLock and enters
- // lets check whether someone hasn't already tried login and
- // have succeeded,
- // should prevent "Trace possible duplicate connections" prints
- if(isRegistered())
- return;
-
- JabberLoginStrategy loginStrategy = createLoginStrategy();
- userCredentials = loginStrategy.prepareLogin(authority, reasonCode);
- if(!loginStrategy.loginPreparationSuccessful())
- return;
-
- String serviceName
- = StringUtils.parseServer(getAccountID().getUserID());
-
- loadResource();
- loadProxy();
- Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
-
- ConnectState state;
- //[0] = hadDnsSecException
- boolean[] hadDnsSecException = new boolean[]{false};
-
- // try connecting with auto-detection if enabled
- boolean isServerOverriden =
- getAccountID().getAccountPropertyBoolean(
- ProtocolProviderFactory.IS_SERVER_OVERRIDDEN, false);
-
- if(!isServerOverriden)
- {
- state = connectUsingSRVRecords(serviceName,
- serviceName, hadDnsSecException, loginStrategy);
- if(hadDnsSecException[0])
- {
- setDnssecLoginFailure();
- return;
- }
- if(state == ConnectState.ABORT_CONNECTING
- || state == ConnectState.STOP_TRYING)
- return;
- }
-
- // check for custom xmpp domain which we will check for
- // SRV records for server addresses
- String customXMPPDomain = getAccountID()
- .getAccountPropertyString("CUSTOM_XMPP_DOMAIN");
-
- if(customXMPPDomain != null && !hadDnsSecException[0])
- {
- logger.info("Connect using custom xmpp domain: " +
- customXMPPDomain);
-
- state = connectUsingSRVRecords(
- customXMPPDomain, serviceName,
- hadDnsSecException, loginStrategy);
-
- logger.info("state for connectUsingSRVRecords: " + state);
-
- if(hadDnsSecException[0])
- {
- setDnssecLoginFailure();
- return;
- }
-
- if(state == ConnectState.ABORT_CONNECTING
- || state == ConnectState.STOP_TRYING)
- return;
- }
-
- // connect with specified server name
- String serverAddressUserSetting
- = getAccountID().getAccountPropertyString(
- ProtocolProviderFactory.SERVER_ADDRESS);
-
- int serverPort = getAccountID().getAccountPropertyInt(
- ProtocolProviderFactory.SERVER_PORT, 5222);
-
- InetSocketAddress[] addrs = null;
- try
- {
- addrs = NetworkUtils.getAandAAAARecords(
- serverAddressUserSetting,
- serverPort
- );
- }
- catch (ParseException e)
- {
- logger.error("Domain not resolved", e);
- }
- catch (DnssecException e)
- {
- logger.error("DNSSEC failure for overridden server", e);
- setDnssecLoginFailure();
- return;
- }
-
- if (addrs == null || addrs.length == 0)
- {
- logger.error("No server addresses found");
-
- eventDuringLogin = null;
-
- fireRegistrationStateChanged(
- getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
- "No server addresses found");
- }
- else
- {
- for (InetSocketAddress isa : addrs)
- {
- try
- {
- state = connectAndLogin(isa, serviceName,
- loginStrategy);
- if(state == ConnectState.ABORT_CONNECTING
- || state == ConnectState.STOP_TRYING)
- return;
- }
- catch(XMPPException ex)
- {
- disconnectAndCleanConnection();
- if(isAuthenticationFailed(ex))
- throw ex;
- }
- }
- }
- }
- }
-
- /**
- * Creates the JabberLoginStrategy to use for the current account.
- */
- private JabberLoginStrategy createLoginStrategy()
- {
- String clientCertId = getAccountID().getAccountPropertyString(
- ProtocolProviderFactory.CLIENT_TLS_CERTIFICATE);
- if(clientCertId != null)
- {
- return new LoginByClientCertificateStrategy(getAccountID());
- }
- else
- {
- return new LoginByPasswordStrategy(this, getAccountID());
- }
- }
-
- private void setDnssecLoginFailure()
- {
- eventDuringLogin = new RegistrationStateChangeEvent(
- this,
- getRegistrationState(),
- RegistrationState.UNREGISTERED,
- RegistrationStateChangeEvent.REASON_USER_REQUEST,
- "No usable host found due to DNSSEC failures");
- }
-
- /**
- * Connects using the domain specified and its SRV records.
- * @param domain the domain to use
- * @param serviceName the domain name of the user's login
- * @param dnssecState state of possible received DNSSEC exceptions
- * @param loginStrategy the login strategy to use
- * @return whether to continue trying or stop.
- */
- private ConnectState connectUsingSRVRecords(
- String domain,
- String serviceName,
- boolean[] dnssecState,
- JabberLoginStrategy loginStrategy)
- throws XMPPException
- {
- // check to see is there SRV records for this server domain
- SRVRecord srvRecords[] = null;
- try
- {
- srvRecords = NetworkUtils
- .getSRVRecords("xmpp-client", "tcp", domain);
- }
- catch (ParseException e)
- {
- logger.error("SRV record not resolved", e);
- }
- catch (DnssecException e)
- {
- logger.error("DNSSEC failure for SRV lookup", e);
- dnssecState[0] = true;
- }
-
- if(srvRecords != null)
- {
- for(SRVRecord srv : srvRecords)
- {
- InetSocketAddress[] addrs = null;
- try
- {
- addrs =
- NetworkUtils.getAandAAAARecords(
- srv.getTarget(),
- srv.getPort()
- );
- }
- catch (ParseException e)
- {
- logger.error("Invalid SRV record target", e);
- }
- catch (DnssecException e)
- {
- logger.error("DNSSEC failure for A/AAAA lookup of SRV", e);
- dnssecState[0] = true;
- }
-
- if (addrs == null || addrs.length == 0)
- {
- logger.error("No A/AAAA addresses found for " +
- srv.getTarget());
- continue;
- }
-
- for (InetSocketAddress isa : addrs)
- {
- try
- {
- // if failover mechanism is enabled, use it,
- // default is not enabled.
- if(JabberActivator.getConfigurationService()
- .getBoolean(FailoverConnectionMonitor
- .REVERSE_FAILOVER_ENABLED_PROP,
- false
- ))
- {
- FailoverConnectionMonitor.getInstance(this)
- .setCurrent(domain, srv.getTarget());
- }
-
- ConnectState state = connectAndLogin(
- isa, serviceName, loginStrategy);
- return state;
- }
- catch(XMPPException ex)
- {
- logger.error("Error connecting to " + isa
- + " for domain:" + domain
- + " serviceName:" + serviceName, ex);
-
- disconnectAndCleanConnection();
-
- if(isAuthenticationFailed(ex))
- throw ex;
- }
- }
- }
- }
- else
- logger.error("No SRV addresses found for _xmpp-client._tcp."
- + domain);
-
- return ConnectState.CONTINUE_TRYING;
- }
-
- /**
- * Tries to login to the XMPP server with the supplied user ID. If the
- * protocol is Google Talk, the user ID including the service name is used.
- * For other protocols, if the login with the user ID without the service
- * name fails, a second attempt including the service name is made.
- *
- * @param currentAddress the IP address to connect to
- * @param serviceName the domain name of the user's login
- * @param loginStrategy the login strategy to use
- * @throws XMPPException when a failure occurs
- */
- private ConnectState connectAndLogin(InetSocketAddress currentAddress,
- String serviceName,
- JabberLoginStrategy loginStrategy)
- throws XMPPException
- {
- String userID = null;
- boolean qualifiedUserID;
-
- /* with a google account (either gmail or google apps
- * related ones), the userID MUST be the full e-mail address
- * not just the ID
- */
- if(getAccountID().getProtocolDisplayName().equals("Google Talk"))
- {
- userID = getAccountID().getUserID();
- qualifiedUserID = true;
- }
- else
- {
- userID = StringUtils.parseName(getAccountID().getUserID());
- qualifiedUserID = false;
- }
-
- try
- {
- return connectAndLogin(
- currentAddress, serviceName,
- userID, resource, loginStrategy);
- }
- catch(XMPPException ex)
- {
- // server disconnect us after such an error, do cleanup
- disconnectAndCleanConnection();
-
- //no need to check with a different username if the
- //socket could not be opened
- if (ex.getWrappedThrowable() instanceof ConnectException
- || ex.getWrappedThrowable() instanceof NoRouteToHostException)
- {
- //as we got an exception not handled in connectAndLogin
- //no state was set, so fire it here so we can continue
- //with the re-register process
- //2013-08-07 do not fire event, if we have several
- // addresses and we fire event will activate reconnect
- // but we will continue connecting with other addresses
- // and can register with address, then unregister and try again
- // that is from reconnect plugin.
- // Storing event for fire after all have failed and we have
- // tried every address.
- eventDuringLogin = new RegistrationStateChangeEvent(
- ProtocolProviderServiceJabberImpl.this,
- getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
- null);
-
- throw ex;
- }
-
- // don't attempt to append the service name if it's already there
- if (!qualifiedUserID)
- {
- try
- {
- // logging in might need the service name
- return connectAndLogin(
- currentAddress, serviceName,
- userID + "@" + serviceName,
- resource,
- loginStrategy);
- }
- catch(XMPPException ex2)
- {
- disconnectAndCleanConnection();
- throw ex; //throw the original exception
- }
- }
- else
- throw ex;
- }
- }
-
- /**
- * Initializes the Jabber Resource identifier.
- */
- private void loadResource()
- {
- if(resource == null)
- {
- String defaultResource = "jitsi";
- String autoGenenerateResource =
- getAccountID().getAccountPropertyString(
- ProtocolProviderFactory.AUTO_GENERATE_RESOURCE);
- if(autoGenenerateResource == null ||
- Boolean.parseBoolean(autoGenenerateResource))
- {
- SecureRandom random = new SecureRandom();
-
- resource = defaultResource + "-" +
- new BigInteger(32, random).toString(32);
- }
- else
- {
- resource = getAccountID().getAccountPropertyString(
- ProtocolProviderFactory.RESOURCE);
-
- if(resource == null || resource.length() == 0)
- resource = defaultResource;
- }
- }
- }
-
- /**
- * Sets the global proxy information based on the configuration
- *
- * @throws OperationFailedException
- */
- private void loadProxy() throws OperationFailedException
- {
- String globalProxyType =
- JabberActivator.getConfigurationService()
- .getString(ProxyInfo.CONNECTION_PROXY_TYPE_PROPERTY_NAME);
- if(globalProxyType == null ||
- globalProxyType.equals(ProxyInfo.ProxyType.NONE.name()))
- {
- proxy = org.jivesoftware.smack.proxy.ProxyInfo.forNoProxy();
- }
- else
- {
- String globalProxyAddress =
- JabberActivator.getConfigurationService().getString(
- ProxyInfo.CONNECTION_PROXY_ADDRESS_PROPERTY_NAME);
- String globalProxyPortStr =
- JabberActivator.getConfigurationService().getString(
- ProxyInfo.CONNECTION_PROXY_PORT_PROPERTY_NAME);
- int globalProxyPort;
- try
- {
- globalProxyPort = Integer.parseInt(
- globalProxyPortStr);
- }
- catch(NumberFormatException ex)
- {
- throw new OperationFailedException("Wrong proxy port, "
- + globalProxyPortStr
- + " does not represent an integer",
- OperationFailedException.INVALID_ACCOUNT_PROPERTIES,
- ex);
- }
- String globalProxyUsername =
- JabberActivator.getConfigurationService().getString(
- ProxyInfo.CONNECTION_PROXY_USERNAME_PROPERTY_NAME);
- String globalProxyPassword =
- JabberActivator.getConfigurationService().getString(
- ProxyInfo.CONNECTION_PROXY_PASSWORD_PROPERTY_NAME);
- if(globalProxyAddress == null ||
- globalProxyAddress.length() <= 0)
- {
- throw new OperationFailedException(
- "Missing Proxy Address",
- OperationFailedException.INVALID_ACCOUNT_PROPERTIES);
- }
- try
- {
- proxy = new org.jivesoftware.smack.proxy.ProxyInfo(
- Enum.valueOf(org.jivesoftware.smack.proxy.ProxyInfo.
- ProxyType.class, globalProxyType),
- globalProxyAddress, globalProxyPort,
- globalProxyUsername, globalProxyPassword);
- }
- catch(IllegalArgumentException e)
- {
- logger.error("Invalid value for smack proxy enum", e);
- proxy = null;
- }
- }
- }
-
- /**
- * Connects xmpp connection and login. Returning the state whether is it
- * final - Abort due to certificate cancel or keep trying cause only current
- * address has failed or stop trying cause we succeeded.
- * @param address the address to connect to
- * @param serviceName the service name to use
- * @param userName the username to use
- * @param resource and the resource.
- * @param loginStrategy the login strategy to use
- * @return return the state how to continue the connect process.
- * @throws XMPPException if we cannot connect for some reason
- */
- private ConnectState connectAndLogin(
- InetSocketAddress address, String serviceName,
- String userName, String resource,
- JabberLoginStrategy loginStrategy)
- throws XMPPException
- {
- ConnectionConfiguration confConn = new ConnectionConfiguration(
- address.getAddress().getHostAddress(),
- address.getPort(),
- serviceName, proxy
- );
-
- confConn.setReconnectionAllowed(false);
- boolean tlsRequired = loginStrategy.isTlsRequired();
-
- // user have the possibility to disable TLS but in this case, it will
- // not be able to connect to a server which requires TLS
- confConn.setSecurityMode(
- tlsRequired ? ConnectionConfiguration.SecurityMode.required :
- ConnectionConfiguration.SecurityMode.enabled);
-
- if(connection != null)
- {
- logger.error("Connection is not null and isConnected:"
- + connection.isConnected(),
- new Exception("Trace possible duplicate connections: " +
- getAccountID().getAccountAddress()));
- disconnectAndCleanConnection();
- }
-
- connection = new XMPPConnection(confConn);
-
- try
- {
- CertificateService cvs =
- getCertificateVerificationService();
- if(cvs != null)
- {
- SSLContext sslContext = loginStrategy.createSslContext(cvs,
- getTrustManager(cvs, serviceName));
-
- // log SSL/TLS algorithms and protocols
- if (logger.isDebugEnabled())
- {
- final StringBuilder buff = new StringBuilder();
- buff.append("Available TLS protocols and algorithms:\n");
- buff.append("Default protocols: ");
- buff.append(Arrays.toString(
- sslContext.getDefaultSSLParameters().getProtocols()));
- buff.append("\n");
- buff.append("Supported protocols: ");
- buff.append(Arrays.toString(
- sslContext.getSupportedSSLParameters().getProtocols()));
- buff.append("\n");
- buff.append("Default cipher suites: ");
- buff.append(Arrays.toString(
- sslContext.getDefaultSSLParameters()
- .getCipherSuites()));
- buff.append("\n");
- buff.append("Supported cipher suites: ");
- buff.append(Arrays.toString(
- sslContext.getSupportedSSLParameters()
- .getCipherSuites()));
- logger.debug(buff.toString());
- }
-
- connection.setCustomSslContext(sslContext);
- }
- else if (tlsRequired)
- throw new XMPPException(
- "Certificate verification service is "
- + "unavailable and TLS is required");
- }
- catch(GeneralSecurityException e)
- {
- logger.error("Error creating custom trust manager", e);
- throw new XMPPException("Error creating custom trust manager", e);
- }
-
- if(debugger == null)
- debugger = new SmackPacketDebugger();
-
- // sets the debugger
- debugger.setConnection(connection);
- connection.addPacketListener(debugger, null);
- connection.addPacketInterceptor(debugger, null);
-
- connection.connect();
-
- setTrafficClass();
-
- if(abortConnecting)
- {
- abortConnecting = false;
- disconnectAndCleanConnection();
-
- return ConnectState.ABORT_CONNECTING;
- }
-
- registerServiceDiscoveryManager();
-
- if(connectionListener == null)
- {
- connectionListener = new JabberConnectionListener();
- }
-
- if(!connection.isSecureConnection() && tlsRequired)
- {
- throw new XMPPException("TLS is required by client");
- }
-
- if(!connection.isConnected())
- {
- // connection is not connected, lets set state to our connection
- // as failed seems there is some lag/problem with network
- // and this way we will inform for it and later reconnect if needed
- // as IllegalStateException that is thrown within
- // addConnectionListener is not handled properly
- disconnectAndCleanConnection();
-
- logger.error("Connection not established, server not found!");
-
- eventDuringLogin = null;
-
- fireRegistrationStateChanged(getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, null);
-
- return ConnectState.ABORT_CONNECTING;
- }
- else
- {
- if (connection.getSocket() instanceof SSLSocket)
- {
- final SSLSocket sslSocket = (SSLSocket) connection.getSocket();
- StringBuilder buff = new StringBuilder();
- buff.append("Chosen TLS protocol and algorithm:\n")
- .append("Protocol: ").append(sslSocket.getSession()
- .getProtocol()).append("\n")
- .append("Cipher suite: ").append(sslSocket.getSession()
- .getCipherSuite());
- logger.info(buff.toString());
-
- if (logger.isDebugEnabled())
- {
- buff = new StringBuilder();
- buff.append("Server TLS certificate chain:\n");
- try
- {
- buff.append(Arrays.toString(
- sslSocket.getSession().getPeerCertificates()));
- }
- catch (SSLPeerUnverifiedException ex)
- {
- buff.append("");
- }
- logger.debug(buff.toString());
- }
- }
-
- connection.addConnectionListener(connectionListener);
- }
-
- if(abortConnecting)
- {
- abortConnecting = false;
- disconnectAndCleanConnection();
-
- return ConnectState.ABORT_CONNECTING;
- }
-
- fireRegistrationStateChanged(
- getRegistrationState()
- , RegistrationState.REGISTERING
- , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
- , null);
-
- if (!loginStrategy.login(connection, userName, resource))
- {
- disconnectAndCleanConnection();
- eventDuringLogin = null;
- fireRegistrationStateChanged(
- getRegistrationState(),
- // not auth failed, or there would be no info-popup
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED,
- loginStrategy.getClass().getName() + " requests abort");
-
- return ConnectState.ABORT_CONNECTING;
- }
-
- if(connection.isAuthenticated())
- {
- eventDuringLogin = null;
-
- fireRegistrationStateChanged(
- getRegistrationState(),
- RegistrationState.REGISTERED,
- RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null);
-
- return ConnectState.STOP_TRYING;
- }
- else
- {
- disconnectAndCleanConnection();
-
- eventDuringLogin = null;
-
- fireRegistrationStateChanged(
- getRegistrationState()
- , RegistrationState.UNREGISTERED
- , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
- , null);
-
- return ConnectState.CONTINUE_TRYING;
- }
- }
-
- /**
- * Gets the TrustManager that should be used for the specified service
- *
- * @param serviceName the service name
- * @param cvs The CertificateVerificationService to retrieve the
- * trust manager
- * @return the trust manager
- */
- private X509TrustManager getTrustManager(CertificateService cvs,
- String serviceName)
- throws GeneralSecurityException
- {
- return new HostTrustManager(
- cvs.getTrustManager(
- Arrays.asList(new String[]{
- serviceName,
- "_xmpp-client." + serviceName
- })
- )
- );
- }
-
- /**
- * Registers our ServiceDiscoveryManager
- */
- private void registerServiceDiscoveryManager()
- {
- // we setup supported features no packets are actually sent
- //during feature registration so we'd better do it here so that
- //our first presence update would contain a caps with the right
- //features.
- String name
- = System.getProperty(
- "sip-communicator.application.name",
- "SIP Communicator ")
- + System.getProperty("sip-communicator.version","SVN");
-
- ServiceDiscoveryManager.setIdentityName(name);
- ServiceDiscoveryManager.setIdentityType("pc");
-
- discoveryManager
- = new ScServiceDiscoveryManager(
- this,
- new String[] { "http://jabber.org/protocol/commands"},
- // Add features Jitsi supports in addition to smack.
- supportedFeatures.toArray(
- new String[supportedFeatures.size()]));
-
- boolean isCallingDisabled
- = JabberActivator.getConfigurationService()
- .getBoolean(IS_CALLING_DISABLED, false);
-
- boolean isCallingDisabledForAccount = false;
- if (accountID != null && accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.IS_CALLING_DISABLED_FOR_ACCOUNT,
- false))
- isCallingDisabled = true;
-
- if(isGTalkTesting()
- && !isCallingDisabled
- && !isCallingDisabledForAccount)
- {
- // Add Google Talk "ext" capabilities
- discoveryManager.addExtFeature(CAPS_GTALK_WEB_VOICE);
- discoveryManager.addExtFeature(CAPS_GTALK_WEB_VIDEO);
- discoveryManager.addExtFeature(CAPS_GTALK_WEB_CAMERA);
- discoveryManager.addFeature(URN_GOOGLE_VOICE);
- discoveryManager.addFeature(URN_GOOGLE_VIDEO);
- discoveryManager.addFeature(URN_GOOGLE_CAMERA);
- }
-
- /*
- * Expose the discoveryManager as service-public through the
- * OperationSetContactCapabilities of this ProtocolProviderService.
- */
- if (opsetContactCapabilities != null)
- opsetContactCapabilities.setDiscoveryManager(discoveryManager);
- }
-
- /**
- * Used to disconnect current connection and clean it.
- */
- public void disconnectAndCleanConnection()
- {
- if(connection != null)
- {
- connection.removeConnectionListener(connectionListener);
-
- // disconnect anyway cause it will clear any listeners
- // that maybe added even if its not connected
- try
- {
- OperationSetPersistentPresenceJabberImpl opSet =
- (OperationSetPersistentPresenceJabberImpl)
- this.getOperationSet(OperationSetPersistentPresence.class);
-
- Presence unavailablePresence =
- new Presence(Presence.Type.unavailable);
-
- if(opSet != null
- && !org.jitsi.util.StringUtils
- .isNullOrEmpty(opSet.getCurrentStatusMessage()))
- {
- unavailablePresence.setStatus(
- opSet.getCurrentStatusMessage());
- }
-
- connection.disconnect(unavailablePresence);
- } catch (Exception e)
- {}
-
- connectionListener = null;
- connection = null;
- // make it null as it also holds a reference to the old connection
- // will be created again on new connection
- try
- {
- /*
- * The discoveryManager is exposed as service-public by the
- * OperationSetContactCapabilities of this
- * ProtocolProviderService. No longer expose it because it's
- * going away.
- */
- if (opsetContactCapabilities != null)
- opsetContactCapabilities.setDiscoveryManager(null);
- }
- finally
- {
- if(discoveryManager != null)
- {
- discoveryManager.stop();
- discoveryManager = null;
- }
- }
- }
- }
-
- /**
- * Ends the registration of this protocol provider with the service.
- */
- public void unregister()
- {
- unregister(true);
- }
-
- /**
- * Unregister and fire the event if requested
- * @param fireEvent boolean
- */
- public void unregister(boolean fireEvent)
- {
- synchronized(initializationLock)
- {
- if(fireEvent)
- {
- eventDuringLogin = null;
- fireRegistrationStateChanged(
- getRegistrationState()
- , RegistrationState.UNREGISTERING
- , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
- , null);
- }
-
- disconnectAndCleanConnection();
-
- RegistrationState currRegState = getRegistrationState();
-
- if(fireEvent)
- {
- eventDuringLogin = null;
- fireRegistrationStateChanged(
- currRegState,
- RegistrationState.UNREGISTERED,
- RegistrationStateChangeEvent.REASON_USER_REQUEST, null);
- }
- }
- }
-
- /**
- * Returns the short name of the protocol that the implementation of this
- * provider is based upon (like SIP, Jabber, ICQ/AIM, or others for
- * example).
- *
- * @return a String containing the short name of the protocol this
- * service is taking care of.
- */
- public String getProtocolName()
- {
- return ProtocolNames.JABBER;
- }
-
- /**
- * Initialized the service implementation, and puts it in a sate where it
- * could interoperate with other services. It is strongly recommended that
- * properties in this Map be mapped to property names as specified by
- * AccountProperties.
- *
- * @param screenname the account id/uin/screenname of the account that
- * we're about to create
- * @param accountID the identifier of the account that this protocol
- * provider represents.
- *
- * @see net.java.sip.communicator.service.protocol.AccountID
- */
- protected void initialize(String screenname,
- AccountID accountID)
- {
- synchronized(initializationLock)
- {
- this.accountID = accountID;
-
- // in case of modified account, we clear list of supported features
- // and every state change listeners, otherwise we can have two
- // OperationSet for same feature and it can causes problem (i.e.
- // two OperationSetBasicTelephony can launch two ICE negotiations
- // (with different ufrag/pwd) and peer will failed call. And
- // by the way user will see two dialog for answering/refusing the
- // call
- supportedFeatures.clear();
- this.clearRegistrationStateChangeListener();
- this.clearSupportedOperationSet();
-
- synchronized(providerCreationLock)
- {
- if(providerManager == null)
- {
- try
- {
- ProviderManager.setInstance(new ProviderManagerExt());
- }
- catch(Throwable t)
- {
- // once loaded if we try to set instance second time
- // IllegalStateException is thrown
- }
- finally
- {
- providerManager = ProviderManager.getInstance();
- }
- }
- }
-
- String protocolIconPath
- = accountID.getAccountPropertyString(
- ProtocolProviderFactory.PROTOCOL_ICON_PATH);
-
- if (protocolIconPath == null)
- protocolIconPath = "resources/images/protocol/jabber";
-
- jabberIcon = new ProtocolIconJabberImpl(protocolIconPath);
-
- jabberStatusEnum
- = JabberStatusEnum.getJabberStatusEnum(protocolIconPath);
-
- //this feature is mandatory to be compliant with Service Discovery
- supportedFeatures.add("http://jabber.org/protocol/disco#info");
-
- String keepAliveStrValue
- = accountID.getAccountPropertyString(
- ProtocolProviderFactory.KEEP_ALIVE_METHOD);
-
- InfoRetreiver infoRetreiver = new InfoRetreiver(this, screenname);
-
- //initialize the presence OperationSet
- OperationSetPersistentPresenceJabberImpl persistentPresence =
- new OperationSetPersistentPresenceJabberImpl(this, infoRetreiver);
-
- addSupportedOperationSet(
- OperationSetPersistentPresence.class,
- persistentPresence);
- // TODO: add the feature, if any, corresponding to persistent
- // presence, if someone knows
- // supportedFeatures.add(_PRESENCE_);
-
- //register it once again for those that simply need presence
- addSupportedOperationSet(
- OperationSetPresence.class,
- persistentPresence);
-
- if(accountID.getAccountPropertyString(
- ProtocolProviderFactory.ACCOUNT_READ_ONLY_GROUPS) != null)
- {
- addSupportedOperationSet(
- OperationSetPersistentPresencePermissions.class,
- new OperationSetPersistentPresencePermissionsJabberImpl(
- this));
- }
-
- //initialize the IM operation set
- OperationSetBasicInstantMessagingJabberImpl basicInstantMessaging =
- new OperationSetBasicInstantMessagingJabberImpl(this);
-
- if (keepAliveStrValue == null
- || keepAliveStrValue.equalsIgnoreCase("XEP-0199"))
- {
- if(keepAliveManager == null)
- keepAliveManager = new KeepAliveManager(this);
- }
-
- addSupportedOperationSet(
- OperationSetBasicInstantMessaging.class,
- basicInstantMessaging);
-
- // The http://jabber.org/protocol/xhtml-im feature is included
- // already in smack.
-
- addSupportedOperationSet(
- OperationSetExtendedAuthorizations.class,
- new OperationSetExtendedAuthorizationsJabberImpl(
- this,
- persistentPresence));
-
- //initialize the Whiteboard operation set
- addSupportedOperationSet(
- OperationSetWhiteboarding.class,
- new OperationSetWhiteboardingJabberImpl(this));
-
- //initialize the typing notifications operation set
- addSupportedOperationSet(
- OperationSetTypingNotifications.class,
- new OperationSetTypingNotificationsJabberImpl(this));
-
- // The http://jabber.org/protocol/chatstates feature implemented in
- // OperationSetTypingNotifications is included already in smack.
-
- //initialize the multi user chat operation set
- addSupportedOperationSet(
- OperationSetMultiUserChat.class,
- new OperationSetMultiUserChatJabberImpl(this));
-
- addSupportedOperationSet(
- OperationSetServerStoredContactInfo.class,
- new OperationSetServerStoredContactInfoJabberImpl(
- infoRetreiver));
-
- OperationSetServerStoredAccountInfo accountInfo =
- new OperationSetServerStoredAccountInfoJabberImpl(this,
- infoRetreiver,
- screenname);
-
- addSupportedOperationSet(
- OperationSetServerStoredAccountInfo.class,
- accountInfo);
-
- // Initialize avatar operation set
- addSupportedOperationSet(
- OperationSetAvatar.class,
- new OperationSetAvatarJabberImpl(this, accountInfo));
-
- // initialize the file transfer operation set
- addSupportedOperationSet(
- OperationSetFileTransfer.class,
- new OperationSetFileTransferJabberImpl(this));
-
- addSupportedOperationSet(
- OperationSetInstantMessageTransform.class,
- new OperationSetInstantMessageTransformImpl());
-
- // Include features we're supporting in addition to the four
- // included by smack itself:
- // http://jabber.org/protocol/si/profile/file-transfer
- // http://jabber.org/protocol/si
- // http://jabber.org/protocol/bytestreams
- // http://jabber.org/protocol/ibb
- supportedFeatures.add("urn:xmpp:thumbs:0");
- supportedFeatures.add("urn:xmpp:bob");
-
- // initialize the thumbnailed file factory operation set
- addSupportedOperationSet(
- OperationSetThumbnailedFileFactory.class,
- new OperationSetThumbnailedFileFactoryImpl());
-
- // TODO: this is the "main" feature to advertise when a client
- // support muc. We have to add some features for
- // specific functionality we support in muc.
- // see http://www.xmpp.org/extensions/xep-0045.html
-
- // The http://jabber.org/protocol/muc feature is already included in
- // smack.
- supportedFeatures.add("http://jabber.org/protocol/muc#rooms");
- supportedFeatures.add("http://jabber.org/protocol/muc#traffic");
-
- // RTP HDR extension
- supportedFeatures.add(URN_XMPP_JINGLE_RTP_HDREXT);
-
- //register our jingle provider
- providerManager.addIQProvider( JingleIQ.ELEMENT_NAME,
- JingleIQ.NAMESPACE,
- new JingleIQProvider());
-
- // register our input event provider
- providerManager.addIQProvider(InputEvtIQ.ELEMENT_NAME,
- InputEvtIQ.NAMESPACE,
- new InputEvtIQProvider());
-
- // register our coin provider
- providerManager.addIQProvider(CoinIQ.ELEMENT_NAME,
- CoinIQ.NAMESPACE,
- new CoinIQProvider());
- supportedFeatures.add(URN_XMPP_JINGLE_COIN);
-
- // register our JingleInfo provider
- providerManager.addIQProvider(JingleInfoQueryIQ.ELEMENT_NAME,
- JingleInfoQueryIQ.NAMESPACE,
- new JingleInfoQueryIQProvider());
-
- // Jitsi Videobridge IQProvider and PacketExtensionProvider
- providerManager.addIQProvider(
- ColibriConferenceIQ.ELEMENT_NAME,
- ColibriConferenceIQ.NAMESPACE,
- new ColibriIQProvider());
-
- providerManager.addExtensionProvider(
- ConferenceDescriptionPacketExtension.ELEMENT_NAME,
- ConferenceDescriptionPacketExtension.NAMESPACE,
- new ConferenceDescriptionPacketExtension.Provider());
-
- providerManager.addExtensionProvider(
- CarbonPacketExtension.RECEIVED_ELEMENT_NAME,
- CarbonPacketExtension.NAMESPACE,
- new CarbonPacketExtension.Provider(
- CarbonPacketExtension.RECEIVED_ELEMENT_NAME));
-
- providerManager.addExtensionProvider(
- CarbonPacketExtension.SENT_ELEMENT_NAME,
- CarbonPacketExtension.NAMESPACE,
- new CarbonPacketExtension.Provider(
- CarbonPacketExtension.SENT_ELEMENT_NAME));
-
- //initialize the telephony operation set
- boolean isCallingDisabled
- = JabberActivator.getConfigurationService()
- .getBoolean(IS_CALLING_DISABLED, false);
-
- boolean isCallingDisabledForAccount
- = accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.IS_CALLING_DISABLED_FOR_ACCOUNT,
- false);
-
- // Check if calling is enabled.
- if (!isCallingDisabled && !isCallingDisabledForAccount)
- {
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = new OperationSetBasicTelephonyJabberImpl(this);
-
- addSupportedOperationSet(
- OperationSetAdvancedTelephony.class,
- basicTelephony);
- addSupportedOperationSet(
- OperationSetBasicTelephony.class,
- basicTelephony);
- addSupportedOperationSet(
- OperationSetSecureZrtpTelephony.class,
- basicTelephony);
- addSupportedOperationSet(
- OperationSetSecureSDesTelephony.class,
- basicTelephony);
-
- // initialize video telephony OperationSet
- addSupportedOperationSet(
- OperationSetVideoTelephony.class,
- new OperationSetVideoTelephonyJabberImpl(basicTelephony));
-
- addSupportedOperationSet(
- OperationSetTelephonyConferencing.class,
- new OperationSetTelephonyConferencingJabberImpl(this));
-
- addSupportedOperationSet(
- OperationSetBasicAutoAnswer.class,
- new OperationSetAutoAnswerJabberImpl(this));
-
- addSupportedOperationSet(
- OperationSetResourceAwareTelephony.class,
- new OperationSetResAwareTelephonyJabberImpl(basicTelephony));
-
- // Only init video bridge if enabled
- boolean isVideobridgeDisabled
- = JabberActivator.getConfigurationService()
- .getBoolean(OperationSetVideoBridge.
- IS_VIDEO_BRIDGE_DISABLED, false);
-
- if (!isVideobridgeDisabled)
- {
- // init video bridge
- addSupportedOperationSet(
- OperationSetVideoBridge.class,
- new OperationSetVideoBridgeImpl(this));
- }
-
- // init DTMF
- OperationSetDTMFJabberImpl operationSetDTMFSip
- = new OperationSetDTMFJabberImpl(this);
- addSupportedOperationSet(
- OperationSetDTMF.class, operationSetDTMFSip);
-
- addJingleFeatures();
-
- // Check if desktop streaming is enabled.
- boolean isDesktopStreamingDisabled
- = JabberActivator.getConfigurationService()
- .getBoolean(IS_DESKTOP_STREAMING_DISABLED, false);
-
- boolean isAccountDesktopStreamingDisabled
- = accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.IS_DESKTOP_STREAMING_DISABLED,
- false);
-
- if (!isDesktopStreamingDisabled
- && !isAccountDesktopStreamingDisabled)
- {
- // initialize desktop streaming OperationSet
- addSupportedOperationSet(
- OperationSetDesktopStreaming.class,
- new OperationSetDesktopStreamingJabberImpl(
- basicTelephony));
-
- if(!accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory
- .IS_DESKTOP_REMOTE_CONTROL_DISABLED,
- false))
- {
- // initialize desktop sharing OperationSets
- addSupportedOperationSet(
- OperationSetDesktopSharingServer.class,
- new OperationSetDesktopSharingServerJabberImpl(
- basicTelephony));
-
- // Adds extension to support remote control as a sharing
- // server (sharer).
- supportedFeatures.add(InputEvtIQ.NAMESPACE_SERVER);
-
- addSupportedOperationSet(
- OperationSetDesktopSharingClient.class,
- new OperationSetDesktopSharingClientJabberImpl(this)
- );
- // Adds extension to support remote control as a sharing
- // client (sharer).
- supportedFeatures.add(InputEvtIQ.NAMESPACE_CLIENT);
- }
- }
- }
-
- // OperationSetContactCapabilities
- opsetContactCapabilities
- = new OperationSetContactCapabilitiesJabberImpl(this);
- if (discoveryManager != null)
- opsetContactCapabilities.setDiscoveryManager(discoveryManager);
- addSupportedOperationSet(
- OperationSetContactCapabilities.class,
- opsetContactCapabilities);
-
- addSupportedOperationSet(
- OperationSetGenericNotifications.class,
- new OperationSetGenericNotificationsJabberImpl(this));
-
- supportedFeatures.add("jabber:iq:version");
- if(versionManager == null)
- versionManager = new VersionManager(this);
-
- supportedFeatures.add(MessageCorrectionExtension.NAMESPACE);
- addSupportedOperationSet(OperationSetMessageCorrection.class,
- basicInstantMessaging);
-
- OperationSetChangePassword opsetChangePassword
- = new OperationSetChangePasswordJabberImpl(this);
- addSupportedOperationSet(OperationSetChangePassword.class,
- opsetChangePassword);
-
- OperationSetCusaxUtils opsetCusaxCusaxUtils
- = new OperationSetCusaxUtilsJabberImpl(this);
- addSupportedOperationSet(OperationSetCusaxUtils.class,
- opsetCusaxCusaxUtils);
-
- boolean isUserSearchEnabled = accountID.getAccountPropertyBoolean(
- IS_USER_SEARCH_ENABLED_PROPERTY, false);
- if(isUserSearchEnabled)
- {
- addSupportedOperationSet(OperationSetUserSearch.class,
- new OperationSetUserSearchJabberImpl(this));
- }
-
- OperationSetTLS opsetTLS
- = new OperationSetTLSJabberImpl(this);
- addSupportedOperationSet(OperationSetTLS.class,
- opsetTLS);
-
- isInitialized = true;
- }
- }
-
- /**
- * Adds Jingle related features to the supported features.
- */
- private void addJingleFeatures()
- {
- // Add Jingle features to supported features.
- supportedFeatures.add(URN_XMPP_JINGLE);
- supportedFeatures.add(URN_XMPP_JINGLE_RTP);
- supportedFeatures.add(URN_XMPP_JINGLE_RAW_UDP_0);
-
- /*
- * Reflect the preference of the user with respect to the use of
- * ICE.
- */
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.IS_USE_ICE,
- true))
- {
- supportedFeatures.add(URN_XMPP_JINGLE_ICE_UDP_1);
- }
-
- supportedFeatures.add(URN_XMPP_JINGLE_RTP_AUDIO);
- supportedFeatures.add(URN_XMPP_JINGLE_RTP_VIDEO);
- supportedFeatures.add(URN_XMPP_JINGLE_RTP_ZRTP);
-
- /*
- * Reflect the preference of the user with respect to the use of
- * Jingle Nodes.
- */
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactoryJabberImpl.IS_USE_JINGLE_NODES,
- true))
- {
- supportedFeatures.add(URN_XMPP_JINGLE_NODES);
- }
-
- // XEP-0251: Jingle Session Transfer
- supportedFeatures.add(URN_XMPP_JINGLE_TRANSFER_0);
-
- // XEP-0320: Use of DTLS-SRTP in Jingle Sessions
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- DtlsControl.PROTO_NAME))
- {
- supportedFeatures.add(URN_XMPP_JINGLE_DTLS_SRTP);
- }
- }
-
- /**
- * Makes the service implementation close all open sockets and release
- * any resources that it might have taken and prepare for
- * shutdown/garbage collection.
- */
- public void shutdown()
- {
- synchronized(initializationLock)
- {
- if (logger.isTraceEnabled())
- logger.trace("Killing the Jabber Protocol Provider.");
-
- //kill all active calls
- OperationSetBasicTelephonyJabberImpl telephony
- = (OperationSetBasicTelephonyJabberImpl)getOperationSet(
- OperationSetBasicTelephony.class);
- if (telephony != null)
- {
- telephony.shutdown();
- }
-
- disconnectAndCleanConnection();
-
- isInitialized = false;
- }
- }
-
- /**
- * Returns true if the provider service implementation is initialized and
- * ready for use by other services, and false otherwise.
- *
- * @return true if the provider is initialized and ready for use and false
- * otherwise
- */
- public boolean isInitialized()
- {
- return isInitialized;
- }
-
- /**
- * Returns the AccountID that uniquely identifies the account represented
- * by this instance of the ProtocolProviderService.
- * @return the id of the account represented by this provider.
- */
- public AccountID getAccountID()
- {
- return accountID;
- }
-
- /**
- * Returns the XMPPConnectionopened by this provider
- * @return a reference to the XMPPConnection last opened by this
- * provider.
- */
- public XMPPConnection getConnection()
- {
- return connection;
- }
-
- /**
- * Determines whether a specific XMPPException signals that
- * attempted authentication has failed.
- *
- * @param ex the XMPPException which is to be determined whether it
- * signals that attempted authentication has failed
- * @return true if the specified ex signals that attempted
- * authentication has failed; otherwise, false
- */
- private boolean isAuthenticationFailed(XMPPException ex)
- {
- String exMsg = ex.getMessage().toLowerCase();
-
- // as there are no types or reasons for XMPPException
- // we try determine the reason according to their message
- // all messages that were found in smack 3.1.0 were took in count
- return
- ((exMsg.indexOf("sasl authentication") != -1)
- && (exMsg.indexOf("failed") != -1))
- || (exMsg.indexOf(
- "does not support compatible authentication mechanism")
- != -1)
- || (exMsg.indexOf("unable to determine password") != -1);
- }
-
- /**
- * Tries to determine the appropriate message and status to fire,
- * according the exception.
- *
- * @param ex the {@link XMPPException} that caused the state change.
- */
- private void fireRegistrationStateChanged(XMPPException ex)
- {
- int reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
- RegistrationState regState = RegistrationState.UNREGISTERED;
- String reasonStr = null;
-
- Throwable wrappedEx = ex.getWrappedThrowable();
- if(wrappedEx != null
- && (wrappedEx instanceof UnknownHostException
- || wrappedEx instanceof ConnectException
- || wrappedEx instanceof SocketException))
- {
- reason = RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND;
- regState = RegistrationState.CONNECTION_FAILED;
- }
- else
- {
- String exMsg = ex.getMessage().toLowerCase();
-
- // as there are no types or reasons for XMPPException
- // we try determine the reason according to their message
- // all messages that were found in smack 3.1.0 were took in count
- if(isAuthenticationFailed(ex))
- {
- JabberActivator.getProtocolProviderFactory().
- storePassword(getAccountID(), null);
-
- reason = RegistrationStateChangeEvent
- .REASON_AUTHENTICATION_FAILED;
-
- regState = RegistrationState.AUTHENTICATION_FAILED;
-
- fireRegistrationStateChanged(
- getRegistrationState(), regState, reason, null);
-
- // Try to reregister and to ask user for a new password.
- reregister(SecurityAuthority.WRONG_PASSWORD);
-
- return;
- }
- else if(exMsg.indexOf("no response from the server") != -1
- || exMsg.indexOf("connection failed") != -1)
- {
- reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
- regState = RegistrationState.CONNECTION_FAILED;
- }
- else if(exMsg.indexOf("tls is required") != -1)
- {
- regState = RegistrationState.AUTHENTICATION_FAILED;
- reason = RegistrationStateChangeEvent.REASON_TLS_REQUIRED;
- }
- }
-
- if(regState == RegistrationState.UNREGISTERED
- || regState == RegistrationState.CONNECTION_FAILED)
- {
- // we fired that for some reason we are going offline
- // lets clean the connection state for any future connections
- disconnectAndCleanConnection();
- }
-
- fireRegistrationStateChanged(
- getRegistrationState(), regState, reason, reasonStr);
- }
-
- /**
- * Enable to listen for jabber connection events
- */
- private class JabberConnectionListener
- implements ConnectionListener
- {
- /**
- * Implements connectionClosed from ConnectionListener
- */
- public void connectionClosed()
- {
- // if we are in the middle of connecting process
- // do not fire events, will do it later when the method
- // connectAndLogin finishes its work
- synchronized(connectAndLoginLock)
- {
- if(inConnectAndLogin)
- {
- eventDuringLogin = new RegistrationStateChangeEvent(
- ProtocolProviderServiceJabberImpl.this,
- getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
- null);
- return;
- }
- }
- // fire that a connection failed, the reconnection mechanism
- // will look after us and will clean us, other wise we can do
- // a dead lock (connection closed is called
- // within xmppConneciton and calling disconnect again can lock it)
- fireRegistrationStateChanged(
- getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
- null);
- }
-
- /**
- * Implements connectionClosedOnError from
- * ConnectionListener.
- *
- * @param exception contains information on the error.
- */
- public void connectionClosedOnError(Exception exception)
- {
- logger.error("connectionClosedOnError " +
- exception.getLocalizedMessage());
-
- if(exception instanceof XMPPException)
- {
- StreamError err = ((XMPPException)exception).getStreamError();
-
- if(err != null && err.getCode().equals(
- XMPPError.Condition.conflict.toString()))
- {
- // if we are in the middle of connecting process
- // do not fire events, will do it later when the method
- // connectAndLogin finishes its work
- synchronized(connectAndLoginLock)
- {
- if(inConnectAndLogin)
- {
- eventDuringLogin = new RegistrationStateChangeEvent(
- ProtocolProviderServiceJabberImpl.this,
- getRegistrationState(),
- RegistrationState.UNREGISTERED,
- RegistrationStateChangeEvent.REASON_MULTIPLE_LOGINS,
- "Connecting multiple times with the same resource");
- return;
- }
- }
-
- disconnectAndCleanConnection();
-
- fireRegistrationStateChanged(getRegistrationState(),
- RegistrationState.UNREGISTERED,
- RegistrationStateChangeEvent.REASON_MULTIPLE_LOGINS,
- "Connecting multiple times with the same resource");
-
- return;
- }
- } // Ignore certificate exceptions as we handle them elsewhere
- else if(exception instanceof SSLHandshakeException &&
- exception.getCause() instanceof CertificateException)
- {
- return;
- }
-
- // if we are in the middle of connecting process
- // do not fire events, will do it later when the method
- // connectAndLogin finishes its work
- synchronized(connectAndLoginLock)
- {
- if(inConnectAndLogin)
- {
- eventDuringLogin = new RegistrationStateChangeEvent(
- ProtocolProviderServiceJabberImpl.this,
- getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
- exception.getMessage());
- return;
- }
- }
-
- disconnectAndCleanConnection();
-
- fireRegistrationStateChanged(getRegistrationState(),
- RegistrationState.CONNECTION_FAILED,
- RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
- exception.getMessage());
- }
-
- /**
- * Implements reconnectingIn from ConnectionListener
- *
- * @param i delay in seconds for reconnection.
- */
- public void reconnectingIn(int i)
- {
- if (logger.isInfoEnabled())
- logger.info("reconnectingIn " + i);
- }
-
- /**
- * Implements reconnectingIn from ConnectionListener
- */
- public void reconnectionSuccessful()
- {
- if (logger.isInfoEnabled())
- logger.info("reconnectionSuccessful");
- }
-
- /**
- * Implements reconnectionFailed from
- * ConnectionListener.
- *
- * @param exception description of the failure
- */
- public void reconnectionFailed(Exception exception)
- {
- if (logger.isInfoEnabled())
- logger.info("reconnectionFailed ", exception);
- }
- }
-
- /**
- * Returns the jabber protocol icon.
- * @return the jabber protocol icon
- */
- public ProtocolIcon getProtocolIcon()
- {
- return jabberIcon;
- }
-
- /**
- * Returns the current instance of JabberStatusEnum.
- *
- * @return the current instance of JabberStatusEnum.
- */
- JabberStatusEnum getJabberStatusEnum()
- {
- return jabberStatusEnum;
- }
-
- /**
- * Determines if the given list of ext features is supported by the
- * specified jabber id.
- *
- * @param jid the jabber id for which to check
- * @param extFeatures the list of ext features to check for
- *
- * @return true if the list of ext features is supported;
- * otherwise, false
- */
- public boolean isExtFeatureListSupported(String jid, String... extFeatures)
- {
- EntityCapsManager capsManager = discoveryManager.getCapsManager();
- EntityCapsManager.Caps caps = capsManager.getCapsByUser(jid);
-
- String bypassDomain = accountID.getAccountPropertyString(
- "TELEPHONY_BYPASS_GTALK_CAPS");
- String domain = StringUtils.parseServer(jid);
- boolean domainEquals = domain.equals(bypassDomain);
-
- if(caps != null && caps.ext != null)
- {
- String exts[] = caps.ext.split(" ");
- boolean found = false;
-
- for(String extFeature : extFeatures)
- {
- // in case we have a domain that have to bypass GTalk caps
- if(extFeature.equals(CAPS_GTALK_WEB_VOICE) && domainEquals)
- {
- return true;
- }
-
- found = false;
- for(String ext : exts)
- {
- if(ext.equals(extFeature))
- {
- found = true;
- break;
- }
- }
-
- if(!found)
- {
- break;
- }
- }
-
- return found;
- }
-
- return false;
- }
-
- /**
- * Determines if the given list of features is supported by the
- * specified jabber id.
- *
- * @param jid the jabber id for which to check
- * @param features the list of features to check for
- *
- * @return true if the list of features is supported; otherwise,
- * false
- */
- public boolean isFeatureListSupported(String jid, String... features)
- {
- boolean isFeatureListSupported = true;
-
- try
- {
- if(discoveryManager == null)
- return isFeatureListSupported;
-
- DiscoverInfo featureInfo =
- discoveryManager.discoverInfoNonBlocking(jid);
-
- if(featureInfo == null)
- return isFeatureListSupported;
-
- for (String feature : features)
- {
- if (!featureInfo.containsFeature(feature))
- {
- // If one is not supported we return false and don't check
- // the others.
- isFeatureListSupported = false;
- break;
- }
- }
- }
- catch (XMPPException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to retrive discovery info.", e);
- }
- return isFeatureListSupported;
- }
-
- /**
- * Determines if the given list of features is supported by the
- * specified jabber id.
- *
- * @param jid the jabber id that we'd like to get information about
- * @param feature the feature to check for
- *
- * @return true if the list of features is supported, otherwise
- * returns false
- */
- public boolean isFeatureSupported(String jid, String feature)
- {
- return isFeatureListSupported(jid, feature);
- }
-
- /**
- * Returns the full jabber id (jid) corresponding to the given contact. If
- * the provider is not connected returns null.
- *
- * @param contact the contact, for which we're looking for a jid
- * @return the jid of the specified contact or null if the provider is not
- * yet connected;
- */
- public String getFullJid(Contact contact)
- {
- return getFullJid(contact.getAddress());
- }
-
- /**
- * Returns the full jabber id (jid) corresponding to the given bare jid. If
- * the provider is not connected returns null.
- *
- * @param bareJid the bare contact address (i.e. no resource) whose full
- * jid we are looking for.
- * @return the jid of the specified contact or null if the provider is not
- * yet connected;
- */
- public String getFullJid(String bareJid)
- {
- XMPPConnection connection = getConnection();
-
- // when we are not connected there is no full jid
- if (connection != null && connection.isConnected())
- {
- Roster roster = connection.getRoster();
-
- if (roster != null)
- return roster.getPresence(bareJid).getFrom();
- }
- return null;
- }
-
- /**
- * The trust manager which asks the client whether to trust particular
- * certificate which is not globally trusted.
- */
- private class HostTrustManager
- implements X509TrustManager
- {
- /**
- * The default trust manager.
- */
- private final X509TrustManager tm;
-
- /**
- * Creates the custom trust manager.
- * @param tm the default trust manager.
- */
- HostTrustManager(X509TrustManager tm)
- {
- this.tm = tm;
- }
-
- /**
- * Not used.
- *
- * @return nothing.
- */
- public X509Certificate[] getAcceptedIssuers()
- {
- return new X509Certificate[0];
- }
-
- /**
- * Not used.
- * @param chain the cert chain.
- * @param authType authentication type like: RSA.
- * @throws CertificateException never
- * @throws UnsupportedOperationException always
- */
- public void checkClientTrusted(X509Certificate[] chain, String authType)
- throws CertificateException, UnsupportedOperationException
- {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Check whether a certificate is trusted, if not as user whether he
- * trust it.
- * @param chain the certificate chain.
- * @param authType authentication type like: RSA.
- * @throws CertificateException not trusted.
- */
- public void checkServerTrusted(X509Certificate[] chain, String authType)
- throws CertificateException
- {
- abortConnecting = true;
- try
- {
- tm.checkServerTrusted(chain, authType);
- }
- catch(CertificateException e)
- {
- // notify in a separate thread to avoid a deadlock when a
- // reg state listener accesses a synchronized XMPPConnection
- // method (like getRoster)
- new Thread(new Runnable()
- {
- public void run()
- {
- fireRegistrationStateChanged(getRegistrationState(),
- RegistrationState.UNREGISTERED,
- RegistrationStateChangeEvent.REASON_USER_REQUEST,
- "Not trusted certificate");
- }
- }).start();
- throw e;
- }
-
- if(abortConnecting)
- {
- // connect hasn't finished we will continue normally
- abortConnecting = false;
- return;
- }
- else
- {
- // in this situation connect method has finished
- // and it was disconnected so we wont to connect.
-
- // register.connect in new thread so we can release the
- // current connecting thread, otherwise this blocks
- // jabber
- new Thread(new Runnable()
- {
- public void run()
- {
- reregister(SecurityAuthority.CONNECTION_FAILED);
- }
- }).start();
- return;
- }
- }
- }
-
- /**
- * Returns the currently valid {@link ScServiceDiscoveryManager}.
- *
- * @return the currently valid {@link ScServiceDiscoveryManager}.
- */
- public ScServiceDiscoveryManager getDiscoveryManager()
- {
- return discoveryManager;
- }
-
- /**
- * Returns our own Jabber ID.
- *
- * @return our own Jabber ID.
- */
- public String getOurJID()
- {
- String jid = null;
-
- if (connection != null)
- jid = connection.getUser();
-
- if (jid == null)
- {
- // seems like the connection is not yet initialized so lets try to
- // construct our jid ourselves.
- String accountIDUserID = getAccountID().getUserID();
- String userID = StringUtils.parseName(accountIDUserID);
- String serviceName = StringUtils.parseServer(accountIDUserID);
-
- jid = userID + "@" + serviceName;
- }
-
- return jid;
- }
-
- /**
- * Returns the InetAddress that is most likely to be to be used
- * as a next hop when contacting our XMPP server. This is an utility method
- * that is used whenever we have to choose one of our local addresses (e.g.
- * when trying to pick a best candidate for raw udp). It is based on the
- * assumption that, in absence of any more specific details, chances are
- * that we will be accessing remote destinations via the same interface
- * that we are using to access our jabber server.
- *
- * @return the InetAddress that is most likely to be to be used
- * as a next hop when contacting our server.
- *
- * @throws IllegalArgumentException if we don't have a valid server.
- */
- public InetAddress getNextHop()
- throws IllegalArgumentException
- {
- InetAddress nextHop = null;
- String nextHopStr = null;
-
- if ( proxy != null
- && proxy.getProxyType()
- != org.jivesoftware.smack.proxy.ProxyInfo.ProxyType.NONE)
- {
- nextHopStr = proxy.getProxyAddress();
- }
- else
- {
- nextHopStr = getConnection().getHost();
- }
-
- try
- {
- nextHop = NetworkUtils.getInetAddress(nextHopStr);
- }
- catch (UnknownHostException ex)
- {
- throw new IllegalArgumentException(
- "seems we don't have a valid next hop.", ex);
- }
-
- if(logger.isDebugEnabled())
- logger.debug("Returning address " + nextHop + " as next hop.");
-
- return nextHop;
- }
-
- /**
- * Start auto-discovery of JingleNodes tracker/relays.
- */
- public void startJingleNodesDiscovery()
- {
- // Jingle Nodes Service Initialization
- final JabberAccountIDImpl accID = (JabberAccountIDImpl)getAccountID();
- final SmackServiceNode service = new SmackServiceNode(connection,
- 60000);
- // make sure SmackServiceNode will clean up when connection is closed
- connection.addConnectionListener(service);
-
- for(JingleNodeDescriptor desc : accID.getJingleNodes())
- {
- TrackerEntry entry = new TrackerEntry(
- desc.isRelaySupported() ? TrackerEntry.Type.relay :
- TrackerEntry.Type.tracker,
- TrackerEntry.Policy._public,
- desc.getJID(),
- JingleChannelIQ.UDP);
-
- service.addTrackerEntry(entry);
- }
-
- new Thread(new JingleNodesServiceDiscovery(
- service,
- connection,
- accID,
- jingleNodesSyncRoot))
- .start();
-
- jingleNodesServiceNode = service;
- }
-
- /**
- * Get the Jingle Nodes service. Note that this method will block until
- * Jingle Nodes auto discovery (if enabled) finished.
- *
- * @return Jingle Nodes service
- */
- public SmackServiceNode getJingleNodesServiceNode()
- {
- synchronized(jingleNodesSyncRoot)
- {
- return jingleNodesServiceNode;
- }
- }
-
- /**
- * Logs a specific message and associated Throwable cause as an
- * error using the current Logger and then throws a new
- * OperationFailedException with the message, a specific error code
- * and the cause.
- *
- * @param message the message to be logged and then wrapped in a new
- * OperationFailedException
- * @param errorCode the error code to be assigned to the new
- * OperationFailedException
- * @param cause the Throwable that has caused the necessity to log
- * an error and have a new OperationFailedException thrown
- * @param logger the logger that we'd like to log the error message
- * and cause.
- *
- * @throws OperationFailedException the exception that we wanted this method
- * to throw.
- */
- public static void throwOperationFailedException( String message,
- int errorCode,
- Throwable cause,
- Logger logger)
- throws OperationFailedException
- {
- logger.error(message, cause);
-
- if(cause == null)
- throw new OperationFailedException(message, errorCode);
- else
- throw new OperationFailedException(message, errorCode, cause);
- }
-
- /**
- * Used when we need to re-register or someone needs to obtain credentials.
- * @return the SecurityAuthority.
- */
- public SecurityAuthority getAuthority()
- {
- return authority;
- }
-
- /**
- * Returns true if gtalktesting is enabled, false otherwise.
- *
- * @return true if gtalktesting is enabled, false otherwise.
- */
- public boolean isGTalkTesting()
- {
- return
- Boolean.getBoolean("gtalktesting")
- || JabberActivator.getConfigurationService().getBoolean(
- "net.java.sip.communicator.impl.protocol.jabber"
- + ".gtalktesting",
- false)
- || accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.IS_USE_GOOGLE_ICE,
- true);
- }
-
- UserCredentials getUserCredentials()
- {
- return userCredentials;
- }
-
- /**
- * Returns true if our account is a Gmail or a Google Apps ones.
- *
- * @return true if our account is a Gmail or a Google Apps ones.
- */
- public boolean isGmailOrGoogleAppsAccount()
- {
- String domain = StringUtils.parseServer(
- getAccountID().getUserID());
- return isGmailOrGoogleAppsAccount(domain);
- }
-
- /**
- * Returns true if our account is a Gmail or a Google Apps ones.
- *
- * @param domain domain to check
- * @return true if our account is a Gmail or a Google Apps ones.
- */
- public static boolean isGmailOrGoogleAppsAccount(String domain)
- {
- SRVRecord srvRecords[] = null;
-
- try
- {
- srvRecords = NetworkUtils.getSRVRecords("xmpp-client", "tcp",
- domain);
- }
- catch (ParseException e)
- {
- logger.info("Failed to get SRV records for XMPP domain");
- return false;
- }
- catch (DnssecException e)
- {
- logger.error("DNSSEC failure while checking for google domains", e);
- return false;
- }
-
- if(srvRecords == null)
- {
- return false;
- }
-
- for(SRVRecord srv : srvRecords)
- {
- if(srv.getTarget().endsWith("google.com") ||
- srv.getTarget().endsWith("google.com."))
- {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Sets the traffic class for the XMPP signalling socket.
- */
- private void setTrafficClass()
- {
- Socket s = connection.getSocket();
-
- if(s != null)
- {
- ConfigurationService configService =
- JabberActivator.getConfigurationService();
- String dscp = configService.getString(XMPP_DSCP_PROPERTY);
-
- if(dscp != null)
- {
- try
- {
- int dscpInt = Integer.parseInt(dscp) << 2;
-
- if(dscpInt > 0)
- s.setTrafficClass(dscpInt);
- }
- catch (Exception e)
- {
- logger.info("Failed to set trafficClass", e);
- }
- }
- }
- }
-
- /**
- * Gets the entity ID of the first Jitsi Videobridge associated with
- * {@link #connection} i.e. provided by the serviceName of
- * connection.
- *
- * @return the entity ID of the first Jitsi Videobridge associated with
- * connection
- */
- public String getJitsiVideobridge()
- {
- XMPPConnection connection = getConnection();
-
- if (connection != null)
- {
- ScServiceDiscoveryManager discoveryManager = getDiscoveryManager();
- String serviceName = connection.getServiceName();
- DiscoverItems discoverItems = null;
-
- try
- {
- discoverItems = discoveryManager.discoverItems(serviceName);
- }
- catch (XMPPException xmppe)
- {
- if (logger.isDebugEnabled())
- {
- logger.debug(
- "Failed to discover the items associated with"
- + " Jabber entity: " + serviceName,
- xmppe);
- }
- }
- if (discoverItems != null)
- {
- Iterator discoverItemIter
- = discoverItems.getItems();
-
- while (discoverItemIter.hasNext())
- {
- DiscoverItems.Item discoverItem = discoverItemIter.next();
- String entityID = discoverItem.getEntityID();
- DiscoverInfo discoverInfo = null;
-
- try
- {
- discoverInfo = discoveryManager.discoverInfo(entityID);
- }
- catch (XMPPException xmppe)
- {
- logger.warn(
- "Failed to discover information about Jabber"
- + " entity: " + entityID,
- xmppe);
- }
- if ((discoverInfo != null)
- && discoverInfo.containsFeature(
- ColibriConferenceIQ.NAMESPACE))
- {
- return entityID;
- }
- }
- }
- }
-
- return null;
- }
-
- /**
- * Load jabber service class, their static context will register
- * what is needed. Used in android as when using the other jars
- * these services are loaded from the jar manifest.
- */
- private static void loadJabberServiceClasses()
- {
- if(!OSUtils.IS_ANDROID)
- return;
-
- try
- {
- // pre-configure smack in android
- // just to load class to init their static blocks
- SmackConfiguration.getVersion();
- Class.forName(ServiceDiscoveryManager.class.getName());
-
- Class.forName(DelayInformation.class.getName());
- Class.forName(org.jivesoftware.smackx
- .provider.DelayInformationProvider.class.getName());
- Class.forName(org.jivesoftware.smackx
- .bytestreams.socks5.Socks5BytestreamManager.class.getName());
- Class.forName(XHTMLManager.class.getName());
- Class.forName(org.jivesoftware.smackx
- .bytestreams.ibb.InBandBytestreamManager.class.getName());
-
- }
- catch(ClassNotFoundException e)
- {
- logger.error("Error loading classes in smack", e);
- }
- }
-
- /**
- * Return the SSL socket (if TLS used).
- * @return The SSL socket or null if not used
- */
- public SSLSocket getSSLSocket()
- {
- final SSLSocket result;
- final Socket socket = connection.getSocket();
- if (socket instanceof SSLSocket)
- {
- result = (SSLSocket) socket;
- }
- else
- {
- result = null;
- }
- return result;
- }
-
-}
+/*
+ * 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.math.*;
+import java.net.*;
+import java.security.*;
+import java.security.cert.*;
+import java.text.*;
+import java.util.*;
+
+import javax.net.ssl.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.debugger.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.caps.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.carbon.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingleinfo.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*;
+import net.java.sip.communicator.service.certificate.*;
+import net.java.sip.communicator.service.dns.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.jabberconstants.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.Logger;
+
+import org.jitsi.service.configuration.*;
+import org.jitsi.service.neomedia.*;
+import org.jitsi.util.*;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.*;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.packet.*;
+import org.osgi.framework.*;
+import org.xmpp.jnodes.smack.*;
+
+/**
+ * An implementation of the protocol provider service over the Jabber protocol
+ *
+ * @author Damian Minkov
+ * @author Symphorien Wanko
+ * @author Lyubomir Marinov
+ * @author Yana Stamcheva
+ * @author Emil Ivov
+ */
+public class ProtocolProviderServiceJabberImpl
+ extends AbstractProtocolProviderService
+{
+ /**
+ * Logger of this class
+ */
+ private static final Logger logger =
+ Logger.getLogger(ProtocolProviderServiceJabberImpl.class);
+
+ /**
+ * Jingle's Discovery Info common URN.
+ */
+ public static final String URN_XMPP_JINGLE = JingleIQ.NAMESPACE;
+
+ /**
+ * Jingle's Discovery Info URN for RTP support.
+ */
+ public static final String URN_XMPP_JINGLE_RTP
+ = RtpDescriptionPacketExtension.NAMESPACE;
+
+ /**
+ * Jingle's Discovery Info URN for RTP support with audio.
+ */
+ public static final String URN_XMPP_JINGLE_RTP_AUDIO
+ = "urn:xmpp:jingle:apps:rtp:audio";
+
+ /**
+ * Jingle's Discovery Info URN for RTP support with video.
+ */
+ public static final String URN_XMPP_JINGLE_RTP_VIDEO
+ = "urn:xmpp:jingle:apps:rtp:video";
+
+ /**
+ * Jingle's Discovery Info URN for ZRTP support with RTP.
+ */
+ public static final String URN_XMPP_JINGLE_RTP_ZRTP
+ = ZrtpHashPacketExtension.NAMESPACE;
+
+ /**
+ * Jingle's Discovery Info URN for ICE_UDP transport support.
+ */
+ public static final String URN_XMPP_JINGLE_RAW_UDP_0
+ = RawUdpTransportPacketExtension.NAMESPACE;
+
+ /**
+ * Jingle's Discovery Info URN for ICE_UDP transport support.
+ */
+ public static final String URN_XMPP_JINGLE_ICE_UDP_1
+ = IceUdpTransportPacketExtension.NAMESPACE;
+
+ /**
+ * Jingle's Discovery Info URN for Jingle Nodes support.
+ */
+ public static final String URN_XMPP_JINGLE_NODES
+ = "http://jabber.org/protocol/jinglenodes";
+
+ /**
+ * Jingle's Discovery Info URN for "XEP-0251: Jingle Session Transfer"
+ * support.
+ */
+ public static final String URN_XMPP_JINGLE_TRANSFER_0
+ = TransferPacketExtension.NAMESPACE;
+
+ /**
+ * Jingle's Discovery Info URN for "XEP-298 :Delivering Conference
+ * Information to Jingle Participants (Coin)" support.
+ */
+ public static final String URN_XMPP_JINGLE_COIN = "urn:xmpp:coin";
+
+ /**
+ * Jingle's Discovery Info URN for "XEP-0320: Use of DTLS-SRTP in
+ * Jingle Sessions".
+ */
+ public static final String URN_XMPP_JINGLE_DTLS_SRTP
+ = "urn:xmpp:jingle:apps:dtls:0";
+
+ /**
+ * Discovery Info URN for classic RFC3264-style Offer/Answer negotiation
+ * with no support for Trickle ICE and low tolerance to transport/payload
+ * separation. Defined in XEP-0176
+ */
+ public static final String URN_IETF_RFC_3264 = "urn:ietf:rfc:3264";
+
+ /**
+ * Jingle's Discovery Info URN for "XEP-0294: Jingle RTP Header Extensions
+ * Negotiation" support.
+ */
+ public static final String URN_XMPP_JINGLE_RTP_HDREXT =
+ "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
+
+ /**
+ * Capabilities name for audio call in Google Talk web version.
+ */
+ public static final String CAPS_GTALK_WEB_VOICE = "voice-v1";
+
+ /**
+ * Capabilities name for video call (receive side) in Google Talk web
+ * version.
+ */
+ public static final String CAPS_GTALK_WEB_VIDEO = "video-v1";
+
+ /**
+ * Capabilities name for video call (sender side) in Google Talk web
+ * version.
+ */
+ public static final String CAPS_GTALK_WEB_CAMERA = "camera-v1";
+
+ /**
+ * URN for Google voice.
+ */
+ public static final String URN_GOOGLE_VOICE =
+ "http://www.google.com/xmpp/protocol/voice/v1";
+
+ /**
+ * URN for Google camera.
+ */
+ public static final String URN_GOOGLE_CAMERA =
+ "http://www.google.com/xmpp/protocol/camera/v1";
+
+ /**
+ * URN for Google video.
+ */
+ public static final String URN_GOOGLE_VIDEO =
+ "http://www.google.com/xmpp/protocol/video/v1";
+
+ /**
+ * URN for XEP-0077 inband registration
+ */
+ public static final String URN_REGISTER = "jabber:iq:register";
+
+ /**
+ * The name of the property under which the user may specify if the desktop
+ * streaming or sharing should be disabled.
+ */
+ private static final String IS_DESKTOP_STREAMING_DISABLED
+ = "net.java.sip.communicator.impl.protocol.jabber." +
+ "DESKTOP_STREAMING_DISABLED";
+
+ /**
+ * The name of the property under which the user may specify if audio/video
+ * calls should be disabled.
+ */
+ private static final String IS_CALLING_DISABLED
+ = "net.java.sip.communicator.impl.protocol.jabber.CALLING_DISABLED";
+
+ /**
+ * Smack packet reply timeout.
+ */
+ public static final int SMACK_PACKET_REPLY_TIMEOUT = 45000;
+
+ /**
+ * Property for vcard reply timeout. Time to wait before
+ * we think vcard retrieving has timeouted, default value
+ * of smack is 5000 (5 sec.).
+ */
+ public static final String VCARD_REPLY_TIMEOUT_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.jabber.VCARD_REPLY_TIMEOUT";
+
+ /**
+ * XMPP signaling DSCP configuration property name.
+ */
+ private static final String XMPP_DSCP_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.XMPP_DSCP";
+
+ /**
+ * Indicates if user search is disabled.
+ */
+ private static final String IS_USER_SEARCH_ENABLED_PROPERTY
+ = "USER_SEARCH_ENABLED";
+
+ /**
+ * Google voice domain name.
+ */
+ public static final String GOOGLE_VOICE_DOMAIN = "voice.google.com";
+
+ /**
+ * Used to connect to a XMPP server.
+ */
+ private XMPPConnection connection;
+
+ /**
+ * Indicates whether or not the provider is initialized and ready for use.
+ */
+ private boolean isInitialized = false;
+
+ /**
+ * We use this to lock access to initialization.
+ */
+ private final Object initializationLock = new Object();
+
+ /**
+ * The identifier of the account that this provider represents.
+ */
+ private AccountID accountID = null;
+
+ /**
+ * Used when we need to re-register
+ */
+ private SecurityAuthority authority = null;
+
+ /**
+ * The resource we will use when connecting during this run.
+ */
+ private String resource = null;
+
+ /**
+ * The icon corresponding to the jabber protocol.
+ */
+ private ProtocolIconJabberImpl jabberIcon;
+
+ /**
+ * A set of features supported by our Jabber implementation.
+ * In general, we add new feature(s) when we add new operation sets.
+ * (see xep-0030 : http://www.xmpp.org/extensions/xep-0030.html#info).
+ * Example : to tell the world that we support jingle, we simply have
+ * to do :
+ * supportedFeatures.add("http://www.xmpp.org/extensions/xep-0166.html#ns");
+ * Beware there is no canonical mapping between op set and jabber features
+ * (op set is a SC "concept"). This means that one op set in SC can
+ * correspond to many jabber features. It is also possible that there is no
+ * jabber feature corresponding to a SC op set or again,
+ * we can currently support some features wich do not have a specific
+ * op set in SC (the mandatory feature :
+ * http://jabber.org/protocol/disco#info is one example).
+ * We can find features corresponding to op set in the xep(s) related
+ * to implemented functionality.
+ */
+ private final List supportedFeatures = new ArrayList();
+
+ /**
+ * The ServiceDiscoveryManager is responsible for advertising
+ * supportedFeatures when asked by a remote client. It can also
+ * be used to query remote clients for supported features.
+ */
+ private ScServiceDiscoveryManager discoveryManager = null;
+
+ /**
+ * The OperationSetContactCapabilities of this
+ * ProtocolProviderService which is the service-public counterpart
+ * of {@link #discoveryManager}.
+ */
+ private OperationSetContactCapabilitiesJabberImpl opsetContactCapabilities;
+
+ /**
+ * The statuses.
+ */
+ private JabberStatusEnum jabberStatusEnum;
+
+ /**
+ * The service we use to interact with user.
+ */
+ private CertificateService guiVerification;
+
+ /**
+ * Used with tls connecting when certificates are not trusted
+ * and we ask the user to confirm connection. When some timeout expires
+ * connect method returns, and we use abortConnecting to abort further
+ * execution cause after user chooses we make further processing from there.
+ */
+ private boolean abortConnecting = false;
+
+ /**
+ * Flag indicating are we currently executing connectAndLogin method.
+ */
+ private boolean inConnectAndLogin = false;
+
+ /**
+ * Object used to synchronize the flag inConnectAndLogin.
+ */
+ private final Object connectAndLoginLock = new Object();
+
+ /**
+ * If an event occurs during login we fire it at the end of the login
+ * process (at the end of connectAndLogin method).
+ */
+ private RegistrationStateChangeEvent eventDuringLogin;
+
+ /**
+ * Listens for connection closes or errors.
+ */
+ private JabberConnectionListener connectionListener;
+
+ /**
+ * The details of the proxy we are using to connect to the server (if any)
+ */
+ private org.jivesoftware.smack.proxy.ProxyInfo proxy;
+
+ /**
+ * Our provider manager instances.
+ */
+ private static ProviderManager providerManager = null;
+
+ /**
+ * Lock for creating provider.
+ */
+ private static Object providerCreationLock = new Object();
+
+ /**
+ * State for connect and login state.
+ */
+ enum ConnectState
+ {
+ /**
+ * Abort any further connecting.
+ */
+ ABORT_CONNECTING,
+ /**
+ * Continue trying with next address.
+ */
+ CONTINUE_TRYING,
+ /**
+ * Stop trying we succeeded or just have a final state for
+ * the whole connecting procedure.
+ */
+ STOP_TRYING
+ }
+
+ /**
+ * The debugger who logs packets.
+ */
+ private SmackPacketDebugger debugger = null;
+
+ /**
+ * Jingle Nodes service.
+ */
+ private SmackServiceNode jingleNodesServiceNode = null;
+
+ /**
+ * Synchronization object to monitore jingle nodes auto discovery.
+ */
+ private final Object jingleNodesSyncRoot = new Object();
+
+ /**
+ * Stores user credentials for local use if user hasn't stored
+ * its password.
+ */
+ private UserCredentials userCredentials = null;
+
+ /**
+ * The currently running keepAliveManager if enabled.
+ */
+ private KeepAliveManager keepAliveManager = null;
+
+ /**
+ * The version manager.
+ */
+ private VersionManager versionManager = null;
+
+ // load xmpp manager classes
+ static
+ {
+ if(OSUtils.IS_ANDROID)
+ loadJabberServiceClasses();
+ }
+
+ /**
+ * Returns the state of the registration of this protocol provider
+ * @return the RegistrationState that this provider is
+ * currently in or null in case it is in a unknown state.
+ */
+ public RegistrationState getRegistrationState()
+ {
+ if(connection == null)
+ return RegistrationState.UNREGISTERED;
+ else if(connection.isConnected() && connection.isAuthenticated())
+ return RegistrationState.REGISTERED;
+ else
+ return RegistrationState.UNREGISTERED;
+ }
+
+ /**
+ * Return the certificate verification service impl.
+ * @return the CertificateVerification service.
+ */
+ private CertificateService getCertificateVerificationService()
+ {
+ if(guiVerification == null)
+ {
+ ServiceReference guiVerifyReference
+ = JabberActivator.getBundleContext().getServiceReference(
+ CertificateService.class.getName());
+ if(guiVerifyReference != null)
+ guiVerification = (CertificateService)
+ JabberActivator.getBundleContext().getService(
+ guiVerifyReference);
+ }
+
+ return guiVerification;
+ }
+
+ /**
+ * Starts the registration process. Connection details such as
+ * registration server, user name/number are provided through the
+ * configuration service through implementation specific properties.
+ *
+ * @param authority the security authority that will be used for resolving
+ * any security challenges that may be returned during the
+ * registration or at any moment while we're registered.
+ * @throws OperationFailedException with the corresponding code it the
+ * registration fails for some reason (e.g. a networking error or an
+ * implementation problem).
+ */
+ public void register(final SecurityAuthority authority)
+ throws OperationFailedException
+ {
+ if(authority == null)
+ throw new IllegalArgumentException(
+ "The register method needs a valid non-null authority impl "
+ + " in order to be able and retrieve passwords.");
+
+ this.authority = authority;
+
+ try
+ {
+ // reset states
+ abortConnecting = false;
+
+ // indicate we started connectAndLogin process
+ synchronized(connectAndLoginLock)
+ {
+ inConnectAndLogin = true;
+ }
+
+ initializeConnectAndLogin(authority,
+ SecurityAuthority.AUTHENTICATION_REQUIRED);
+ }
+ catch (XMPPException ex)
+ {
+ logger.error("Error registering", ex);
+
+ eventDuringLogin = null;
+
+ fireRegistrationStateChanged(ex);
+ }
+ finally
+ {
+ synchronized(connectAndLoginLock)
+ {
+ // Checks if an error has occurred during login, if so we fire
+ // it here in order to avoid a deadlock which occurs in
+ // reconnect plugin. The deadlock is cause we fired an event
+ // during login process and have locked initializationLock and
+ // we cannot unregister from reconnect, cause unregister method
+ // also needs this lock.
+ if(eventDuringLogin != null)
+ {
+ if(eventDuringLogin.getNewState().equals(
+ RegistrationState.CONNECTION_FAILED) ||
+ eventDuringLogin.getNewState().equals(
+ RegistrationState.UNREGISTERED))
+ disconnectAndCleanConnection();
+
+ fireRegistrationStateChanged(
+ eventDuringLogin.getOldState(),
+ eventDuringLogin.getNewState(),
+ eventDuringLogin.getReasonCode(),
+ eventDuringLogin.getReason());
+
+ eventDuringLogin = null;
+ inConnectAndLogin = false;
+ return;
+ }
+
+ inConnectAndLogin = false;
+ }
+ }
+ }
+
+ /**
+ * Connects and logins again to the server.
+ *
+ * @param authReasonCode indicates the reason of the re-authentication.
+ */
+ void reregister(int authReasonCode)
+ {
+ try
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("Trying to reregister us!");
+
+ // sets this if any is trying to use us through registration
+ // to know we are not registered
+ this.unregister(false);
+
+ // reset states
+ this.abortConnecting = false;
+
+ // indicate we started connectAndLogin process
+ synchronized(connectAndLoginLock)
+ {
+ inConnectAndLogin = true;
+ }
+
+ initializeConnectAndLogin(authority, authReasonCode);
+ }
+ catch(OperationFailedException ex)
+ {
+ logger.error("Error ReRegistering", ex);
+
+ eventDuringLogin = null;
+
+ disconnectAndCleanConnection();
+
+ fireRegistrationStateChanged(getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null);
+ }
+ catch (XMPPException ex)
+ {
+ logger.error("Error ReRegistering", ex);
+
+ eventDuringLogin = null;
+
+ fireRegistrationStateChanged(ex);
+ }
+ finally
+ {
+ synchronized(connectAndLoginLock)
+ {
+ // Checks if an error has occurred during login, if so we fire
+ // it here in order to avoid a deadlock which occurs in
+ // reconnect plugin. The deadlock is cause we fired an event
+ // during login process and have locked initializationLock and
+ // we cannot unregister from reconnect, cause unregister method
+ // also needs this lock.
+ if(eventDuringLogin != null)
+ {
+ if(eventDuringLogin.getNewState().equals(
+ RegistrationState.CONNECTION_FAILED) ||
+ eventDuringLogin.getNewState().equals(
+ RegistrationState.UNREGISTERED))
+ disconnectAndCleanConnection();
+
+ fireRegistrationStateChanged(
+ eventDuringLogin.getOldState(),
+ eventDuringLogin.getNewState(),
+ eventDuringLogin.getReasonCode(),
+ eventDuringLogin.getReason());
+
+ eventDuringLogin = null;
+ inConnectAndLogin = false;
+ return;
+ }
+
+ inConnectAndLogin = false;
+ }
+ }
+ }
+
+ /**
+ * Indicates if the XMPP transport channel is using a TLS secured socket.
+ *
+ * @return True when TLS is used, false otherwise.
+ */
+ public boolean isSignalingTransportSecure()
+ {
+ return connection != null && connection.isUsingTLS();
+ }
+
+ /**
+ * Returns the "transport" protocol of this instance used to carry the
+ * control channel for the current protocol service.
+ *
+ * @return The "transport" protocol of this instance: TCP, TLS or UNKNOWN.
+ */
+ public TransportProtocol getTransportProtocol()
+ {
+ // Without a connection, there is no transport available.
+ if(connection != null && connection.isConnected())
+ {
+ // Transport using a secure connection.
+ if(connection.isUsingTLS())
+ {
+ return TransportProtocol.TLS;
+ }
+ // Transport using a unsecure connection.
+ return TransportProtocol.TCP;
+ }
+ return TransportProtocol.UNKNOWN;
+ }
+
+ /**
+ * Connects and logins to the server
+ * @param authority SecurityAuthority
+ * @param reasonCode the authentication reason code. Indicates the reason of
+ * this authentication.
+ * @throws XMPPException if we cannot connect to the server - network problem
+ * @throws OperationFailedException if login parameters
+ * as server port are not correct
+ */
+ private void initializeConnectAndLogin(SecurityAuthority authority,
+ int reasonCode)
+ throws XMPPException, OperationFailedException
+ {
+ synchronized(initializationLock)
+ {
+ // if a thread is waiting for initializationLock and enters
+ // lets check whether someone hasn't already tried login and
+ // have succeeded,
+ // should prevent "Trace possible duplicate connections" prints
+ if(isRegistered())
+ return;
+
+ JabberLoginStrategy loginStrategy = createLoginStrategy();
+ userCredentials = loginStrategy.prepareLogin(authority, reasonCode);
+ if(!loginStrategy.loginPreparationSuccessful())
+ return;
+
+ String serviceName
+ = StringUtils.parseServer(getAccountID().getUserID());
+
+ loadResource();
+ loadProxy();
+ Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
+
+ ConnectState state;
+ //[0] = hadDnsSecException
+ boolean[] hadDnsSecException = new boolean[]{false};
+
+ // try connecting with auto-detection if enabled
+ boolean isServerOverriden =
+ getAccountID().getAccountPropertyBoolean(
+ ProtocolProviderFactory.IS_SERVER_OVERRIDDEN, false);
+
+ if(!isServerOverriden)
+ {
+ state = connectUsingSRVRecords(serviceName,
+ serviceName, hadDnsSecException, loginStrategy);
+ if(hadDnsSecException[0])
+ {
+ setDnssecLoginFailure();
+ return;
+ }
+ if(state == ConnectState.ABORT_CONNECTING
+ || state == ConnectState.STOP_TRYING)
+ return;
+ }
+
+ // check for custom xmpp domain which we will check for
+ // SRV records for server addresses
+ String customXMPPDomain = getAccountID()
+ .getAccountPropertyString("CUSTOM_XMPP_DOMAIN");
+
+ if(customXMPPDomain != null && !hadDnsSecException[0])
+ {
+ logger.info("Connect using custom xmpp domain: " +
+ customXMPPDomain);
+
+ state = connectUsingSRVRecords(
+ customXMPPDomain, serviceName,
+ hadDnsSecException, loginStrategy);
+
+ logger.info("state for connectUsingSRVRecords: " + state);
+
+ if(hadDnsSecException[0])
+ {
+ setDnssecLoginFailure();
+ return;
+ }
+
+ if(state == ConnectState.ABORT_CONNECTING
+ || state == ConnectState.STOP_TRYING)
+ return;
+ }
+
+ // connect with specified server name
+ String serverAddressUserSetting
+ = getAccountID().getAccountPropertyString(
+ ProtocolProviderFactory.SERVER_ADDRESS);
+
+ int serverPort = getAccountID().getAccountPropertyInt(
+ ProtocolProviderFactory.SERVER_PORT, 5222);
+
+ InetSocketAddress[] addrs = null;
+ try
+ {
+ addrs = NetworkUtils.getAandAAAARecords(
+ serverAddressUserSetting,
+ serverPort
+ );
+ }
+ catch (ParseException e)
+ {
+ logger.error("Domain not resolved", e);
+ }
+ catch (DnssecException e)
+ {
+ logger.error("DNSSEC failure for overridden server", e);
+ setDnssecLoginFailure();
+ return;
+ }
+
+ if (addrs == null || addrs.length == 0)
+ {
+ logger.error("No server addresses found");
+
+ eventDuringLogin = null;
+
+ fireRegistrationStateChanged(
+ getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
+ "No server addresses found");
+ }
+ else
+ {
+ for (InetSocketAddress isa : addrs)
+ {
+ try
+ {
+ state = connectAndLogin(isa, serviceName,
+ loginStrategy);
+ if(state == ConnectState.ABORT_CONNECTING
+ || state == ConnectState.STOP_TRYING)
+ return;
+ }
+ catch(XMPPException ex)
+ {
+ disconnectAndCleanConnection();
+ if(isAuthenticationFailed(ex))
+ throw ex;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the JabberLoginStrategy to use for the current account.
+ */
+ private JabberLoginStrategy createLoginStrategy()
+ {
+ String clientCertId = getAccountID().getAccountPropertyString(
+ ProtocolProviderFactory.CLIENT_TLS_CERTIFICATE);
+ if(clientCertId != null)
+ {
+ return new LoginByClientCertificateStrategy(getAccountID());
+ }
+ else
+ {
+ return new LoginByPasswordStrategy(this, getAccountID());
+ }
+ }
+
+ private void setDnssecLoginFailure()
+ {
+ eventDuringLogin = new RegistrationStateChangeEvent(
+ this,
+ getRegistrationState(),
+ RegistrationState.UNREGISTERED,
+ RegistrationStateChangeEvent.REASON_USER_REQUEST,
+ "No usable host found due to DNSSEC failures");
+ }
+
+ /**
+ * Connects using the domain specified and its SRV records.
+ * @param domain the domain to use
+ * @param serviceName the domain name of the user's login
+ * @param dnssecState state of possible received DNSSEC exceptions
+ * @param loginStrategy the login strategy to use
+ * @return whether to continue trying or stop.
+ */
+ private ConnectState connectUsingSRVRecords(
+ String domain,
+ String serviceName,
+ boolean[] dnssecState,
+ JabberLoginStrategy loginStrategy)
+ throws XMPPException
+ {
+ // check to see is there SRV records for this server domain
+ SRVRecord srvRecords[] = null;
+ try
+ {
+ srvRecords = NetworkUtils
+ .getSRVRecords("xmpp-client", "tcp", domain);
+ }
+ catch (ParseException e)
+ {
+ logger.error("SRV record not resolved", e);
+ }
+ catch (DnssecException e)
+ {
+ logger.error("DNSSEC failure for SRV lookup", e);
+ dnssecState[0] = true;
+ }
+
+ if(srvRecords != null)
+ {
+ for(SRVRecord srv : srvRecords)
+ {
+ InetSocketAddress[] addrs = null;
+ try
+ {
+ addrs =
+ NetworkUtils.getAandAAAARecords(
+ srv.getTarget(),
+ srv.getPort()
+ );
+ }
+ catch (ParseException e)
+ {
+ logger.error("Invalid SRV record target", e);
+ }
+ catch (DnssecException e)
+ {
+ logger.error("DNSSEC failure for A/AAAA lookup of SRV", e);
+ dnssecState[0] = true;
+ }
+
+ if (addrs == null || addrs.length == 0)
+ {
+ logger.error("No A/AAAA addresses found for " +
+ srv.getTarget());
+ continue;
+ }
+
+ for (InetSocketAddress isa : addrs)
+ {
+ try
+ {
+ // if failover mechanism is enabled, use it,
+ // default is not enabled.
+ if(JabberActivator.getConfigurationService()
+ .getBoolean(FailoverConnectionMonitor
+ .REVERSE_FAILOVER_ENABLED_PROP,
+ false
+ ))
+ {
+ FailoverConnectionMonitor.getInstance(this)
+ .setCurrent(domain, srv.getTarget());
+ }
+
+ ConnectState state = connectAndLogin(
+ isa, serviceName, loginStrategy);
+ return state;
+ }
+ catch(XMPPException ex)
+ {
+ logger.error("Error connecting to " + isa
+ + " for domain:" + domain
+ + " serviceName:" + serviceName, ex);
+
+ disconnectAndCleanConnection();
+
+ if(isAuthenticationFailed(ex))
+ throw ex;
+ }
+ }
+ }
+ }
+ else
+ logger.error("No SRV addresses found for _xmpp-client._tcp."
+ + domain);
+
+ return ConnectState.CONTINUE_TRYING;
+ }
+
+ /**
+ * Tries to login to the XMPP server with the supplied user ID. If the
+ * protocol is Google Talk, the user ID including the service name is used.
+ * For other protocols, if the login with the user ID without the service
+ * name fails, a second attempt including the service name is made.
+ *
+ * @param currentAddress the IP address to connect to
+ * @param serviceName the domain name of the user's login
+ * @param loginStrategy the login strategy to use
+ * @throws XMPPException when a failure occurs
+ */
+ private ConnectState connectAndLogin(InetSocketAddress currentAddress,
+ String serviceName,
+ JabberLoginStrategy loginStrategy)
+ throws XMPPException
+ {
+ String userID = null;
+ boolean qualifiedUserID;
+
+ /* with a google account (either gmail or google apps
+ * related ones), the userID MUST be the full e-mail address
+ * not just the ID
+ */
+ if(getAccountID().getProtocolDisplayName().equals("Google Talk"))
+ {
+ userID = getAccountID().getUserID();
+ qualifiedUserID = true;
+ }
+ else
+ {
+ userID = StringUtils.parseName(getAccountID().getUserID());
+ qualifiedUserID = false;
+ }
+
+ try
+ {
+ return connectAndLogin(
+ currentAddress, serviceName,
+ userID, resource, loginStrategy);
+ }
+ catch(XMPPException ex)
+ {
+ // server disconnect us after such an error, do cleanup
+ disconnectAndCleanConnection();
+
+ //no need to check with a different username if the
+ //socket could not be opened
+ if (ex.getWrappedThrowable() instanceof ConnectException
+ || ex.getWrappedThrowable() instanceof NoRouteToHostException)
+ {
+ //as we got an exception not handled in connectAndLogin
+ //no state was set, so fire it here so we can continue
+ //with the re-register process
+ //2013-08-07 do not fire event, if we have several
+ // addresses and we fire event will activate reconnect
+ // but we will continue connecting with other addresses
+ // and can register with address, then unregister and try again
+ // that is from reconnect plugin.
+ // Storing event for fire after all have failed and we have
+ // tried every address.
+ eventDuringLogin = new RegistrationStateChangeEvent(
+ ProtocolProviderServiceJabberImpl.this,
+ getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
+ null);
+
+ throw ex;
+ }
+
+ // don't attempt to append the service name if it's already there
+ if (!qualifiedUserID)
+ {
+ try
+ {
+ // logging in might need the service name
+ return connectAndLogin(
+ currentAddress, serviceName,
+ userID + "@" + serviceName,
+ resource,
+ loginStrategy);
+ }
+ catch(XMPPException ex2)
+ {
+ disconnectAndCleanConnection();
+ throw ex; //throw the original exception
+ }
+ }
+ else
+ throw ex;
+ }
+ }
+
+ /**
+ * Initializes the Jabber Resource identifier.
+ */
+ private void loadResource()
+ {
+ if(resource == null)
+ {
+ String defaultResource = "jitsi";
+ String autoGenenerateResource =
+ getAccountID().getAccountPropertyString(
+ ProtocolProviderFactory.AUTO_GENERATE_RESOURCE);
+ if(autoGenenerateResource == null ||
+ Boolean.parseBoolean(autoGenenerateResource))
+ {
+ SecureRandom random = new SecureRandom();
+
+ resource = defaultResource + "-" +
+ new BigInteger(32, random).toString(32);
+ }
+ else
+ {
+ resource = getAccountID().getAccountPropertyString(
+ ProtocolProviderFactory.RESOURCE);
+
+ if(resource == null || resource.length() == 0)
+ resource = defaultResource;
+ }
+ }
+ }
+
+ /**
+ * Sets the global proxy information based on the configuration
+ *
+ * @throws OperationFailedException
+ */
+ private void loadProxy() throws OperationFailedException
+ {
+ String globalProxyType =
+ JabberActivator.getConfigurationService()
+ .getString(ProxyInfo.CONNECTION_PROXY_TYPE_PROPERTY_NAME);
+ if(globalProxyType == null ||
+ globalProxyType.equals(ProxyInfo.ProxyType.NONE.name()))
+ {
+ proxy = org.jivesoftware.smack.proxy.ProxyInfo.forNoProxy();
+ }
+ else
+ {
+ String globalProxyAddress =
+ JabberActivator.getConfigurationService().getString(
+ ProxyInfo.CONNECTION_PROXY_ADDRESS_PROPERTY_NAME);
+ String globalProxyPortStr =
+ JabberActivator.getConfigurationService().getString(
+ ProxyInfo.CONNECTION_PROXY_PORT_PROPERTY_NAME);
+ int globalProxyPort;
+ try
+ {
+ globalProxyPort = Integer.parseInt(
+ globalProxyPortStr);
+ }
+ catch(NumberFormatException ex)
+ {
+ throw new OperationFailedException("Wrong proxy port, "
+ + globalProxyPortStr
+ + " does not represent an integer",
+ OperationFailedException.INVALID_ACCOUNT_PROPERTIES,
+ ex);
+ }
+ String globalProxyUsername =
+ JabberActivator.getConfigurationService().getString(
+ ProxyInfo.CONNECTION_PROXY_USERNAME_PROPERTY_NAME);
+ String globalProxyPassword =
+ JabberActivator.getConfigurationService().getString(
+ ProxyInfo.CONNECTION_PROXY_PASSWORD_PROPERTY_NAME);
+ if(globalProxyAddress == null ||
+ globalProxyAddress.length() <= 0)
+ {
+ throw new OperationFailedException(
+ "Missing Proxy Address",
+ OperationFailedException.INVALID_ACCOUNT_PROPERTIES);
+ }
+ try
+ {
+ proxy = new org.jivesoftware.smack.proxy.ProxyInfo(
+ Enum.valueOf(org.jivesoftware.smack.proxy.ProxyInfo.
+ ProxyType.class, globalProxyType),
+ globalProxyAddress, globalProxyPort,
+ globalProxyUsername, globalProxyPassword);
+ }
+ catch(IllegalArgumentException e)
+ {
+ logger.error("Invalid value for smack proxy enum", e);
+ proxy = null;
+ }
+ }
+ }
+
+ /**
+ * Connects xmpp connection and login. Returning the state whether is it
+ * final - Abort due to certificate cancel or keep trying cause only current
+ * address has failed or stop trying cause we succeeded.
+ * @param address the address to connect to
+ * @param serviceName the service name to use
+ * @param userName the username to use
+ * @param resource and the resource.
+ * @param loginStrategy the login strategy to use
+ * @return return the state how to continue the connect process.
+ * @throws XMPPException if we cannot connect for some reason
+ */
+ private ConnectState connectAndLogin(
+ InetSocketAddress address, String serviceName,
+ String userName, String resource,
+ JabberLoginStrategy loginStrategy)
+ throws XMPPException
+ {
+ ConnectionConfiguration confConn = new ConnectionConfiguration(
+ address.getAddress().getHostAddress(),
+ address.getPort(),
+ serviceName, proxy
+ );
+
+ confConn.setReconnectionAllowed(false);
+ boolean tlsRequired = loginStrategy.isTlsRequired();
+
+ // user have the possibility to disable TLS but in this case, it will
+ // not be able to connect to a server which requires TLS
+ confConn.setSecurityMode(
+ tlsRequired ? ConnectionConfiguration.SecurityMode.required :
+ ConnectionConfiguration.SecurityMode.enabled);
+
+ if(connection != null)
+ {
+ logger.error("Connection is not null and isConnected:"
+ + connection.isConnected(),
+ new Exception("Trace possible duplicate connections: " +
+ getAccountID().getAccountAddress()));
+ disconnectAndCleanConnection();
+ }
+
+ connection = new XMPPConnection(confConn);
+
+ try
+ {
+ CertificateService cvs =
+ getCertificateVerificationService();
+ if(cvs != null)
+ {
+ SSLContext sslContext = loginStrategy.createSslContext(cvs,
+ getTrustManager(cvs, serviceName));
+
+ // log SSL/TLS algorithms and protocols
+ if (logger.isDebugEnabled())
+ {
+ final StringBuilder buff = new StringBuilder();
+ buff.append("Available TLS protocols and algorithms:\n");
+ buff.append("Default protocols: ");
+ buff.append(Arrays.toString(
+ sslContext.getDefaultSSLParameters().getProtocols()));
+ buff.append("\n");
+ buff.append("Supported protocols: ");
+ buff.append(Arrays.toString(
+ sslContext.getSupportedSSLParameters().getProtocols()));
+ buff.append("\n");
+ buff.append("Default cipher suites: ");
+ buff.append(Arrays.toString(
+ sslContext.getDefaultSSLParameters()
+ .getCipherSuites()));
+ buff.append("\n");
+ buff.append("Supported cipher suites: ");
+ buff.append(Arrays.toString(
+ sslContext.getSupportedSSLParameters()
+ .getCipherSuites()));
+ logger.debug(buff.toString());
+ }
+
+ connection.setCustomSslContext(sslContext);
+ }
+ else if (tlsRequired)
+ throw new XMPPException(
+ "Certificate verification service is "
+ + "unavailable and TLS is required");
+ }
+ catch(GeneralSecurityException e)
+ {
+ logger.error("Error creating custom trust manager", e);
+ throw new XMPPException("Error creating custom trust manager", e);
+ }
+
+ if(debugger == null)
+ debugger = new SmackPacketDebugger();
+
+ // sets the debugger
+ debugger.setConnection(connection);
+ connection.addPacketListener(debugger, null);
+ connection.addPacketInterceptor(debugger, null);
+
+ connection.connect();
+
+ setTrafficClass();
+
+ if(abortConnecting)
+ {
+ abortConnecting = false;
+ disconnectAndCleanConnection();
+
+ return ConnectState.ABORT_CONNECTING;
+ }
+
+ registerServiceDiscoveryManager();
+
+ if(connectionListener == null)
+ {
+ connectionListener = new JabberConnectionListener();
+ }
+
+ if(!connection.isSecureConnection() && tlsRequired)
+ {
+ throw new XMPPException("TLS is required by client");
+ }
+
+ if(!connection.isConnected())
+ {
+ // connection is not connected, lets set state to our connection
+ // as failed seems there is some lag/problem with network
+ // and this way we will inform for it and later reconnect if needed
+ // as IllegalStateException that is thrown within
+ // addConnectionListener is not handled properly
+ disconnectAndCleanConnection();
+
+ logger.error("Connection not established, server not found!");
+
+ eventDuringLogin = null;
+
+ fireRegistrationStateChanged(getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, null);
+
+ return ConnectState.ABORT_CONNECTING;
+ }
+ else
+ {
+ if (connection.getSocket() instanceof SSLSocket)
+ {
+ final SSLSocket sslSocket = (SSLSocket) connection.getSocket();
+ StringBuilder buff = new StringBuilder();
+ buff.append("Chosen TLS protocol and algorithm:\n")
+ .append("Protocol: ").append(sslSocket.getSession()
+ .getProtocol()).append("\n")
+ .append("Cipher suite: ").append(sslSocket.getSession()
+ .getCipherSuite());
+ logger.info(buff.toString());
+
+ if (logger.isDebugEnabled())
+ {
+ buff = new StringBuilder();
+ buff.append("Server TLS certificate chain:\n");
+ try
+ {
+ buff.append(Arrays.toString(
+ sslSocket.getSession().getPeerCertificates()));
+ }
+ catch (SSLPeerUnverifiedException ex)
+ {
+ buff.append("");
+ }
+ logger.debug(buff.toString());
+ }
+ }
+
+ connection.addConnectionListener(connectionListener);
+ }
+
+ if(abortConnecting)
+ {
+ abortConnecting = false;
+ disconnectAndCleanConnection();
+
+ return ConnectState.ABORT_CONNECTING;
+ }
+
+ fireRegistrationStateChanged(
+ getRegistrationState()
+ , RegistrationState.REGISTERING
+ , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
+ , null);
+
+ if (!loginStrategy.login(connection, userName, resource))
+ {
+ disconnectAndCleanConnection();
+ eventDuringLogin = null;
+ fireRegistrationStateChanged(
+ getRegistrationState(),
+ // not auth failed, or there would be no info-popup
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED,
+ loginStrategy.getClass().getName() + " requests abort");
+
+ return ConnectState.ABORT_CONNECTING;
+ }
+
+ if(connection.isAuthenticated())
+ {
+ eventDuringLogin = null;
+
+ fireRegistrationStateChanged(
+ getRegistrationState(),
+ RegistrationState.REGISTERED,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null);
+
+ return ConnectState.STOP_TRYING;
+ }
+ else
+ {
+ disconnectAndCleanConnection();
+
+ eventDuringLogin = null;
+
+ fireRegistrationStateChanged(
+ getRegistrationState()
+ , RegistrationState.UNREGISTERED
+ , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
+ , null);
+
+ return ConnectState.CONTINUE_TRYING;
+ }
+ }
+
+ /**
+ * Gets the TrustManager that should be used for the specified service
+ *
+ * @param serviceName the service name
+ * @param cvs The CertificateVerificationService to retrieve the
+ * trust manager
+ * @return the trust manager
+ */
+ private X509TrustManager getTrustManager(CertificateService cvs,
+ String serviceName)
+ throws GeneralSecurityException
+ {
+ return new HostTrustManager(
+ cvs.getTrustManager(
+ Arrays.asList(new String[]{
+ serviceName,
+ "_xmpp-client." + serviceName
+ })
+ )
+ );
+ }
+
+ /**
+ * Registers our ServiceDiscoveryManager
+ */
+ private void registerServiceDiscoveryManager()
+ {
+ // we setup supported features no packets are actually sent
+ //during feature registration so we'd better do it here so that
+ //our first presence update would contain a caps with the right
+ //features.
+ String name
+ = System.getProperty(
+ "sip-communicator.application.name",
+ "SIP Communicator ")
+ + System.getProperty("sip-communicator.version","SVN");
+
+ ServiceDiscoveryManager.setIdentityName(name);
+ ServiceDiscoveryManager.setIdentityType("pc");
+
+ discoveryManager
+ = new ScServiceDiscoveryManager(
+ this,
+ new String[] { "http://jabber.org/protocol/commands"},
+ // Add features Jitsi supports in addition to smack.
+ supportedFeatures.toArray(
+ new String[supportedFeatures.size()]));
+
+ boolean isCallingDisabled
+ = JabberActivator.getConfigurationService()
+ .getBoolean(IS_CALLING_DISABLED, false);
+
+ boolean isCallingDisabledForAccount = false;
+ if (accountID != null && accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.IS_CALLING_DISABLED_FOR_ACCOUNT,
+ false))
+ isCallingDisabled = true;
+
+ if(isGTalkTesting()
+ && !isCallingDisabled
+ && !isCallingDisabledForAccount)
+ {
+ // Add Google Talk "ext" capabilities
+ discoveryManager.addExtFeature(CAPS_GTALK_WEB_VOICE);
+ discoveryManager.addExtFeature(CAPS_GTALK_WEB_VIDEO);
+ discoveryManager.addExtFeature(CAPS_GTALK_WEB_CAMERA);
+ discoveryManager.addFeature(URN_GOOGLE_VOICE);
+ discoveryManager.addFeature(URN_GOOGLE_VIDEO);
+ discoveryManager.addFeature(URN_GOOGLE_CAMERA);
+ }
+
+ /*
+ * Expose the discoveryManager as service-public through the
+ * OperationSetContactCapabilities of this ProtocolProviderService.
+ */
+ if (opsetContactCapabilities != null)
+ opsetContactCapabilities.setDiscoveryManager(discoveryManager);
+ }
+
+ /**
+ * Used to disconnect current connection and clean it.
+ */
+ public void disconnectAndCleanConnection()
+ {
+ if(connection != null)
+ {
+ connection.removeConnectionListener(connectionListener);
+
+ // disconnect anyway cause it will clear any listeners
+ // that maybe added even if its not connected
+ try
+ {
+ OperationSetPersistentPresenceJabberImpl opSet =
+ (OperationSetPersistentPresenceJabberImpl)
+ this.getOperationSet(OperationSetPersistentPresence.class);
+
+ Presence unavailablePresence =
+ new Presence(Presence.Type.unavailable);
+
+ if(opSet != null
+ && !org.jitsi.util.StringUtils
+ .isNullOrEmpty(opSet.getCurrentStatusMessage()))
+ {
+ unavailablePresence.setStatus(
+ opSet.getCurrentStatusMessage());
+ }
+
+ connection.disconnect(unavailablePresence);
+ } catch (Exception e)
+ {}
+
+ connectionListener = null;
+ connection = null;
+ // make it null as it also holds a reference to the old connection
+ // will be created again on new connection
+ try
+ {
+ /*
+ * The discoveryManager is exposed as service-public by the
+ * OperationSetContactCapabilities of this
+ * ProtocolProviderService. No longer expose it because it's
+ * going away.
+ */
+ if (opsetContactCapabilities != null)
+ opsetContactCapabilities.setDiscoveryManager(null);
+ }
+ finally
+ {
+ if(discoveryManager != null)
+ {
+ discoveryManager.stop();
+ discoveryManager = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Ends the registration of this protocol provider with the service.
+ */
+ public void unregister()
+ {
+ unregister(true);
+ }
+
+ /**
+ * Unregister and fire the event if requested
+ * @param fireEvent boolean
+ */
+ public void unregister(boolean fireEvent)
+ {
+ synchronized(initializationLock)
+ {
+ if(fireEvent)
+ {
+ eventDuringLogin = null;
+ fireRegistrationStateChanged(
+ getRegistrationState()
+ , RegistrationState.UNREGISTERING
+ , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
+ , null);
+ }
+
+ disconnectAndCleanConnection();
+
+ RegistrationState currRegState = getRegistrationState();
+
+ if(fireEvent)
+ {
+ eventDuringLogin = null;
+ fireRegistrationStateChanged(
+ currRegState,
+ RegistrationState.UNREGISTERED,
+ RegistrationStateChangeEvent.REASON_USER_REQUEST, null);
+ }
+ }
+ }
+
+ /**
+ * Returns the short name of the protocol that the implementation of this
+ * provider is based upon (like SIP, Jabber, ICQ/AIM, or others for
+ * example).
+ *
+ * @return a String containing the short name of the protocol this
+ * service is taking care of.
+ */
+ public String getProtocolName()
+ {
+ return ProtocolNames.JABBER;
+ }
+
+ /**
+ * Initialized the service implementation, and puts it in a sate where it
+ * could interoperate with other services. It is strongly recommended that
+ * properties in this Map be mapped to property names as specified by
+ * AccountProperties.
+ *
+ * @param screenname the account id/uin/screenname of the account that
+ * we're about to create
+ * @param accountID the identifier of the account that this protocol
+ * provider represents.
+ *
+ * @see net.java.sip.communicator.service.protocol.AccountID
+ */
+ protected void initialize(String screenname,
+ AccountID accountID)
+ {
+ synchronized(initializationLock)
+ {
+ this.accountID = accountID;
+
+ // in case of modified account, we clear list of supported features
+ // and every state change listeners, otherwise we can have two
+ // OperationSet for same feature and it can causes problem (i.e.
+ // two OperationSetBasicTelephony can launch two ICE negotiations
+ // (with different ufrag/pwd) and peer will failed call. And
+ // by the way user will see two dialog for answering/refusing the
+ // call
+ supportedFeatures.clear();
+ this.clearRegistrationStateChangeListener();
+ this.clearSupportedOperationSet();
+
+ synchronized(providerCreationLock)
+ {
+ if(providerManager == null)
+ {
+ try
+ {
+ ProviderManager.setInstance(new ProviderManagerExt());
+ }
+ catch(Throwable t)
+ {
+ // once loaded if we try to set instance second time
+ // IllegalStateException is thrown
+ }
+ finally
+ {
+ providerManager = ProviderManager.getInstance();
+ }
+ }
+ }
+
+ String protocolIconPath
+ = accountID.getAccountPropertyString(
+ ProtocolProviderFactory.PROTOCOL_ICON_PATH);
+
+ if (protocolIconPath == null)
+ protocolIconPath = "resources/images/protocol/jabber";
+
+ jabberIcon = new ProtocolIconJabberImpl(protocolIconPath);
+
+ jabberStatusEnum
+ = JabberStatusEnum.getJabberStatusEnum(protocolIconPath);
+
+ //this feature is mandatory to be compliant with Service Discovery
+ supportedFeatures.add("http://jabber.org/protocol/disco#info");
+
+ String keepAliveStrValue
+ = accountID.getAccountPropertyString(
+ ProtocolProviderFactory.KEEP_ALIVE_METHOD);
+
+ InfoRetreiver infoRetreiver = new InfoRetreiver(this, screenname);
+
+ //initialize the presence OperationSet
+ OperationSetPersistentPresenceJabberImpl persistentPresence =
+ new OperationSetPersistentPresenceJabberImpl(this, infoRetreiver);
+
+ addSupportedOperationSet(
+ OperationSetPersistentPresence.class,
+ persistentPresence);
+ // TODO: add the feature, if any, corresponding to persistent
+ // presence, if someone knows
+ // supportedFeatures.add(_PRESENCE_);
+
+ //register it once again for those that simply need presence
+ addSupportedOperationSet(
+ OperationSetPresence.class,
+ persistentPresence);
+
+ if(accountID.getAccountPropertyString(
+ ProtocolProviderFactory.ACCOUNT_READ_ONLY_GROUPS) != null)
+ {
+ addSupportedOperationSet(
+ OperationSetPersistentPresencePermissions.class,
+ new OperationSetPersistentPresencePermissionsJabberImpl(
+ this));
+ }
+
+ //initialize the IM operation set
+ OperationSetBasicInstantMessagingJabberImpl basicInstantMessaging =
+ new OperationSetBasicInstantMessagingJabberImpl(this);
+
+ if (keepAliveStrValue == null
+ || keepAliveStrValue.equalsIgnoreCase("XEP-0199"))
+ {
+ if(keepAliveManager == null)
+ keepAliveManager = new KeepAliveManager(this);
+ }
+
+ addSupportedOperationSet(
+ OperationSetBasicInstantMessaging.class,
+ basicInstantMessaging);
+
+ // The http://jabber.org/protocol/xhtml-im feature is included
+ // already in smack.
+
+ addSupportedOperationSet(
+ OperationSetExtendedAuthorizations.class,
+ new OperationSetExtendedAuthorizationsJabberImpl(
+ this,
+ persistentPresence));
+
+ //initialize the Whiteboard operation set
+ addSupportedOperationSet(
+ OperationSetWhiteboarding.class,
+ new OperationSetWhiteboardingJabberImpl(this));
+
+ //initialize the typing notifications operation set
+ addSupportedOperationSet(
+ OperationSetTypingNotifications.class,
+ new OperationSetTypingNotificationsJabberImpl(this));
+
+ // The http://jabber.org/protocol/chatstates feature implemented in
+ // OperationSetTypingNotifications is included already in smack.
+
+ //initialize the multi user chat operation set
+ addSupportedOperationSet(
+ OperationSetMultiUserChat.class,
+ new OperationSetMultiUserChatJabberImpl(this));
+
+ addSupportedOperationSet(
+ OperationSetServerStoredContactInfo.class,
+ new OperationSetServerStoredContactInfoJabberImpl(
+ infoRetreiver));
+
+ OperationSetServerStoredAccountInfo accountInfo =
+ new OperationSetServerStoredAccountInfoJabberImpl(this,
+ infoRetreiver,
+ screenname);
+
+ addSupportedOperationSet(
+ OperationSetServerStoredAccountInfo.class,
+ accountInfo);
+
+ // Initialize avatar operation set
+ addSupportedOperationSet(
+ OperationSetAvatar.class,
+ new OperationSetAvatarJabberImpl(this, accountInfo));
+
+ // initialize the file transfer operation set
+ addSupportedOperationSet(
+ OperationSetFileTransfer.class,
+ new OperationSetFileTransferJabberImpl(this));
+
+ addSupportedOperationSet(
+ OperationSetInstantMessageTransform.class,
+ new OperationSetInstantMessageTransformImpl());
+
+ // Include features we're supporting in addition to the four
+ // included by smack itself:
+ // http://jabber.org/protocol/si/profile/file-transfer
+ // http://jabber.org/protocol/si
+ // http://jabber.org/protocol/bytestreams
+ // http://jabber.org/protocol/ibb
+ supportedFeatures.add("urn:xmpp:thumbs:0");
+ supportedFeatures.add("urn:xmpp:bob");
+
+ // initialize the thumbnailed file factory operation set
+ addSupportedOperationSet(
+ OperationSetThumbnailedFileFactory.class,
+ new OperationSetThumbnailedFileFactoryImpl());
+
+ // TODO: this is the "main" feature to advertise when a client
+ // support muc. We have to add some features for
+ // specific functionality we support in muc.
+ // see http://www.xmpp.org/extensions/xep-0045.html
+
+ // The http://jabber.org/protocol/muc feature is already included in
+ // smack.
+ supportedFeatures.add("http://jabber.org/protocol/muc#rooms");
+ supportedFeatures.add("http://jabber.org/protocol/muc#traffic");
+
+ // RTP HDR extension
+ supportedFeatures.add(URN_XMPP_JINGLE_RTP_HDREXT);
+
+ //register our jingle provider
+ providerManager.addIQProvider( JingleIQ.ELEMENT_NAME,
+ JingleIQ.NAMESPACE,
+ new JingleIQProvider());
+
+ // register our input event provider
+ providerManager.addIQProvider(InputEvtIQ.ELEMENT_NAME,
+ InputEvtIQ.NAMESPACE,
+ new InputEvtIQProvider());
+
+ // register our coin provider
+ providerManager.addIQProvider(CoinIQ.ELEMENT_NAME,
+ CoinIQ.NAMESPACE,
+ new CoinIQProvider());
+ supportedFeatures.add(URN_XMPP_JINGLE_COIN);
+
+ // register our JingleInfo provider
+ providerManager.addIQProvider(JingleInfoQueryIQ.ELEMENT_NAME,
+ JingleInfoQueryIQ.NAMESPACE,
+ new JingleInfoQueryIQProvider());
+
+ // Jitsi Videobridge IQProvider and PacketExtensionProvider
+ providerManager.addIQProvider(
+ ColibriConferenceIQ.ELEMENT_NAME,
+ ColibriConferenceIQ.NAMESPACE,
+ new ColibriIQProvider());
+
+ providerManager.addExtensionProvider(
+ ConferenceDescriptionPacketExtension.ELEMENT_NAME,
+ ConferenceDescriptionPacketExtension.NAMESPACE,
+ new ConferenceDescriptionPacketExtension.Provider());
+
+ providerManager.addExtensionProvider(
+ CarbonPacketExtension.RECEIVED_ELEMENT_NAME,
+ CarbonPacketExtension.NAMESPACE,
+ new CarbonPacketExtension.Provider(
+ CarbonPacketExtension.RECEIVED_ELEMENT_NAME));
+
+ providerManager.addExtensionProvider(
+ CarbonPacketExtension.SENT_ELEMENT_NAME,
+ CarbonPacketExtension.NAMESPACE,
+ new CarbonPacketExtension.Provider(
+ CarbonPacketExtension.SENT_ELEMENT_NAME));
+
+ //initialize the telephony operation set
+ boolean isCallingDisabled
+ = JabberActivator.getConfigurationService()
+ .getBoolean(IS_CALLING_DISABLED, false);
+
+ boolean isCallingDisabledForAccount
+ = accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.IS_CALLING_DISABLED_FOR_ACCOUNT,
+ false);
+
+ // Check if calling is enabled.
+ if (!isCallingDisabled && !isCallingDisabledForAccount)
+ {
+ OperationSetBasicTelephonyJabberImpl basicTelephony
+ = new OperationSetBasicTelephonyJabberImpl(this);
+
+ addSupportedOperationSet(
+ OperationSetAdvancedTelephony.class,
+ basicTelephony);
+ addSupportedOperationSet(
+ OperationSetBasicTelephony.class,
+ basicTelephony);
+ addSupportedOperationSet(
+ OperationSetSecureZrtpTelephony.class,
+ basicTelephony);
+ addSupportedOperationSet(
+ OperationSetSecureSDesTelephony.class,
+ basicTelephony);
+
+ // initialize video telephony OperationSet
+ addSupportedOperationSet(
+ OperationSetVideoTelephony.class,
+ new OperationSetVideoTelephonyJabberImpl(basicTelephony));
+
+ addSupportedOperationSet(
+ OperationSetTelephonyConferencing.class,
+ new OperationSetTelephonyConferencingJabberImpl(this));
+
+ addSupportedOperationSet(
+ OperationSetBasicAutoAnswer.class,
+ new OperationSetAutoAnswerJabberImpl(this));
+
+ addSupportedOperationSet(
+ OperationSetResourceAwareTelephony.class,
+ new OperationSetResAwareTelephonyJabberImpl(basicTelephony));
+
+ // Only init video bridge if enabled
+ boolean isVideobridgeDisabled
+ = JabberActivator.getConfigurationService()
+ .getBoolean(OperationSetVideoBridge.
+ IS_VIDEO_BRIDGE_DISABLED, false);
+
+ if (!isVideobridgeDisabled)
+ {
+ // init video bridge
+ addSupportedOperationSet(
+ OperationSetVideoBridge.class,
+ new OperationSetVideoBridgeImpl(this));
+ }
+
+ // init DTMF
+ OperationSetDTMFJabberImpl operationSetDTMFSip
+ = new OperationSetDTMFJabberImpl(this);
+ addSupportedOperationSet(
+ OperationSetDTMF.class, operationSetDTMFSip);
+
+ addJingleFeatures();
+
+ // Check if desktop streaming is enabled.
+ boolean isDesktopStreamingDisabled
+ = JabberActivator.getConfigurationService()
+ .getBoolean(IS_DESKTOP_STREAMING_DISABLED, false);
+
+ boolean isAccountDesktopStreamingDisabled
+ = accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.IS_DESKTOP_STREAMING_DISABLED,
+ false);
+
+ if (!isDesktopStreamingDisabled
+ && !isAccountDesktopStreamingDisabled)
+ {
+ // initialize desktop streaming OperationSet
+ addSupportedOperationSet(
+ OperationSetDesktopStreaming.class,
+ new OperationSetDesktopStreamingJabberImpl(
+ basicTelephony));
+
+ if(!accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory
+ .IS_DESKTOP_REMOTE_CONTROL_DISABLED,
+ false))
+ {
+ // initialize desktop sharing OperationSets
+ addSupportedOperationSet(
+ OperationSetDesktopSharingServer.class,
+ new OperationSetDesktopSharingServerJabberImpl(
+ basicTelephony));
+
+ // Adds extension to support remote control as a sharing
+ // server (sharer).
+ supportedFeatures.add(InputEvtIQ.NAMESPACE_SERVER);
+
+ addSupportedOperationSet(
+ OperationSetDesktopSharingClient.class,
+ new OperationSetDesktopSharingClientJabberImpl(this)
+ );
+ // Adds extension to support remote control as a sharing
+ // client (sharer).
+ supportedFeatures.add(InputEvtIQ.NAMESPACE_CLIENT);
+ }
+ }
+ }
+
+ // OperationSetContactCapabilities
+ opsetContactCapabilities
+ = new OperationSetContactCapabilitiesJabberImpl(this);
+ if (discoveryManager != null)
+ opsetContactCapabilities.setDiscoveryManager(discoveryManager);
+ addSupportedOperationSet(
+ OperationSetContactCapabilities.class,
+ opsetContactCapabilities);
+
+ addSupportedOperationSet(
+ OperationSetGenericNotifications.class,
+ new OperationSetGenericNotificationsJabberImpl(this));
+
+ supportedFeatures.add("jabber:iq:version");
+ if(versionManager == null)
+ versionManager = new VersionManager(this);
+
+ supportedFeatures.add(MessageCorrectionExtension.NAMESPACE);
+ addSupportedOperationSet(OperationSetMessageCorrection.class,
+ basicInstantMessaging);
+
+ OperationSetChangePassword opsetChangePassword
+ = new OperationSetChangePasswordJabberImpl(this);
+ addSupportedOperationSet(OperationSetChangePassword.class,
+ opsetChangePassword);
+
+ OperationSetCusaxUtils opsetCusaxCusaxUtils
+ = new OperationSetCusaxUtilsJabberImpl(this);
+ addSupportedOperationSet(OperationSetCusaxUtils.class,
+ opsetCusaxCusaxUtils);
+
+ boolean isUserSearchEnabled = accountID.getAccountPropertyBoolean(
+ IS_USER_SEARCH_ENABLED_PROPERTY, false);
+ if(isUserSearchEnabled)
+ {
+ addSupportedOperationSet(OperationSetUserSearch.class,
+ new OperationSetUserSearchJabberImpl(this));
+ }
+
+ OperationSetTLS opsetTLS
+ = new OperationSetTLSJabberImpl(this);
+ addSupportedOperationSet(OperationSetTLS.class,
+ opsetTLS);
+
+ isInitialized = true;
+ }
+ }
+
+ /**
+ * Adds Jingle related features to the supported features.
+ */
+ private void addJingleFeatures()
+ {
+ // Add Jingle features to supported features.
+ supportedFeatures.add(URN_XMPP_JINGLE);
+ supportedFeatures.add(URN_XMPP_JINGLE_RTP);
+ supportedFeatures.add(URN_XMPP_JINGLE_RAW_UDP_0);
+
+ /*
+ * Reflect the preference of the user with respect to the use of
+ * ICE.
+ */
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.IS_USE_ICE,
+ true))
+ {
+ supportedFeatures.add(URN_XMPP_JINGLE_ICE_UDP_1);
+ }
+
+ supportedFeatures.add(URN_XMPP_JINGLE_RTP_AUDIO);
+ supportedFeatures.add(URN_XMPP_JINGLE_RTP_VIDEO);
+ supportedFeatures.add(URN_XMPP_JINGLE_RTP_ZRTP);
+
+ /*
+ * Reflect the preference of the user with respect to the use of
+ * Jingle Nodes.
+ */
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactoryJabberImpl.IS_USE_JINGLE_NODES,
+ true))
+ {
+ supportedFeatures.add(URN_XMPP_JINGLE_NODES);
+ }
+
+ // XEP-0251: Jingle Session Transfer
+ supportedFeatures.add(URN_XMPP_JINGLE_TRANSFER_0);
+
+ // XEP-0320: Use of DTLS-SRTP in Jingle Sessions
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ SrtpControlType.DTLS_SRTP))
+ {
+ supportedFeatures.add(URN_XMPP_JINGLE_DTLS_SRTP);
+ }
+ }
+
+ /**
+ * Makes the service implementation close all open sockets and release
+ * any resources that it might have taken and prepare for
+ * shutdown/garbage collection.
+ */
+ public void shutdown()
+ {
+ synchronized(initializationLock)
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("Killing the Jabber Protocol Provider.");
+
+ //kill all active calls
+ OperationSetBasicTelephonyJabberImpl telephony
+ = (OperationSetBasicTelephonyJabberImpl)getOperationSet(
+ OperationSetBasicTelephony.class);
+ if (telephony != null)
+ {
+ telephony.shutdown();
+ }
+
+ disconnectAndCleanConnection();
+
+ isInitialized = false;
+ }
+ }
+
+ /**
+ * Returns true if the provider service implementation is initialized and
+ * ready for use by other services, and false otherwise.
+ *
+ * @return true if the provider is initialized and ready for use and false
+ * otherwise
+ */
+ public boolean isInitialized()
+ {
+ return isInitialized;
+ }
+
+ /**
+ * Returns the AccountID that uniquely identifies the account represented
+ * by this instance of the ProtocolProviderService.
+ * @return the id of the account represented by this provider.
+ */
+ public AccountID getAccountID()
+ {
+ return accountID;
+ }
+
+ /**
+ * Returns the XMPPConnectionopened by this provider
+ * @return a reference to the XMPPConnection last opened by this
+ * provider.
+ */
+ public XMPPConnection getConnection()
+ {
+ return connection;
+ }
+
+ /**
+ * Determines whether a specific XMPPException signals that
+ * attempted authentication has failed.
+ *
+ * @param ex the XMPPException which is to be determined whether it
+ * signals that attempted authentication has failed
+ * @return true if the specified ex signals that attempted
+ * authentication has failed; otherwise, false
+ */
+ private boolean isAuthenticationFailed(XMPPException ex)
+ {
+ String exMsg = ex.getMessage().toLowerCase();
+
+ // as there are no types or reasons for XMPPException
+ // we try determine the reason according to their message
+ // all messages that were found in smack 3.1.0 were took in count
+ return
+ ((exMsg.indexOf("sasl authentication") != -1)
+ && (exMsg.indexOf("failed") != -1))
+ || (exMsg.indexOf(
+ "does not support compatible authentication mechanism")
+ != -1)
+ || (exMsg.indexOf("unable to determine password") != -1);
+ }
+
+ /**
+ * Tries to determine the appropriate message and status to fire,
+ * according the exception.
+ *
+ * @param ex the {@link XMPPException} that caused the state change.
+ */
+ private void fireRegistrationStateChanged(XMPPException ex)
+ {
+ int reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
+ RegistrationState regState = RegistrationState.UNREGISTERED;
+ String reasonStr = null;
+
+ Throwable wrappedEx = ex.getWrappedThrowable();
+ if(wrappedEx != null
+ && (wrappedEx instanceof UnknownHostException
+ || wrappedEx instanceof ConnectException
+ || wrappedEx instanceof SocketException))
+ {
+ reason = RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND;
+ regState = RegistrationState.CONNECTION_FAILED;
+ }
+ else
+ {
+ String exMsg = ex.getMessage().toLowerCase();
+
+ // as there are no types or reasons for XMPPException
+ // we try determine the reason according to their message
+ // all messages that were found in smack 3.1.0 were took in count
+ if(isAuthenticationFailed(ex))
+ {
+ JabberActivator.getProtocolProviderFactory().
+ storePassword(getAccountID(), null);
+
+ reason = RegistrationStateChangeEvent
+ .REASON_AUTHENTICATION_FAILED;
+
+ regState = RegistrationState.AUTHENTICATION_FAILED;
+
+ fireRegistrationStateChanged(
+ getRegistrationState(), regState, reason, null);
+
+ // Try to reregister and to ask user for a new password.
+ reregister(SecurityAuthority.WRONG_PASSWORD);
+
+ return;
+ }
+ else if(exMsg.indexOf("no response from the server") != -1
+ || exMsg.indexOf("connection failed") != -1)
+ {
+ reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
+ regState = RegistrationState.CONNECTION_FAILED;
+ }
+ else if(exMsg.indexOf("tls is required") != -1)
+ {
+ regState = RegistrationState.AUTHENTICATION_FAILED;
+ reason = RegistrationStateChangeEvent.REASON_TLS_REQUIRED;
+ }
+ }
+
+ if(regState == RegistrationState.UNREGISTERED
+ || regState == RegistrationState.CONNECTION_FAILED)
+ {
+ // we fired that for some reason we are going offline
+ // lets clean the connection state for any future connections
+ disconnectAndCleanConnection();
+ }
+
+ fireRegistrationStateChanged(
+ getRegistrationState(), regState, reason, reasonStr);
+ }
+
+ /**
+ * Enable to listen for jabber connection events
+ */
+ private class JabberConnectionListener
+ implements ConnectionListener
+ {
+ /**
+ * Implements connectionClosed from ConnectionListener
+ */
+ public void connectionClosed()
+ {
+ // if we are in the middle of connecting process
+ // do not fire events, will do it later when the method
+ // connectAndLogin finishes its work
+ synchronized(connectAndLoginLock)
+ {
+ if(inConnectAndLogin)
+ {
+ eventDuringLogin = new RegistrationStateChangeEvent(
+ ProtocolProviderServiceJabberImpl.this,
+ getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
+ null);
+ return;
+ }
+ }
+ // fire that a connection failed, the reconnection mechanism
+ // will look after us and will clean us, other wise we can do
+ // a dead lock (connection closed is called
+ // within xmppConneciton and calling disconnect again can lock it)
+ fireRegistrationStateChanged(
+ getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
+ null);
+ }
+
+ /**
+ * Implements connectionClosedOnError from
+ * ConnectionListener.
+ *
+ * @param exception contains information on the error.
+ */
+ public void connectionClosedOnError(Exception exception)
+ {
+ logger.error("connectionClosedOnError " +
+ exception.getLocalizedMessage());
+
+ if(exception instanceof XMPPException)
+ {
+ StreamError err = ((XMPPException)exception).getStreamError();
+
+ if(err != null && err.getCode().equals(
+ XMPPError.Condition.conflict.toString()))
+ {
+ // if we are in the middle of connecting process
+ // do not fire events, will do it later when the method
+ // connectAndLogin finishes its work
+ synchronized(connectAndLoginLock)
+ {
+ if(inConnectAndLogin)
+ {
+ eventDuringLogin = new RegistrationStateChangeEvent(
+ ProtocolProviderServiceJabberImpl.this,
+ getRegistrationState(),
+ RegistrationState.UNREGISTERED,
+ RegistrationStateChangeEvent.REASON_MULTIPLE_LOGINS,
+ "Connecting multiple times with the same resource");
+ return;
+ }
+ }
+
+ disconnectAndCleanConnection();
+
+ fireRegistrationStateChanged(getRegistrationState(),
+ RegistrationState.UNREGISTERED,
+ RegistrationStateChangeEvent.REASON_MULTIPLE_LOGINS,
+ "Connecting multiple times with the same resource");
+
+ return;
+ }
+ } // Ignore certificate exceptions as we handle them elsewhere
+ else if(exception instanceof SSLHandshakeException &&
+ exception.getCause() instanceof CertificateException)
+ {
+ return;
+ }
+
+ // if we are in the middle of connecting process
+ // do not fire events, will do it later when the method
+ // connectAndLogin finishes its work
+ synchronized(connectAndLoginLock)
+ {
+ if(inConnectAndLogin)
+ {
+ eventDuringLogin = new RegistrationStateChangeEvent(
+ ProtocolProviderServiceJabberImpl.this,
+ getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
+ exception.getMessage());
+ return;
+ }
+ }
+
+ disconnectAndCleanConnection();
+
+ fireRegistrationStateChanged(getRegistrationState(),
+ RegistrationState.CONNECTION_FAILED,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
+ exception.getMessage());
+ }
+
+ /**
+ * Implements reconnectingIn from ConnectionListener
+ *
+ * @param i delay in seconds for reconnection.
+ */
+ public void reconnectingIn(int i)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("reconnectingIn " + i);
+ }
+
+ /**
+ * Implements reconnectingIn from ConnectionListener
+ */
+ public void reconnectionSuccessful()
+ {
+ if (logger.isInfoEnabled())
+ logger.info("reconnectionSuccessful");
+ }
+
+ /**
+ * Implements reconnectionFailed from
+ * ConnectionListener.
+ *
+ * @param exception description of the failure
+ */
+ public void reconnectionFailed(Exception exception)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("reconnectionFailed ", exception);
+ }
+ }
+
+ /**
+ * Returns the jabber protocol icon.
+ * @return the jabber protocol icon
+ */
+ public ProtocolIcon getProtocolIcon()
+ {
+ return jabberIcon;
+ }
+
+ /**
+ * Returns the current instance of JabberStatusEnum.
+ *
+ * @return the current instance of JabberStatusEnum.
+ */
+ JabberStatusEnum getJabberStatusEnum()
+ {
+ return jabberStatusEnum;
+ }
+
+ /**
+ * Determines if the given list of ext features is supported by the
+ * specified jabber id.
+ *
+ * @param jid the jabber id for which to check
+ * @param extFeatures the list of ext features to check for
+ *
+ * @return true if the list of ext features is supported;
+ * otherwise, false
+ */
+ public boolean isExtFeatureListSupported(String jid, String... extFeatures)
+ {
+ EntityCapsManager capsManager = discoveryManager.getCapsManager();
+ EntityCapsManager.Caps caps = capsManager.getCapsByUser(jid);
+
+ String bypassDomain = accountID.getAccountPropertyString(
+ "TELEPHONY_BYPASS_GTALK_CAPS");
+ String domain = StringUtils.parseServer(jid);
+ boolean domainEquals = domain.equals(bypassDomain);
+
+ if(caps != null && caps.ext != null)
+ {
+ String exts[] = caps.ext.split(" ");
+ boolean found = false;
+
+ for(String extFeature : extFeatures)
+ {
+ // in case we have a domain that have to bypass GTalk caps
+ if(extFeature.equals(CAPS_GTALK_WEB_VOICE) && domainEquals)
+ {
+ return true;
+ }
+
+ found = false;
+ for(String ext : exts)
+ {
+ if(ext.equals(extFeature))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found)
+ {
+ break;
+ }
+ }
+
+ return found;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if the given list of features is supported by the
+ * specified jabber id.
+ *
+ * @param jid the jabber id for which to check
+ * @param features the list of features to check for
+ *
+ * @return true if the list of features is supported; otherwise,
+ * false
+ */
+ public boolean isFeatureListSupported(String jid, String... features)
+ {
+ boolean isFeatureListSupported = true;
+
+ try
+ {
+ if(discoveryManager == null)
+ return isFeatureListSupported;
+
+ DiscoverInfo featureInfo =
+ discoveryManager.discoverInfoNonBlocking(jid);
+
+ if(featureInfo == null)
+ return isFeatureListSupported;
+
+ for (String feature : features)
+ {
+ if (!featureInfo.containsFeature(feature))
+ {
+ // If one is not supported we return false and don't check
+ // the others.
+ isFeatureListSupported = false;
+ break;
+ }
+ }
+ }
+ catch (XMPPException e)
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Failed to retrive discovery info.", e);
+ }
+ return isFeatureListSupported;
+ }
+
+ /**
+ * Determines if the given list of features is supported by the
+ * specified jabber id.
+ *
+ * @param jid the jabber id that we'd like to get information about
+ * @param feature the feature to check for
+ *
+ * @return true if the list of features is supported, otherwise
+ * returns false
+ */
+ public boolean isFeatureSupported(String jid, String feature)
+ {
+ return isFeatureListSupported(jid, feature);
+ }
+
+ /**
+ * Returns the full jabber id (jid) corresponding to the given contact. If
+ * the provider is not connected returns null.
+ *
+ * @param contact the contact, for which we're looking for a jid
+ * @return the jid of the specified contact or null if the provider is not
+ * yet connected;
+ */
+ public String getFullJid(Contact contact)
+ {
+ return getFullJid(contact.getAddress());
+ }
+
+ /**
+ * Returns the full jabber id (jid) corresponding to the given bare jid. If
+ * the provider is not connected returns null.
+ *
+ * @param bareJid the bare contact address (i.e. no resource) whose full
+ * jid we are looking for.
+ * @return the jid of the specified contact or null if the provider is not
+ * yet connected;
+ */
+ public String getFullJid(String bareJid)
+ {
+ XMPPConnection connection = getConnection();
+
+ // when we are not connected there is no full jid
+ if (connection != null && connection.isConnected())
+ {
+ Roster roster = connection.getRoster();
+
+ if (roster != null)
+ return roster.getPresence(bareJid).getFrom();
+ }
+ return null;
+ }
+
+ /**
+ * The trust manager which asks the client whether to trust particular
+ * certificate which is not globally trusted.
+ */
+ private class HostTrustManager
+ implements X509TrustManager
+ {
+ /**
+ * The default trust manager.
+ */
+ private final X509TrustManager tm;
+
+ /**
+ * Creates the custom trust manager.
+ * @param tm the default trust manager.
+ */
+ HostTrustManager(X509TrustManager tm)
+ {
+ this.tm = tm;
+ }
+
+ /**
+ * Not used.
+ *
+ * @return nothing.
+ */
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return new X509Certificate[0];
+ }
+
+ /**
+ * Not used.
+ * @param chain the cert chain.
+ * @param authType authentication type like: RSA.
+ * @throws CertificateException never
+ * @throws UnsupportedOperationException always
+ */
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException, UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Check whether a certificate is trusted, if not as user whether he
+ * trust it.
+ * @param chain the certificate chain.
+ * @param authType authentication type like: RSA.
+ * @throws CertificateException not trusted.
+ */
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ abortConnecting = true;
+ try
+ {
+ tm.checkServerTrusted(chain, authType);
+ }
+ catch(CertificateException e)
+ {
+ // notify in a separate thread to avoid a deadlock when a
+ // reg state listener accesses a synchronized XMPPConnection
+ // method (like getRoster)
+ new Thread(new Runnable()
+ {
+ public void run()
+ {
+ fireRegistrationStateChanged(getRegistrationState(),
+ RegistrationState.UNREGISTERED,
+ RegistrationStateChangeEvent.REASON_USER_REQUEST,
+ "Not trusted certificate");
+ }
+ }).start();
+ throw e;
+ }
+
+ if(abortConnecting)
+ {
+ // connect hasn't finished we will continue normally
+ abortConnecting = false;
+ return;
+ }
+ else
+ {
+ // in this situation connect method has finished
+ // and it was disconnected so we wont to connect.
+
+ // register.connect in new thread so we can release the
+ // current connecting thread, otherwise this blocks
+ // jabber
+ new Thread(new Runnable()
+ {
+ public void run()
+ {
+ reregister(SecurityAuthority.CONNECTION_FAILED);
+ }
+ }).start();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns the currently valid {@link ScServiceDiscoveryManager}.
+ *
+ * @return the currently valid {@link ScServiceDiscoveryManager}.
+ */
+ public ScServiceDiscoveryManager getDiscoveryManager()
+ {
+ return discoveryManager;
+ }
+
+ /**
+ * Returns our own Jabber ID.
+ *
+ * @return our own Jabber ID.
+ */
+ public String getOurJID()
+ {
+ String jid = null;
+
+ if (connection != null)
+ jid = connection.getUser();
+
+ if (jid == null)
+ {
+ // seems like the connection is not yet initialized so lets try to
+ // construct our jid ourselves.
+ String accountIDUserID = getAccountID().getUserID();
+ String userID = StringUtils.parseName(accountIDUserID);
+ String serviceName = StringUtils.parseServer(accountIDUserID);
+
+ jid = userID + "@" + serviceName;
+ }
+
+ return jid;
+ }
+
+ /**
+ * Returns the InetAddress that is most likely to be to be used
+ * as a next hop when contacting our XMPP server. This is an utility method
+ * that is used whenever we have to choose one of our local addresses (e.g.
+ * when trying to pick a best candidate for raw udp). It is based on the
+ * assumption that, in absence of any more specific details, chances are
+ * that we will be accessing remote destinations via the same interface
+ * that we are using to access our jabber server.
+ *
+ * @return the InetAddress that is most likely to be to be used
+ * as a next hop when contacting our server.
+ *
+ * @throws IllegalArgumentException if we don't have a valid server.
+ */
+ public InetAddress getNextHop()
+ throws IllegalArgumentException
+ {
+ InetAddress nextHop = null;
+ String nextHopStr = null;
+
+ if ( proxy != null
+ && proxy.getProxyType()
+ != org.jivesoftware.smack.proxy.ProxyInfo.ProxyType.NONE)
+ {
+ nextHopStr = proxy.getProxyAddress();
+ }
+ else
+ {
+ nextHopStr = getConnection().getHost();
+ }
+
+ try
+ {
+ nextHop = NetworkUtils.getInetAddress(nextHopStr);
+ }
+ catch (UnknownHostException ex)
+ {
+ throw new IllegalArgumentException(
+ "seems we don't have a valid next hop.", ex);
+ }
+
+ if(logger.isDebugEnabled())
+ logger.debug("Returning address " + nextHop + " as next hop.");
+
+ return nextHop;
+ }
+
+ /**
+ * Start auto-discovery of JingleNodes tracker/relays.
+ */
+ public void startJingleNodesDiscovery()
+ {
+ // Jingle Nodes Service Initialization
+ final JabberAccountIDImpl accID = (JabberAccountIDImpl)getAccountID();
+ final SmackServiceNode service = new SmackServiceNode(connection,
+ 60000);
+ // make sure SmackServiceNode will clean up when connection is closed
+ connection.addConnectionListener(service);
+
+ for(JingleNodeDescriptor desc : accID.getJingleNodes())
+ {
+ TrackerEntry entry = new TrackerEntry(
+ desc.isRelaySupported() ? TrackerEntry.Type.relay :
+ TrackerEntry.Type.tracker,
+ TrackerEntry.Policy._public,
+ desc.getJID(),
+ JingleChannelIQ.UDP);
+
+ service.addTrackerEntry(entry);
+ }
+
+ new Thread(new JingleNodesServiceDiscovery(
+ service,
+ connection,
+ accID,
+ jingleNodesSyncRoot))
+ .start();
+
+ jingleNodesServiceNode = service;
+ }
+
+ /**
+ * Get the Jingle Nodes service. Note that this method will block until
+ * Jingle Nodes auto discovery (if enabled) finished.
+ *
+ * @return Jingle Nodes service
+ */
+ public SmackServiceNode getJingleNodesServiceNode()
+ {
+ synchronized(jingleNodesSyncRoot)
+ {
+ return jingleNodesServiceNode;
+ }
+ }
+
+ /**
+ * Logs a specific message and associated Throwable cause as an
+ * error using the current Logger and then throws a new
+ * OperationFailedException with the message, a specific error code
+ * and the cause.
+ *
+ * @param message the message to be logged and then wrapped in a new
+ * OperationFailedException
+ * @param errorCode the error code to be assigned to the new
+ * OperationFailedException
+ * @param cause the Throwable that has caused the necessity to log
+ * an error and have a new OperationFailedException thrown
+ * @param logger the logger that we'd like to log the error message
+ * and cause.
+ *
+ * @throws OperationFailedException the exception that we wanted this method
+ * to throw.
+ */
+ public static void throwOperationFailedException( String message,
+ int errorCode,
+ Throwable cause,
+ Logger logger)
+ throws OperationFailedException
+ {
+ logger.error(message, cause);
+
+ if(cause == null)
+ throw new OperationFailedException(message, errorCode);
+ else
+ throw new OperationFailedException(message, errorCode, cause);
+ }
+
+ /**
+ * Used when we need to re-register or someone needs to obtain credentials.
+ * @return the SecurityAuthority.
+ */
+ public SecurityAuthority getAuthority()
+ {
+ return authority;
+ }
+
+ /**
+ * Returns true if gtalktesting is enabled, false otherwise.
+ *
+ * @return true if gtalktesting is enabled, false otherwise.
+ */
+ public boolean isGTalkTesting()
+ {
+ return
+ Boolean.getBoolean("gtalktesting")
+ || JabberActivator.getConfigurationService().getBoolean(
+ "net.java.sip.communicator.impl.protocol.jabber"
+ + ".gtalktesting",
+ false)
+ || accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.IS_USE_GOOGLE_ICE,
+ true);
+ }
+
+ UserCredentials getUserCredentials()
+ {
+ return userCredentials;
+ }
+
+ /**
+ * Returns true if our account is a Gmail or a Google Apps ones.
+ *
+ * @return true if our account is a Gmail or a Google Apps ones.
+ */
+ public boolean isGmailOrGoogleAppsAccount()
+ {
+ String domain = StringUtils.parseServer(
+ getAccountID().getUserID());
+ return isGmailOrGoogleAppsAccount(domain);
+ }
+
+ /**
+ * Returns true if our account is a Gmail or a Google Apps ones.
+ *
+ * @param domain domain to check
+ * @return true if our account is a Gmail or a Google Apps ones.
+ */
+ public static boolean isGmailOrGoogleAppsAccount(String domain)
+ {
+ SRVRecord srvRecords[] = null;
+
+ try
+ {
+ srvRecords = NetworkUtils.getSRVRecords("xmpp-client", "tcp",
+ domain);
+ }
+ catch (ParseException e)
+ {
+ logger.info("Failed to get SRV records for XMPP domain");
+ return false;
+ }
+ catch (DnssecException e)
+ {
+ logger.error("DNSSEC failure while checking for google domains", e);
+ return false;
+ }
+
+ if(srvRecords == null)
+ {
+ return false;
+ }
+
+ for(SRVRecord srv : srvRecords)
+ {
+ if(srv.getTarget().endsWith("google.com") ||
+ srv.getTarget().endsWith("google.com."))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the traffic class for the XMPP signalling socket.
+ */
+ private void setTrafficClass()
+ {
+ Socket s = connection.getSocket();
+
+ if(s != null)
+ {
+ ConfigurationService configService =
+ JabberActivator.getConfigurationService();
+ String dscp = configService.getString(XMPP_DSCP_PROPERTY);
+
+ if(dscp != null)
+ {
+ try
+ {
+ int dscpInt = Integer.parseInt(dscp) << 2;
+
+ if(dscpInt > 0)
+ s.setTrafficClass(dscpInt);
+ }
+ catch (Exception e)
+ {
+ logger.info("Failed to set trafficClass", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the entity ID of the first Jitsi Videobridge associated with
+ * {@link #connection} i.e. provided by the serviceName of
+ * connection.
+ *
+ * @return the entity ID of the first Jitsi Videobridge associated with
+ * connection
+ */
+ public String getJitsiVideobridge()
+ {
+ XMPPConnection connection = getConnection();
+
+ if (connection != null)
+ {
+ ScServiceDiscoveryManager discoveryManager = getDiscoveryManager();
+ String serviceName = connection.getServiceName();
+ DiscoverItems discoverItems = null;
+
+ try
+ {
+ discoverItems = discoveryManager.discoverItems(serviceName);
+ }
+ catch (XMPPException xmppe)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Failed to discover the items associated with"
+ + " Jabber entity: " + serviceName,
+ xmppe);
+ }
+ }
+ if (discoverItems != null)
+ {
+ Iterator discoverItemIter
+ = discoverItems.getItems();
+
+ while (discoverItemIter.hasNext())
+ {
+ DiscoverItems.Item discoverItem = discoverItemIter.next();
+ String entityID = discoverItem.getEntityID();
+ DiscoverInfo discoverInfo = null;
+
+ try
+ {
+ discoverInfo = discoveryManager.discoverInfo(entityID);
+ }
+ catch (XMPPException xmppe)
+ {
+ logger.warn(
+ "Failed to discover information about Jabber"
+ + " entity: " + entityID,
+ xmppe);
+ }
+ if ((discoverInfo != null)
+ && discoverInfo.containsFeature(
+ ColibriConferenceIQ.NAMESPACE))
+ {
+ return entityID;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Load jabber service class, their static context will register
+ * what is needed. Used in android as when using the other jars
+ * these services are loaded from the jar manifest.
+ */
+ private static void loadJabberServiceClasses()
+ {
+ if(!OSUtils.IS_ANDROID)
+ return;
+
+ try
+ {
+ // pre-configure smack in android
+ // just to load class to init their static blocks
+ SmackConfiguration.getVersion();
+ Class.forName(ServiceDiscoveryManager.class.getName());
+
+ Class.forName(DelayInformation.class.getName());
+ Class.forName(org.jivesoftware.smackx
+ .provider.DelayInformationProvider.class.getName());
+ Class.forName(org.jivesoftware.smackx
+ .bytestreams.socks5.Socks5BytestreamManager.class.getName());
+ Class.forName(XHTMLManager.class.getName());
+ Class.forName(org.jivesoftware.smackx
+ .bytestreams.ibb.InBandBytestreamManager.class.getName());
+
+ }
+ catch(ClassNotFoundException e)
+ {
+ logger.error("Error loading classes in smack", e);
+ }
+ }
+
+ /**
+ * Return the SSL socket (if TLS used).
+ * @return The SSL socket or null if not used
+ */
+ public SSLSocket getSSLSocket()
+ {
+ final SSLSocket result;
+ final Socket socket = connection.getSocket();
+ if (socket instanceof SSLSocket)
+ {
+ result = (SSLSocket) socket;
+ }
+ else
+ {
+ result = null;
+ }
+ return result;
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java
index 32e726a71..2ca7d83e6 100644
--- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java
@@ -789,7 +789,7 @@ private boolean updateMediaDescriptionForDtls(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled(
- DtlsControl.PROTO_NAME))
+ SrtpControlType.DTLS_SRTP))
{
/*
* The transport protocol of the media described by localMd should
@@ -1019,7 +1019,7 @@ private boolean updateMediaDescriptionForSDes(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
|| !accountID.isEncryptionProtocolEnabled(
- SDesControl.PROTO_NAME))
+ SrtpControlType.SDES))
{
return false;
}
@@ -1102,7 +1102,7 @@ private boolean updateMediaDescriptionForZrtp(
if(accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
- && accountID.isEncryptionProtocolEnabled(ZrtpControl.PROTO_NAME)
+ && accountID.isEncryptionProtocolEnabled(SrtpControlType.ZRTP)
&& peer.getCall().isSipZrtpAttribute())
{
ZrtpControl zrtpControl
diff --git a/src/net/java/sip/communicator/service/protocol/AccountID.java b/src/net/java/sip/communicator/service/protocol/AccountID.java
index 959bd0a0c..412ce3f1f 100644
--- a/src/net/java/sip/communicator/service/protocol/AccountID.java
+++ b/src/net/java/sip/communicator/service/protocol/AccountID.java
@@ -795,16 +795,16 @@ public void setAccountProperties(Map accountProperties)
* @param encryptionProtocolName The name of the encryption protocol
* ("ZRTP", "SDES" or "MIKEY").
*/
- public boolean isEncryptionProtocolEnabled(String encryptionProtocolName)
+ public boolean isEncryptionProtocolEnabled(SrtpControlType type)
{
// The default value is false, except for ZRTP.
- boolean defaultValue = "ZRTP".equals(encryptionProtocolName);
+ boolean defaultValue = type == SrtpControlType.ZRTP;
return
getAccountPropertyBoolean(
ProtocolProviderFactory.ENCRYPTION_PROTOCOL_STATUS
+ "."
- + encryptionProtocolName,
+ + type.toString(),
defaultValue);
}
diff --git a/src/net/java/sip/communicator/service/protocol/SecurityAccountRegistration.java b/src/net/java/sip/communicator/service/protocol/SecurityAccountRegistration.java
index e4a62ac67..4a1de01a7 100644
--- a/src/net/java/sip/communicator/service/protocol/SecurityAccountRegistration.java
+++ b/src/net/java/sip/communicator/service/protocol/SecurityAccountRegistration.java
@@ -29,9 +29,9 @@ public abstract class SecurityAccountRegistration
public static final List ENCRYPTION_PROTOCOLS
= Collections.unmodifiableList(
Arrays.asList(
- ZrtpControl.PROTO_NAME,
- SDesControl.PROTO_NAME,
- DtlsControl.PROTO_NAME));
+ SrtpControlType.ZRTP.toString(),
+ SrtpControlType.SDES.toString(),
+ SrtpControlType.DTLS_SRTP.toString()));
/**
* Enables support to encrypt calls.