aboutsummaryrefslogtreecommitdiffstats
path: root/src/net
diff options
context:
space:
mode:
authorLyubomir Marinov <lyubomir.marinov@jitsi.org>2013-11-13 22:42:37 +0200
committerLyubomir Marinov <lyubomir.marinov@jitsi.org>2013-11-14 01:12:59 +0200
commit755b17033e7ee6395aa7a140d78e365b6f429c58 (patch)
treeb3fd2265989b7e38d812e6eb8b3221cb4a113851 /src/net
parentc307d5ebab5e5f20c5550d6bf341731b8b47497a (diff)
downloadjitsi-755b17033e7ee6395aa7a140d78e365b6f429c58.zip
jitsi-755b17033e7ee6395aa7a140d78e365b6f429c58.tar.gz
jitsi-755b17033e7ee6395aa7a140d78e365b6f429c58.tar.bz2
Fixes warnings, formatting, naming consistency.
Diffstat (limited to 'src/net')
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/CallManager.java8354
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java5040
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java558
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java1146
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java2770
-rw-r--r--src/net/java/sip/communicator/impl/libjitsi/LibJitsiActivator.java120
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerMediaHandlerJabberGTalkImpl.java1208
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java28
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java3250
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java1147
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java570
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java5714
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java154
-rw-r--r--src/net/java/sip/communicator/service/protocol/CallConference.java1862
-rw-r--r--src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java2518
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java1232
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java2502
-rw-r--r--src/net/java/sip/communicator/service/protocol/media/TransportManager.java1543
18 files changed, 19863 insertions, 19853 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java
index 3af18ee..ca4f8ff 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java
@@ -1,4177 +1,4177 @@
-/*
- * 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.gui.main.call;
-
-import java.awt.*;
-import java.lang.ref.*;
-import java.text.*;
-import java.util.*;
-import java.util.List;
-import java.util.regex.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.customcontrols.*;
-import net.java.sip.communicator.impl.gui.main.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.plugin.desktoputil.transparent.*;
-import net.java.sip.communicator.service.contactlist.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.util.Logger;
-import net.java.sip.communicator.util.account.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.codec.*;
-import org.jitsi.service.neomedia.device.*;
-import org.jitsi.service.neomedia.format.*;
-import org.jitsi.service.resources.*;
-import org.jitsi.util.*;
-
-/**
- * The <tt>CallManager</tt> is the one that handles calls. It contains also
- * the "Call" and "Hang up" buttons panel. Here are handles incoming and
- * outgoing calls from and to the call operation set.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public class CallManager
-{
- /**
- * The <tt>Logger</tt> used by the <tt>CallManager</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger = Logger.getLogger(CallManager.class);
-
- /**
- * The name of the property which indicates whether the user should be
- * warned when starting a desktop sharing session.
- */
- private static final String desktopSharingWarningProperty
- = "net.java.sip.communicator.impl.gui.main"
- + ".call.SHOW_DESKTOP_SHARING_WARNING";
-
- /**
- * The name of the property which indicates whether the preferred provider
- * will be used when calling UIContact (call history).
- */
- private static final String IGNORE_PREFERRED_PROVIDER_PROP
- = "net.java.sip.communicator.impl.gui.main"
- + ".call.IGNORE_PREFERRED_PROVIDER_PROP";
-
- /**
- * The <tt>CallPanel</tt>s opened by <tt>CallManager</tt> (because
- * <tt>CallContainer</tt> does not give access to such lists.)
- */
- private static final Map<CallConference, CallPanel> callPanels
- = new HashMap<CallConference, CallPanel>();
-
- /**
- * A map of active outgoing calls per <tt>UIContactImpl</tt>.
- */
- private static Map<Call, UIContactImpl> uiContactCalls;
-
- /**
- * The group of notifications dedicated to missed calls.
- */
- private static UINotificationGroup missedCallGroup;
-
- /**
- * A <tt>CallListener</tt>.
- */
- public static class GuiCallListener
- extends SwingCallListener
- {
- /**
- * Maps for incoming call handlers. The handlers needs to be created
- * in the protocol thread while their method
- * incomingCallReceivedInEventDispatchThread will be called on EDT.
- * On the protocol thread a call state changed listener is added,
- * if this is done on the EDT there is a almost no gap between incoming
- * CallEvent and call state changed when doing auto answer and we
- * end up with call answered and dialog for incoming call.
- */
- private Map<CallEvent,WeakReference<IncomingCallHandler>>
- inCallHandlers = Collections.synchronizedMap(
- new WeakHashMap<CallEvent,
- WeakReference<IncomingCallHandler>>());
-
- /**
- * Delivers the <tt>CallEvent</tt> in the protocol thread.
- */
- public void incomingCallReceived(CallEvent ev)
- {
- inCallHandlers.put(
- ev,
- new WeakReference<IncomingCallHandler>(
- new IncomingCallHandler(ev.getSourceCall())));
-
- super.incomingCallReceived(ev);
- }
-
- /**
- * Implements {@link CallListener#incomingCallReceived(CallEvent)}. When
- * a call is received, creates a <tt>ReceivedCallDialog</tt> and plays
- * the ring phone sound to the user.
- *
- * @param ev the <tt>CallEvent</tt>
- */
- @Override
- public void incomingCallReceivedInEventDispatchThread(CallEvent ev)
- {
- WeakReference<IncomingCallHandler> ihRef
- = inCallHandlers.remove(ev);
-
- if(ihRef != null)
- {
- ihRef.get().incomingCallReceivedInEventDispatchThread(ev);
- }
- }
-
- /**
- * Implements CallListener.callEnded. Stops sounds that are playing at
- * the moment if there're any. Removes the <tt>CallPanel</tt> and
- * disables the hang-up button.
- *
- * @param ev the <tt>CallEvent</tt> which specifies the <tt>Call</tt>
- * that has ended
- */
- @Override
- public void callEndedInEventDispatchThread(CallEvent ev)
- {
- CallConference callConference = ev.getCallConference();
-
- closeCallContainerIfNotNecessary(callConference);
-
- /*
- * Notify the existing CallPanels about the CallEvent (in case
- * they need to update their UI, for example).
- */
- forwardCallEventToCallPanels(ev);
-
- // If we're currently in the call history view, refresh
- // it.
- TreeContactList contactList
- = GuiActivator.getContactList();
-
- if (contactList.getCurrentFilter().equals(
- TreeContactList.historyFilter))
- {
- contactList.applyFilter(
- TreeContactList.historyFilter);
- }
- }
-
- /**
- * Creates and opens a call dialog. Implements
- * {@link CallListener#outgoingCallCreated(CallEvent)}.
- *
- * @param ev the <tt>CallEvent</tt>
- */
- @Override
- public void outgoingCallCreatedInEventDispatchThread(CallEvent ev)
- {
- Call sourceCall = ev.getSourceCall();
-
- openCallContainerIfNecessary(sourceCall);
-
- /*
- * Notify the existing CallPanels about the CallEvent (in case they
- * need to update their UI, for example).
- */
- forwardCallEventToCallPanels(ev);
- }
- }
-
- /**
- * Handles incoming calls. Must be created on the protocol thread while the
- * method incomingCallReceivedInEventDispatchThread is executed on the EDT.
- */
- private static class IncomingCallHandler
- extends CallChangeAdapter
- {
- /**
- * The dialog shown
- */
- private ReceivedCallDialog receivedCallDialog;
-
- /**
- * Peer name.
- */
- private String peerName;
-
- /**
- * The time of the incoming call.
- */
- private long callTime;
-
- /**
- * Construct
- * @param sourceCall
- */
- IncomingCallHandler(Call sourceCall)
- {
- Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();
-
- if(!peerIter.hasNext())
- {
- return;
- }
-
- peerName = peerIter.next().getDisplayName();
- callTime = System.currentTimeMillis();
-
- sourceCall.addCallChangeListener(this);
- }
-
- /**
- * State has changed.
- * @param ev
- */
- @Override
- public void callStateChanged(final CallChangeEvent ev)
- {
- if(!SwingUtilities.isEventDispatchThread())
- {
- SwingUtilities.invokeLater(
- new Runnable()
- {
- public void run()
- {
- callStateChanged(ev);
- }
- });
- return;
- }
- if (!CallChangeEvent.CALL_STATE_CHANGE
- .equals(ev.getPropertyName()))
- return;
-
- // When the call state changes, we ensure here that the
- // received call notification dialog is closed.
- if (receivedCallDialog != null && receivedCallDialog.isVisible())
- receivedCallDialog.setVisible(false);
-
- // Ensure that the CallDialog is created, because it is the
- // one that listens for CallPeers.
- Object newValue = ev.getNewValue();
- Call call = ev.getSourceCall();
-
- if (CallState.CALL_INITIALIZATION.equals(newValue)
- || CallState.CALL_IN_PROGRESS.equals(newValue))
- {
- openCallContainerIfNecessary(call);
- }
- else if (CallState.CALL_ENDED.equals(newValue))
- {
- if (ev.getOldValue().equals(
- CallState.CALL_INITIALIZATION))
- {
- // If the call was answered elsewhere, don't mark it
- // as missed.
- CallPeerChangeEvent cause = ev.getCause();
-
- if ((cause == null)
- || (cause.getReasonCode()
- != CallPeerChangeEvent
- .NORMAL_CALL_CLEARING))
- {
- addMissedCallNotification(peerName, callTime);
- }
- }
-
- call.removeCallChangeListener(this);
- }
- }
-
- /**
- * Executed on EDT cause will create dialog and will show it.
- * @param ev
- */
- public void incomingCallReceivedInEventDispatchThread(CallEvent ev)
- {
- Call sourceCall = ev.getSourceCall();
- boolean isVideoCall
- = ev.isVideoCall()
- && ConfigurationUtils.hasEnabledVideoFormat(
- sourceCall.getProtocolProvider());
- receivedCallDialog = new ReceivedCallDialog(
- sourceCall,
- isVideoCall,
- (CallManager.getInProgressCalls().size() > 0));
-
- receivedCallDialog.setVisible(true);
-
- Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();
-
- if(!peerIter.hasNext())
- {
- if (receivedCallDialog.isVisible())
- receivedCallDialog.setVisible(false);
- return;
- }
-
- /*
- * Notify the existing CallPanels about the CallEvent (in case they
- * need to update their UI, for example).
- */
- forwardCallEventToCallPanels(ev);
- }
- }
-
- /**
- * Answers the given call.
- *
- * @param call the call to answer
- */
- public static void answerCall(Call call)
- {
- answerCall(call, null, false /* without video */);
- }
-
- /**
- * Answers a specific <tt>Call</tt> with or without video and, optionally,
- * does that in a telephony conference with an existing <tt>Call</tt>.
- *
- * @param call
- * @param existingCall
- * @param video
- */
- private static void answerCall(Call call, Call existingCall, boolean video)
- {
- if (existingCall == null)
- openCallContainerIfNecessary(call);
-
- new AnswerCallThread(call, existingCall, video).start();
- }
-
- /**
- * Answers the given call in an existing call. It will end up with a
- * conference call.
- *
- * @param call the call to answer
- */
- public static void answerCallInFirstExistingCall(Call call)
- {
- // Find the first existing call.
- Iterator<Call> existingCallIter = getInProgressCalls().iterator();
- Call existingCall
- = existingCallIter.hasNext() ? existingCallIter.next() : null;
-
- answerCall(call, existingCall, false /* without video */);
- }
-
- /**
- * Merges specific existing <tt>Call</tt>s into a specific telephony
- * conference.
- *
- * @param conference the conference
- * @param calls list of calls
- */
- public static void mergeExistingCalls(
- CallConference conference,
- Collection<Call> calls)
- {
- new MergeExistingCalls(conference, calls).start();
- }
-
- /**
- * Answers the given call with video.
- *
- * @param call the call to answer
- */
- public static void answerVideoCall(Call call)
- {
- answerCall(call, null, true /* with video */);
- }
-
- /**
- * Hang ups the given call.
- *
- * @param call the call to hang up
- */
- public static void hangupCall(Call call)
- {
- new HangupCallThread(call).start();
- }
-
- /**
- * Hang ups the given <tt>callPeer</tt>.
- *
- * @param peer the <tt>CallPeer</tt> to hang up
- */
- public static void hangupCallPeer(CallPeer peer)
- {
- new HangupCallThread(peer).start();
- }
-
- /**
- * Asynchronously hangs up the <tt>Call</tt>s participating in a specific
- * <tt>CallConference</tt>.
- *
- * @param conference the <tt>CallConference</tt> whose participating
- * <tt>Call</tt>s are to be hanged up
- */
- public static void hangupCalls(CallConference conference)
- {
- new HangupCallThread(conference).start();
- }
-
- /**
- * Creates a call to the contact represented by the given string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- */
- public static void createCall( ProtocolProviderService protocolProvider,
- String contact)
- {
- new CreateCallThread(protocolProvider, contact, false /* audio-only */)
- .start();
- }
-
- /**
- * Creates a call to the contact represented by the given string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- * @param uiContact the meta contact we're calling
- */
- public static void createCall( ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact)
- {
- new CreateCallThread(protocolProvider, null, null, uiContact,
- contact, null, null, false /* audio-only */).start();
- }
-
- /**
- * Creates a video call to the contact represented by the given string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- */
- public static void createVideoCall(ProtocolProviderService protocolProvider,
- String contact)
- {
- new CreateCallThread(protocolProvider, contact, true /* video */)
- .start();
- }
-
- /**
- * Creates a video call to the contact represented by the given string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- */
- public static void createVideoCall( ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact)
- {
- new CreateCallThread(protocolProvider, null, null, uiContact,
- contact, null, null, true /* video */).start();
- }
-
- /**
- * Enables/disables local video for a specific <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> to enable/disable to local video for
- * @param enable <tt>true</tt> to enable the local video; otherwise,
- * <tt>false</tt>
- */
- public static void enableLocalVideo(Call call, boolean enable)
- {
- new EnableLocalVideoThread(call, enable).start();
- }
-
- /**
- * Indicates if the desktop sharing is currently enabled for the given
- * <tt>call</tt>.
- *
- * @param call the <tt>Call</tt>, for which we would to check if the desktop
- * sharing is currently enabled
- * @return <tt>true</tt> if the desktop sharing is currently enabled for the
- * given <tt>call</tt>, <tt>false</tt> otherwise
- */
- public static boolean isLocalVideoEnabled(Call call)
- {
- OperationSetVideoTelephony telephony
- = call.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
-
- return (telephony != null) && telephony.isLocalVideoAllowed(call);
- }
-
- /**
- * Creates a desktop sharing call to the contact represented by the given
- * string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- */
- private static void createDesktopSharing(
- ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact)
- {
- // If the user presses cancel on the desktop sharing warning then we
- // have nothing more to do here.
- if (!showDesktopSharingWarning())
- return;
-
- MediaService mediaService = GuiActivator.getMediaService();
- List<MediaDevice> desktopDevices
- = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
- int deviceNumber = desktopDevices.size();
-
- if (deviceNumber == 1)
- {
- createDesktopSharing(
- protocolProvider,
- contact,
- uiContact,
- desktopDevices.get(0));
- }
- else if (deviceNumber > 1)
- {
- SelectScreenDialog selectDialog
- = new SelectScreenDialog(desktopDevices);
-
- selectDialog.setVisible(true);
- if (selectDialog.getSelectedDevice() != null)
- createDesktopSharing(
- protocolProvider,
- contact,
- uiContact,
- selectDialog.getSelectedDevice());
- }
- }
-
- /**
- * Creates a region desktop sharing through the given
- * <tt>protocolProvider</tt> with the given <tt>contact</tt>.
- *
- * @param protocolProvider the <tt>ProtocolProviderService</tt>, through
- * which the sharing session will be established
- * @param contact the address of the contact recipient
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- */
- private static void createRegionDesktopSharing(
- ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact)
- {
- if (showDesktopSharingWarning())
- {
- TransparentFrame frame = DesktopSharingFrame
- .createTransparentFrame(
- protocolProvider, contact, uiContact, true);
-
- frame.setLocationRelativeTo(null);
- frame.setVisible(true);
- }
- }
-
- /**
- * Creates a desktop sharing call to the contact represented by the given
- * string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- * @param uiContact the <tt>MetaContact</tt> we're calling
- * @param x the x coordinate of the shared region
- * @param y the y coordinated of the shared region
- * @param width the width of the shared region
- * @param height the height of the shared region
- */
- public static void createRegionDesktopSharing(
- ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact,
- int x,
- int y,
- int width,
- int height)
- {
- MediaService mediaService = GuiActivator.getMediaService();
-
- List<MediaDevice> desktopDevices = mediaService.getDevices(
- MediaType.VIDEO, MediaUseCase.DESKTOP);
-
- int deviceNumber = desktopDevices.size();
-
- if (deviceNumber > 0)
- {
- createDesktopSharing(
- protocolProvider,
- contact,
- uiContact,
- mediaService.getMediaDeviceForPartialDesktopStreaming(
- width,
- height,
- x,
- y));
- }
- }
-
- /**
- * Creates a desktop sharing call to the contact represented by the given
- * string.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param contact the contact to call to
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- * @param mediaDevice the media device corresponding to the screen to share
- */
- private static void createDesktopSharing(
- ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact,
- MediaDevice mediaDevice)
- {
- new CreateDesktopSharingThread( protocolProvider,
- contact,
- uiContact,
- mediaDevice).start();
- }
-
- /**
- * Enables the desktop sharing in an existing <tt>call</tt>.
- *
- * @param call the call for which desktop sharing should be enabled
- * @param enable indicates if the desktop sharing should be enabled or
- * disabled
- */
- public static void enableDesktopSharing(Call call, boolean enable)
- {
- if (!enable)
- enableDesktopSharing(call, null, enable);
- else if (showDesktopSharingWarning())
- {
- MediaService mediaService = GuiActivator.getMediaService();
- List<MediaDevice> desktopDevices
- = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
- int deviceNumber = desktopDevices.size();
-
- if (deviceNumber == 1)
- enableDesktopSharing(call, null, enable);
- else if (deviceNumber > 1)
- {
- SelectScreenDialog selectDialog
- = new SelectScreenDialog(desktopDevices);
-
- selectDialog.setVisible(true);
-
- if (selectDialog.getSelectedDevice() != null)
- enableDesktopSharing(
- call, selectDialog.getSelectedDevice(), enable);
- }
- }
-
- // in case we switch to video, disable remote control if it was
- // enabled
- enableDesktopRemoteControl(call.getCallPeers().next(), false);
- }
-
- /**
- * Enables the region desktop sharing for the given call.
- *
- * @param call the call, for which the region desktop sharing should be
- * enabled
- * @param enable indicates if the desktop sharing should be enabled or
- * disabled
- */
- public static void enableRegionDesktopSharing(Call call, boolean enable)
- {
- if (!enable)
- enableDesktopSharing(call, null, enable);
- else if (showDesktopSharingWarning())
- {
- TransparentFrame frame
- = DesktopSharingFrame.createTransparentFrame(call, true);
-
- frame.setVisible(true);
- }
- }
-
- /**
- * Creates a desktop sharing call to the contact represented by the given
- * string.
- *
- * @param call the call for which desktop sharing should be enabled
- * @param x the x coordinate of the shared region
- * @param y the y coordinated of the shared region
- * @param width the width of the shared region
- * @param height the height of the shared region
- */
- public static void enableRegionDesktopSharing(
- Call call,
- int x,
- int y,
- int width,
- int height)
- {
- // Use the default media device corresponding to the screen to share
- MediaService mediaService = GuiActivator.getMediaService();
-
- List<MediaDevice> desktopDevices = mediaService.getDevices(
- MediaType.VIDEO, MediaUseCase.DESKTOP);
-
- int deviceNumber = desktopDevices.size();
-
- if (deviceNumber > 0)
- {
- boolean succeed = enableDesktopSharing(
- call,
- mediaService.getMediaDeviceForPartialDesktopStreaming(
- width,
- height,
- x,
- y),
- true);
- // If the region sharing succeed, then display the frame of the
- // current region shared.
- if(succeed)
- {
- TransparentFrame frame
- = DesktopSharingFrame.createTransparentFrame(call, false);
-
- frame.setVisible(true);
- }
- }
-
- // in case we switch to video, disable remote control if it was
- // enabled
- enableDesktopRemoteControl(call.getCallPeers().next(), false);
- }
-
- /**
- * Enables the desktop sharing in an existing <tt>call</tt>.
- *
- * @param call the call for which desktop sharing should be enabled
- * @param mediaDevice the media device corresponding to the screen to share
- * @param enable indicates if the desktop sharing should be enabled or
- * disabled
- *
- * @return True if the desktop sharing succeed (we are currently sharing the
- * whole or a part of the desktop). False, otherwise.
- */
- private static boolean enableDesktopSharing(Call call,
- MediaDevice mediaDevice,
- boolean enable)
- {
- OperationSetDesktopSharingServer desktopOpSet
- = call.getProtocolProvider().getOperationSet(
- OperationSetDesktopSharingServer.class);
- boolean enableSucceeded = false;
-
- // This shouldn't happen at this stage, because we disable the button
- // if the operation set isn't available.
- if (desktopOpSet != null)
- {
- // First make sure the local video button is disabled.
- if (enable && isLocalVideoEnabled(call))
- getActiveCallContainer(call).setVideoButtonSelected(false);
-
- try
- {
- if (mediaDevice != null)
- {
- desktopOpSet.setLocalVideoAllowed(
- call,
- mediaDevice,
- enable);
- }
- else
- desktopOpSet.setLocalVideoAllowed(call, enable);
-
- enableSucceeded = true;
- }
- catch (OperationFailedException ex)
- {
- logger.error(
- "Failed to toggle the streaming of local video.",
- ex);
- }
- }
-
- return (enable && enableSucceeded);
- }
-
- /**
- * Indicates if the desktop sharing is currently enabled for the given
- * <tt>call</tt>.
- *
- * @param call the <tt>Call</tt>, for which we would to check if the desktop
- * sharing is currently enabled
- * @return <tt>true</tt> if the desktop sharing is currently enabled for the
- * given <tt>call</tt>, <tt>false</tt> otherwise
- */
- public static boolean isDesktopSharingEnabled(Call call)
- {
- OperationSetDesktopSharingServer desktopOpSet
- = call.getProtocolProvider().getOperationSet(
- OperationSetDesktopSharingServer.class);
-
- if (desktopOpSet != null
- && desktopOpSet.isLocalVideoAllowed(call))
- return true;
-
- return false;
- }
-
- /**
- * Indicates if the desktop sharing is currently enabled for the given
- * <tt>call</tt>.
- *
- * @param call the <tt>Call</tt>, for which we would to check if the desktop
- * sharing is currently enabled
- * @return <tt>true</tt> if the desktop sharing is currently enabled for the
- * given <tt>call</tt>, <tt>false</tt> otherwise
- */
- public static boolean isRegionDesktopSharingEnabled(Call call)
- {
- OperationSetDesktopSharingServer desktopOpSet
- = call.getProtocolProvider().getOperationSet(
- OperationSetDesktopSharingServer.class);
-
- if (desktopOpSet != null
- && desktopOpSet.isPartialStreaming(call))
- return true;
-
- return false;
- }
-
- /**
- * Enables/disables remote control when in a desktop sharing session with
- * the given <tt>callPeer</tt>.
- *
- * @param callPeer the call peer for which we enable/disable remote control
- * @param isEnable indicates if the remote control should be enabled
- */
- public static void enableDesktopRemoteControl( CallPeer callPeer,
- boolean isEnable)
- {
- OperationSetDesktopSharingServer sharingOpSet
- = callPeer.getProtocolProvider().getOperationSet(
- OperationSetDesktopSharingServer.class);
-
- if (sharingOpSet == null)
- return;
-
- if (isEnable)
- sharingOpSet.enableRemoteControl(callPeer);
- else
- sharingOpSet.disableRemoteControl(callPeer);
- }
-
- /**
- * Creates a call to the given call string. The given component indicates
- * where should be shown the "call via" menu if needed.
- *
- * @param callString the string to call
- * @param c the component, which indicates where should be shown the "call
- * via" menu if needed
- */
- public static void createCall( String callString,
- JComponent c)
- {
- createCall(callString, c, null);
- }
-
- /**
- * Creates a call to the given call string. The given component indicates
- * where should be shown the "call via" menu if needed.
- *
- * @param callString the string to call
- * @param c the component, which indicates where should be shown the "call
- * via" menu if needed
- * @param l listener that is notified when the call interface has been
- * started after call was created
- */
- public static void createCall( String callString,
- JComponent c,
- CallInterfaceListener l)
- {
- callString = callString.trim();
-
- // Removes special characters from phone numbers.
- if (ConfigurationUtils.isNormalizePhoneNumber())
- callString = PhoneNumberI18nService.normalize(callString);
-
- List<ProtocolProviderService> telephonyProviders
- = CallManager.getTelephonyProviders();
-
- if (telephonyProviders.size() == 1)
- {
- CallManager.createCall(
- telephonyProviders.get(0), callString);
-
- if (l != null)
- l.callInterfaceStarted();
- }
- else if (telephonyProviders.size() > 1)
- {
- /*
- * Allow plugins which do not have a (Jitsi) UI to create calls by
- * automagically picking up a telephony provider.
- */
- if (c == null)
- {
- ProtocolProviderService preferredTelephonyProvider = null;
-
- for (ProtocolProviderService telephonyProvider
- : telephonyProviders)
- {
- try
- {
- OperationSetPresence presenceOpSet
- = telephonyProvider.getOperationSet(
- OperationSetPresence.class);
-
- if ((presenceOpSet != null)
- && (presenceOpSet.findContactByID(callString)
- != null))
- {
- preferredTelephonyProvider = telephonyProvider;
- break;
- }
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- }
- }
- if (preferredTelephonyProvider == null)
- preferredTelephonyProvider = telephonyProviders.get(0);
-
- CallManager.createCall(preferredTelephonyProvider, callString);
- if (l != null)
- l.callInterfaceStarted();
- }
- else
- {
- ChooseCallAccountPopupMenu chooseAccountDialog
- = new ChooseCallAccountPopupMenu(
- c,
- callString,
- telephonyProviders,
- l);
-
- chooseAccountDialog.setLocation(c.getLocation());
- chooseAccountDialog.showPopupMenu();
- }
- }
- else
- {
- ResourceManagementService resources = GuiActivator.getResources();
-
- new ErrorDialog(
- null,
- resources.getI18NString("service.gui.WARNING"),
- resources.getI18NString(
- "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
- .showDialog();
- }
- }
-
- /**
- * Creates a call to the given list of contacts.
- *
- * @param protocolProvider the protocol provider to which this call belongs.
- * @param callees the list of contacts to call to
- */
- public static void createConferenceCall(
- String[] callees,
- ProtocolProviderService protocolProvider)
- {
- Map<ProtocolProviderService, List<String>> crossProtocolCallees
- = new HashMap<ProtocolProviderService, List<String>>();
-
- crossProtocolCallees.put(protocolProvider, Arrays.asList(callees));
- createConferenceCall(crossProtocolCallees);
- }
-
- /**
- * Invites the given list of <tt>callees</tt> to the given conference
- * <tt>call</tt>.
- *
- * @param callees the list of contacts to invite
- * @param call the protocol provider to which this call belongs
- */
- public static void inviteToConferenceCall(String[] callees, Call call)
- {
- Map<ProtocolProviderService, List<String>> crossProtocolCallees
- = new HashMap<ProtocolProviderService, List<String>>();
-
- crossProtocolCallees.put(
- call.getProtocolProvider(),
- Arrays.asList(callees));
- inviteToConferenceCall(crossProtocolCallees, call);
- }
-
- /**
- * Invites the given list of <tt>callees</tt> to the given conference
- * <tt>call</tt>.
- *
- * @param callees the list of contacts to invite
- * @param call existing call
- */
- public static void inviteToConferenceCall(
- Map<ProtocolProviderService, List<String>> callees,
- Call call)
- {
- new InviteToConferenceCallThread(callees, call).start();
- }
-
- /**
- * Invites specific <tt>callees</tt> to a specific telephony conference.
- *
- * @param callees the list of contacts to invite
- * @param conference the telephony conference to invite the specified
- * <tt>callees</tt> into
- */
- public static void inviteToConferenceCall(
- Map<ProtocolProviderService, List<String>> callees,
- CallConference conference)
- {
- /*
- * InviteToConferenceCallThread takes a specific Call but actually
- * invites to the telephony conference associated with the specified
- * Call (if any). In order to not change the signature of its
- * constructor at this time, just pick up a Call participating in the
- * specified telephony conference (if any).
- */
- Call call = null;
-
- if (conference != null)
- {
- List<Call> calls = conference.getCalls();
-
- if (!calls.isEmpty())
- call = calls.get(0);
- }
-
- new InviteToConferenceCallThread(callees, call).start();
- }
-
- /**
- * Asynchronously creates a new conference <tt>Call</tt> with a specific
- * list of participants/callees.
- *
- * @param callees the list of participants/callees to invite to a
- * newly-created conference <tt>Call</tt>
- */
- public static void createConferenceCall(
- Map<ProtocolProviderService, List<String>> callees)
- {
- new InviteToConferenceCallThread(callees, null).start();
- }
-
- /**
- * Asynchronously creates a new video bridge conference <tt>Call</tt> with
- * a specific list of participants/callees.
- *
- * @param callProvider the <tt>ProtocolProviderService</tt> to use for
- * creating the call
- * @param callees the list of participants/callees to invite to the
- * newly-created video bridge conference <tt>Call</tt>
- */
- public static void createVideoBridgeConfCall(
- ProtocolProviderService callProvider,
- String[] callees)
- {
- new InviteToConferenceBridgeThread(callProvider, callees, null).start();
- }
-
- /**
- * Invites the given list of <tt>callees</tt> to the given conference
- * <tt>call</tt>.
- *
- * @param callees the list of contacts to invite
- * @param call the protocol provider to which this call belongs
- */
- public static void inviteToVideoBridgeConfCall(String[] callees, Call call)
- {
- new InviteToConferenceBridgeThread( call.getProtocolProvider(),
- callees,
- call).start();
- }
-
- /**
- * Puts on or off hold the given <tt>callPeer</tt>.
- * @param callPeer the peer to put on/off hold
- * @param isOnHold indicates the action (on hold or off hold)
- */
- public static void putOnHold(CallPeer callPeer, boolean isOnHold)
- {
- new PutOnHoldCallPeerThread(callPeer, isOnHold).start();
- }
-
- /**
- * Transfers the given <tt>peer</tt> to the given <tt>target</tt>.
- * @param peer the <tt>CallPeer</tt> to transfer
- * @param target the <tt>CallPeer</tt> target to transfer to
- */
- public static void transferCall(CallPeer peer, CallPeer target)
- {
- OperationSetAdvancedTelephony<?> telephony
- = peer.getCall().getProtocolProvider()
- .getOperationSet(OperationSetAdvancedTelephony.class);
-
- if (telephony != null)
- {
- try
- {
- telephony.transfer(peer, target);
- }
- catch (OperationFailedException ex)
- {
- logger.error("Failed to transfer " + peer.getAddress()
- + " to " + target, ex);
- }
- }
- }
-
- /**
- * Transfers the given <tt>peer</tt> to the given <tt>target</tt>.
- * @param peer the <tt>CallPeer</tt> to transfer
- * @param target the target of the transfer
- */
- public static void transferCall(CallPeer peer, String target)
- {
- OperationSetAdvancedTelephony<?> telephony
- = peer.getCall().getProtocolProvider()
- .getOperationSet(OperationSetAdvancedTelephony.class);
-
- if (telephony != null)
- {
- try
- {
- telephony.transfer(peer, target);
- }
- catch (OperationFailedException ex)
- {
- logger.error("Failed to transfer " + peer.getAddress()
- + " to " + target, ex);
- }
- }
- }
-
- /**
- * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no
- * longer necessary (i.e. is not used by other <tt>Call</tt>s participating
- * in the same telephony conference as the specified <tt>Call</tt>.)
- *
- * @param callConference The <tt>CallConference</tt> which is to have its
- * associated <tt>CallPanel</tt>, if any
- */
- private static void closeCallContainerIfNotNecessary(
- final CallConference callConference)
- {
- CallPanel callPanel = callPanels.get(callConference);
-
- if (callPanel != null)
- closeCallContainerIfNotNecessary(
- callConference, callPanel.isCloseWaitAfterHangup());
- }
-
- /**
- * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no
- * longer necessary (i.e. is not used by other <tt>Call</tt>s participating
- * in the same telephony conference as the specified <tt>Call</tt>.)
- *
- * @param callConference The <tt>CallConference</tt> which is to have its
- * associated <tt>CallPanel</tt>, if any, closed
- * @param wait <tt>true</tt> to set <tt>delay</tt> param of
- * {@link CallContainer#close(CallPanel, boolean)} (CallPanel)}
- */
- private static void closeCallContainerIfNotNecessary(
- final CallConference callConference,
- final boolean wait)
- {
- if (!SwingUtilities.isEventDispatchThread())
- {
- SwingUtilities.invokeLater(
- new Runnable()
- {
- public void run()
- {
- closeCallContainerIfNotNecessary(
- callConference,
- wait);
- }
- });
- return;
- }
-
- /*
- * XXX The integrity of the execution of the method may be compromised
- * if it is not invoked on the AWT event dispatching thread because
- * findCallPanel and callPanels.remove must be atomically executed. The
- * uninterrupted execution (with respect to the synchronization) is
- * guaranteed by requiring all modifications to callPanels to be made on
- * the AWT event dispatching thread.
- */
-
- for (Iterator<Map.Entry<CallConference, CallPanel>> entryIter
- = callPanels.entrySet().iterator();
- entryIter.hasNext();)
- {
- Map.Entry<CallConference, CallPanel> entry = entryIter.next();
- CallConference aConference = entry.getKey();
- boolean notNecessary = aConference.isEnded();
-
- if (notNecessary)
- {
- CallPanel aCallPanel = entry.getValue();
- CallContainer window = aCallPanel.getCallWindow();
-
- try
- {
- window.close(
- aCallPanel,
- wait && (aConference == callConference));
- }
- finally
- {
- /*
- * We allow non-modifications i.e. reads of callPanels on
- * threads other than the AWT event dispatching thread so we
- * have to make sure that we will not cause
- * ConcurrentModificationException.
- */
- synchronized (callPanels)
- {
- entryIter.remove();
- }
-
- aCallPanel.dispose();
- }
- }
- }
- }
-
- /**
- * Opens a <tt>CallPanel</tt> for a specific <tt>Call</tt> if there is none.
- * <p>
- * <b>Note</b>: The method can be called only on the AWT event dispatching
- * thread.
- * </p>
- *
- * @param call the <tt>Call</tt> to open a <tt>CallPanel</tt> for
- * @return the <tt>CallPanel</tt> associated with the <tt>Call</tt>
- * @throws RuntimeException if the method is not called on the AWT event
- * dispatching thread
- */
- private static CallPanel openCallContainerIfNecessary(Call call)
- {
- /*
- * XXX The integrity of the execution of the method may be compromised
- * if it is not invoked on the AWT event dispatching thread because
- * findCallPanel and callPanels.put must be atomically executed. The
- * uninterrupted execution (with respect to the synchronization) is
- * guaranteed by requiring all modifications to callPanels to be made on
- * the AWT event dispatching thread.
- */
- assertIsEventDispatchingThread();
-
- /*
- * CallPanel displays a CallConference (which may contain multiple
- * Calls.)
- */
- CallConference conference = call.getConference();
- CallPanel callPanel = findCallPanel(conference);
-
- if (callPanel == null)
- {
- // If we're in single-window mode, the single window is the
- // CallContainer.
- CallContainer callContainer
- = GuiActivator.getUIService().getSingleWindowContainer();
-
- // If we're in multi-window mode, we create the CallDialog.
- if (callContainer == null)
- callContainer = new CallDialog();
-
- callPanel = new CallPanel(conference, callContainer);
- callContainer.addCallPanel(callPanel);
-
- synchronized (callPanels)
- {
- callPanels.put(conference, callPanel);
- }
- }
-
- return callPanel;
- }
-
- /**
- * Returns a list of all currently registered telephony providers.
- * @return a list of all currently registered telephony providers
- */
- public static List<ProtocolProviderService> getTelephonyProviders()
- {
- return AccountUtils
- .getRegisteredProviders(OperationSetBasicTelephony.class);
- }
-
- /**
- * Returns a list of all currently registered telephony providers supporting
- * conferencing.
- *
- * @return a list of all currently registered telephony providers supporting
- * conferencing
- */
- public static List<ProtocolProviderService>
- getTelephonyConferencingProviders()
- {
- return AccountUtils
- .getRegisteredProviders(OperationSetTelephonyConferencing.class);
- }
-
- /**
- * Returns a list of all currently active calls.
- *
- * @return a list of all currently active calls
- */
- private static List<Call> getActiveCalls()
- {
- CallConference[] conferences;
-
- synchronized (callPanels)
- {
- Set<CallConference> keySet = callPanels.keySet();
-
- conferences = keySet.toArray(new CallConference[keySet.size()]);
- }
-
- List<Call> calls = new ArrayList<Call>();
-
- for (CallConference conference : conferences)
- {
- for (Call call : conference.getCalls())
- {
- if (call.getCallState() == CallState.CALL_IN_PROGRESS)
- calls.add(call);
- }
- }
- return calls;
- }
-
- /**
- * Returns a collection of all currently in progress calls. A call is active
- * if it is in progress so the method merely delegates to
- *
- * @return a collection of all currently in progress calls.
- */
- public static Collection<Call> getInProgressCalls()
- {
- return getActiveCalls();
- }
-
- /**
- * Returns the <tt>CallContainer</tt> corresponding to the given
- * <tt>call</tt>. If the call has been finished and no active
- * <tt>CallContainer</tt> could be found it returns null.
- *
- * @param call the <tt>Call</tt>, which dialog we're looking for
- * @return the <tt>CallContainer</tt> corresponding to the given
- * <tt>call</tt>
- */
- public static CallPanel getActiveCallContainer(Call call)
- {
- return findCallPanel(call.getConference());
- }
-
- /**
- * A informative text to show for the peer. If display name is missing
- * return the address.
- * @param peer the peer.
- * @return the text contain display name.
- */
- public static String getPeerDisplayName(CallPeer peer)
- {
- String displayName = null;
-
- // We try to find the <tt>UIContact</tt>, to which the call was
- // created if this was an outgoing call.
- UIContactImpl uiContact
- = CallManager.getCallUIContact(peer.getCall());
-
- if(uiContact != null)
- {
- if(uiContact.getDescriptor() instanceof SourceContact)
- {
- // if it is source contact (history record)
- // search for cusax contact match
- Contact contact = getPeerCusaxContact(peer.getAddress(),
- (SourceContact)uiContact.getDescriptor());
- if(contact != null)
- displayName = contact.getDisplayName();
- }
-
- if(StringUtils.isNullOrEmpty(displayName, true))
- displayName = uiContact.getDisplayName();
- }
-
- // We search for a contact corresponding to this call peer and
- // try to get its display name.
- if (StringUtils.isNullOrEmpty(displayName, true)
- && peer.getContact() != null)
- {
- displayName = peer.getContact().getDisplayName();
- }
-
- // We try to find the an alternative peer address.
- if (StringUtils.isNullOrEmpty(displayName, true))
- {
- String imppAddress = peer.getAlternativeIMPPAddress();
-
- if (!StringUtils.isNullOrEmpty(imppAddress))
- {
- int protocolPartIndex = imppAddress.indexOf(":");
-
- imppAddress = (protocolPartIndex >= 0)
- ? imppAddress.substring(protocolPartIndex + 1)
- : imppAddress;
-
- Collection<ProtocolProviderService> cusaxProviders
- = AccountUtils.getRegisteredProviders(
- OperationSetCusaxUtils.class);
-
- if (cusaxProviders != null && cusaxProviders.size() > 0)
- {
- Contact contact = getPeerContact(
- peer,
- cusaxProviders.iterator().next(),
- imppAddress);
-
- displayName = (contact != null)
- ? contact.getDisplayName() : null;
- }
- else
- {
- MetaContact metaContact
- = getPeerMetaContact(peer, imppAddress);
-
- displayName = (metaContact != null)
- ? metaContact.getDisplayName() : null;
- }
- }
- }
-
- if (StringUtils.isNullOrEmpty(displayName, true))
- {
- displayName = (!StringUtils.isNullOrEmpty
- (peer.getDisplayName(), true))
- ? peer.getDisplayName()
- : peer.getAddress();
-
- // Try to resolve the display name
- String resolvedName = resolveContactSource(displayName);
- if(resolvedName != null)
- {
- displayName = resolvedName;
- }
- }
-
- return displayName;
- }
-
- /**
- * Returns the image corresponding to the given <tt>peer</tt>.
- *
- * @param peer the call peer, for which we're returning an image
- * @return the peer image
- */
- public static byte[] getPeerImage(CallPeer peer)
- {
- byte[] image = null;
- // We search for a contact corresponding to this call peer and
- // try to get its image.
- if (peer.getContact() != null)
- {
- image = getContactImage(peer.getContact());
- }
-
- // We try to find the <tt>UIContact</tt>, to which the call was
- // created if this was an outgoing call.
- if (image == null || image.length == 0)
- {
- UIContactImpl uiContact
- = CallManager.getCallUIContact(peer.getCall());
-
- if (uiContact != null)
- {
- if(uiContact.getDescriptor() instanceof SourceContact
- && ((SourceContact)uiContact.getDescriptor())
- .isDefaultImage())
- {
- // if it is source contact (history record)
- // search for cusax contact match
- Contact contact = getPeerCusaxContact(peer.getAddress(),
- (SourceContact)uiContact.getDescriptor());
-
- if(contact != null)
- image = contact.getImage();
- }
- else
- image = uiContact.getAvatar();
- }
- }
-
- // We try to find the an alternative peer address.
- if (image == null || image.length == 0)
- {
- String imppAddress = peer.getAlternativeIMPPAddress();
-
- if (!StringUtils.isNullOrEmpty(imppAddress))
- {
- int protocolPartIndex = imppAddress.indexOf(":");
-
- imppAddress = (protocolPartIndex >= 0)
- ? imppAddress.substring(protocolPartIndex + 1)
- : imppAddress;
-
- Collection<ProtocolProviderService> cusaxProviders
- = AccountUtils.getRegisteredProviders(
- OperationSetCusaxUtils.class);
-
- if (cusaxProviders != null && cusaxProviders.size() > 0)
- {
- Contact contact = getPeerContact(
- peer,
- cusaxProviders.iterator().next(),
- imppAddress);
-
- image = (contact != null) ? getContactImage(contact) : null;
- }
- else
- {
- MetaContact metaContact
- = getPeerMetaContact(peer, imppAddress);
-
- image = (metaContact != null)
- ? metaContact.getAvatar() : null;
- }
- }
- }
-
- // If the icon is still null we try to get an image from the call
- // peer.
- if ((image == null || image.length == 0)
- && peer.getImage() != null)
- image = peer.getImage();
-
- return image;
- }
-
- /**
- * Searches the cusax enabled providers for a contact with
- * the detail (address) of the call peer if found and the contact
- * is provided by a provider which is IM capable, return the contact.
- * @param peer the peer we are calling.
- * @return the im capable contact corresponding the <tt>peer</tt>.
- */
- public static Contact getIMCapableCusaxContact(CallPeer peer)
- {
- // We try to find the <tt>UIContact</tt>, to which the call was
- // created if this was an outgoing call.
- UIContactImpl uiContact
- = CallManager.getCallUIContact(peer.getCall());
-
- if (uiContact != null)
- {
- if(uiContact.getDescriptor() instanceof MetaContact)
- {
- MetaContact metaContact =
- (MetaContact)uiContact.getDescriptor();
- Iterator<Contact> iter = metaContact.getContacts();
- while(iter.hasNext())
- {
- Contact contact = iter.next();
- if(contact.getProtocolProvider()
- .getOperationSet(
- OperationSetBasicInstantMessaging.class) != null)
- return contact;
- }
- }
- else if(uiContact.getDescriptor() instanceof SourceContact)
- {
- // if it is source contact (history record)
- // search for cusax contact match
- Contact contact = getPeerCusaxContact(peer.getAddress(),
- (SourceContact)uiContact.getDescriptor());
- if(contact != null
- && contact.getProtocolProvider().getOperationSet(
- OperationSetBasicInstantMessaging.class) != null)
- return contact;
- }
- }
-
- // We try to find the an alternative peer address.
- String imppAddress = peer.getAlternativeIMPPAddress();
-
- if (!StringUtils.isNullOrEmpty(imppAddress))
- {
- int protocolPartIndex = imppAddress.indexOf(":");
-
- imppAddress = (protocolPartIndex >= 0)
- ? imppAddress.substring(protocolPartIndex + 1)
- : imppAddress;
-
- Collection<ProtocolProviderService> cusaxProviders
- = AccountUtils.getRegisteredProviders(
- OperationSetCusaxUtils.class);
-
- if (cusaxProviders != null && cusaxProviders.size() > 0)
- {
- ProtocolProviderService cusaxProvider
- = cusaxProviders.iterator().next();
-
- Contact contact = getPeerContact(
- peer,
- cusaxProvider,
- imppAddress);
-
- if(contact != null
- && cusaxProvider.getOperationSet(
- OperationSetBasicInstantMessaging.class) != null)
- {
- return contact;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Find is there a linked cusax protocol provider for this source contact,
- * if it exist we try to resolve current peer to one of its contacts
- * or details of a contact (numbers).
- * @param peerAddress the address of the peer to check
- * @param sourceContact the currently selected source contact.
- * @return matching cusax contact.
- */
- private static Contact getPeerCusaxContact(
- String peerAddress, SourceContact sourceContact)
- {
- ProtocolProviderService linkedCusaxProvider = null;
- for(ContactDetail detail : sourceContact.getContactDetails())
- {
- ProtocolProviderService pps
- = detail.getPreferredProtocolProvider(
- OperationSetBasicTelephony.class);
-
- if(pps != null)
- {
- OperationSetCusaxUtils cusaxOpSet =
- pps.getOperationSet(OperationSetCusaxUtils.class);
-
- if(cusaxOpSet != null)
- {
- linkedCusaxProvider
- = cusaxOpSet.getLinkedCusaxProvider();
- break;
- }
- }
- }
-
- if(linkedCusaxProvider != null)
- {
- OperationSetPersistentPresence opSetPersistentPresence
- = linkedCusaxProvider.getOperationSet(
- OperationSetPersistentPresence.class);
-
- if(opSetPersistentPresence != null)
- {
- // will strip the @server-address part, as the regular expression
- // will match it
- int index = peerAddress.indexOf("@");
- String peerUserID =
- (index > -1) ? peerAddress.substring(0, index) : peerAddress;
-
- // searches for the whole number/username or with the @serverpart
- String peerUserIDQ = Pattern.quote(peerUserID);
-
- Pattern pattern = Pattern.compile(
- "^(" + peerUserIDQ + "|" + peerUserIDQ + "@.*)$");
-
- return findContactByPeer(
- peerUserID,
- pattern,
- opSetPersistentPresence.getServerStoredContactListRoot(),
- linkedCusaxProvider.getOperationSet(
- OperationSetCusaxUtils.class));
- }
- }
-
- return null;
- }
-
- /**
- * Finds a matching cusax contact.
- * @param peerUserID the userID of the call peer to search for
- * @param searchPattern the pattern (userID | userID@...)
- * @param parent the parent group of the groups and contacts to search in
- * @param cusaxOpSet the opset of the provider which will be used to match
- * contact's details to peer userID (stored numbers).
- * @return a cusax matching contac
- */
- private static Contact findContactByPeer(
- String peerUserID,
- Pattern searchPattern,
- ContactGroup parent,
- OperationSetCusaxUtils cusaxOpSet)
- {
- Iterator<Contact> contactIterator = parent.contacts();
- while(contactIterator.hasNext())
- {
- Contact contact = contactIterator.next();
-
- if(searchPattern.matcher(contact.getAddress()).find()
- || cusaxOpSet.doesDetailBelong(contact, peerUserID))
- {
- return contact;
- }
- }
-
- Iterator<ContactGroup> groupsIterator = parent.subgroups();
- while(groupsIterator.hasNext())
- {
- ContactGroup gr = groupsIterator.next();
- Contact contact = findContactByPeer(
- peerUserID, searchPattern, gr, cusaxOpSet);
- if(contact != null)
- return contact;
- }
-
- return null;
- }
-
- /**
- * Returns the image for the given contact.
- *
- * @param contact the <tt>Contact</tt>, which image we're looking for
- * @return the array of bytes representing the image for the given contact
- * or null if such image doesn't exist
- */
- private static byte[] getContactImage(Contact contact)
- {
- MetaContact metaContact = GuiActivator.getContactListService()
- .findMetaContactByContact(contact);
-
- if (metaContact != null)
- return metaContact.getAvatar();
-
- return null;
- }
-
- /**
- * Returns the image for the given <tt>alternativePeerAddress</tt> by
- * checking the if the <tt>callPeer</tt> exists as a detail in the given
- * <tt>cusaxProvider</tt>.
- *
- * @param callPeer the <tt>CallPeer</tt> to check in the cusax provider
- * details
- * @param cusaxProvider the linked cusax <tt>ProtocolProviderService</tt>
- * @param alternativePeerAddress the alternative peer address to obtain the
- * image from
- * @return the protocol <tt>Contact</tt> corresponding to the given
- * <tt>alternativePeerAddress</tt>
- */
- private static Contact getPeerContact( CallPeer callPeer,
- ProtocolProviderService cusaxProvider,
- String alternativePeerAddress)
- {
- OperationSetPresence presenceOpSet
- = cusaxProvider.getOperationSet(OperationSetPresence.class);
-
- if (presenceOpSet == null)
- return null;
-
- Contact contact = presenceOpSet.findContactByID(alternativePeerAddress);
-
- if (contact == null)
- return null;
-
- OperationSetCusaxUtils cusaxOpSet
- = cusaxProvider.getOperationSet(OperationSetCusaxUtils.class);
-
- if (cusaxOpSet != null && cusaxOpSet.doesDetailBelong(
- contact, callPeer.getAddress()))
- return contact;
-
- return null;
- }
-
- /**
- * Returns the metacontact for the given <tt>CallPeer</tt> by
- * checking the if the <tt>callPeer</tt> contact exists, if not checks the
- * contacts in our contact list that are provided by cusax enabled
- * providers.
- *
- * @param peer the <tt>CallPeer</tt> to check in contact details
- * @return the <tt>MetaContact</tt> corresponding to the given
- * <tt>peer</tt>.
- */
- public static MetaContact getPeerMetaContact(CallPeer peer)
- {
- if(peer == null)
- return null;
-
- if(peer.getContact() != null)
- return GuiActivator.getContactListService()
- .findMetaContactByContact(peer.getContact());
-
- // We try to find the <tt>UIContact</tt>, to which the call was
- // created if this was an outgoing call.
- UIContactImpl uiContact
- = CallManager.getCallUIContact(peer.getCall());
-
- if (uiContact != null)
- {
- if(uiContact.getDescriptor() instanceof MetaContact)
- {
- return (MetaContact)uiContact.getDescriptor();
- }
- else if(uiContact.getDescriptor() instanceof SourceContact)
- {
- // if it is a source contact check for matching cusax contact
- Contact contact = getPeerCusaxContact(peer.getAddress(),
- (SourceContact)uiContact.getDescriptor());
- if(contact != null)
- return GuiActivator.getContactListService()
- .findMetaContactByContact(contact);
- }
- }
-
- String imppAddress = peer.getAlternativeIMPPAddress();
-
- if (!StringUtils.isNullOrEmpty(imppAddress))
- {
- int protocolPartIndex = imppAddress.indexOf(":");
-
- imppAddress = (protocolPartIndex >= 0)
- ? imppAddress.substring(protocolPartIndex + 1)
- : imppAddress;
-
- Collection<ProtocolProviderService> cusaxProviders
- = AccountUtils.getRegisteredProviders(
- OperationSetCusaxUtils.class);
-
- if (cusaxProviders != null && cusaxProviders.size() > 0)
- {
- Contact contact = getPeerContact(
- peer,
- cusaxProviders.iterator().next(),
- imppAddress);
-
- return GuiActivator.getContactListService()
- .findMetaContactByContact(contact);
- }
- else
- {
- return getPeerMetaContact(peer, imppAddress);
- }
- }
-
- return null;
- }
-
- /**
- * Returns the image for the given <tt>alternativePeerAddress</tt> by
- * checking the if the <tt>callPeer</tt> exists as a detail in one of the
- * contacts in our contact list.
- *
- * @param callPeer the <tt>CallPeer</tt> to check in contact details
- * @param alternativePeerAddress the alternative peer address to obtain the
- * image from
- * @return the <tt>MetaContact</tt> corresponding to the given
- * <tt>alternativePeerAddress</tt>
- */
- private static MetaContact getPeerMetaContact(
- CallPeer callPeer,
- String alternativePeerAddress)
- {
- Iterator<MetaContact> metaContacts
- = GuiActivator.getContactListService()
- .findAllMetaContactsForAddress(alternativePeerAddress);
-
- while (metaContacts.hasNext())
- {
- MetaContact metaContact = metaContacts.next();
-
- UIPhoneUtil phoneUtil
- = UIPhoneUtil.getPhoneUtil(metaContact);
-
- List<UIContactDetail> additionalNumbers
- = phoneUtil.getAdditionalNumbers();
-
- if (additionalNumbers == null || additionalNumbers.size() > 0)
- continue;
-
- Iterator<UIContactDetail> numbersIter
- = additionalNumbers.iterator();
- while (numbersIter.hasNext())
- {
- if (numbersIter.next().getAddress()
- .equals(callPeer.getAddress()))
- return metaContact;
- }
- }
-
- return null;
- }
-
- /**
- * Opens a call transfer dialog to transfer the given <tt>peer</tt>.
- * @param peer the <tt>CallPeer</tt> to transfer
- */
- public static void openCallTransferDialog(CallPeer peer)
- {
- final TransferCallDialog dialog
- = new TransferCallDialog(peer);
-
- final Call call = peer.getCall();
-
- /*
- * Transferring a call works only when the call is in progress
- * so close the dialog (if it's not already closed, of course)
- * once the dialog ends.
- */
- CallChangeListener callChangeListener = new CallChangeAdapter()
- {
- /*
- * Overrides
- * CallChangeAdapter#callStateChanged(CallChangeEvent).
- */
- @Override
- public void callStateChanged(CallChangeEvent evt)
- {
- // we are interested only in CALL_STATE_CHANGEs
- if(!evt.getEventType().equals(
- CallChangeEvent.CALL_STATE_CHANGE))
- return;
-
- if (!CallState.CALL_IN_PROGRESS.equals(call
- .getCallState()))
- {
- dialog.setVisible(false);
- dialog.dispose();
- }
- }
- };
- call.addCallChangeListener(callChangeListener);
- try
- {
- dialog.pack();
- dialog.setVisible(true);
- }
- finally
- {
- call.removeCallChangeListener(callChangeListener);
- }
- }
-
- /**
- * Checks whether the <tt>callPeer</tt> supports setting video
- * quality presets. If quality controls is null, its not supported.
- * @param callPeer the peer, which video quality we're checking
- * @return whether call peer supports setting quality preset.
- */
- public static boolean isVideoQualityPresetSupported(CallPeer callPeer)
- {
- ProtocolProviderService provider = callPeer.getProtocolProvider();
- OperationSetVideoTelephony videoOpSet
- = provider.getOperationSet(OperationSetVideoTelephony.class);
-
- if (videoOpSet == null)
- return false;
-
- return videoOpSet.getQualityControl(callPeer) != null;
- }
-
- /**
- * Sets the given quality preset for the video of the given call peer.
- *
- * @param callPeer the peer, which video quality we're setting
- * @param qualityPreset the new quality settings
- */
- public static void setVideoQualityPreset(final CallPeer callPeer,
- final QualityPreset qualityPreset)
- {
- ProtocolProviderService provider = callPeer.getProtocolProvider();
- final OperationSetVideoTelephony videoOpSet
- = provider.getOperationSet(OperationSetVideoTelephony.class);
-
- if (videoOpSet == null)
- return;
-
- final QualityControl qualityControl =
- videoOpSet.getQualityControl(callPeer);
-
- if (qualityControl != null)
- {
- new Thread(new Runnable()
- {
- public void run()
- {
- try
- {
- qualityControl.setPreferredRemoteSendMaxPreset(
- qualityPreset);
- }
- catch(org.jitsi.service.protocol.OperationFailedException e)
- {
- logger.info("Unable to change video quality.", e);
-
- ResourceManagementService resources
- = GuiActivator.getResources();
-
- new ErrorDialog(
- null,
- resources.getI18NString("service.gui.WARNING"),
- resources.getI18NString(
- "service.gui.UNABLE_TO_CHANGE_VIDEO_QUALITY"),
- e)
- .showDialog();
- }
- }
- }).start();
- }
- }
-
- /**
- * Indicates if we have video streams to show in this interface.
- *
- * @param call the call to check for video streaming
- * @return <tt>true</tt> if we have video streams to show in this interface;
- * otherwise, <tt>false</tt>
- */
- public static boolean isVideoStreaming(Call call)
- {
- return isVideoStreaming(call.getConference());
- }
-
- /**
- * Indicates if we have video streams to show in this interface.
- *
- * @param conference the conference we check for video streaming
- * @return <tt>true</tt> if we have video streams to show in this interface;
- * otherwise, <tt>false</tt>
- */
- public static boolean isVideoStreaming(CallConference conference)
- {
- for (Call call : conference.getCalls())
- {
- OperationSetVideoTelephony videoTelephony
- = call.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
-
- if (videoTelephony == null)
- continue;
-
- if (videoTelephony.isLocalVideoStreaming(call))
- return true;
-
- Iterator<? extends CallPeer> callPeers = call.getCallPeers();
-
- while (callPeers.hasNext())
- {
- List<Component> remoteVideos
- = videoTelephony.getVisualComponents(callPeers.next());
-
- if ((remoteVideos != null) && (remoteVideos.size() > 0))
- return true;
- }
- }
- return false;
- }
-
- /**
- * Determines whether two specific addresses refer to one and the same
- * peer/resource/contact.
- * <p>
- * <b>Warning</b>: Use the functionality sparingly because it assumes that
- * an unspecified service is equal to any service.
- * </p>
- *
- * @param a one of the addresses to be compared
- * @param b the other address to be compared to <tt>a</tt>
- * @return <tt>true</tt> if <tt>a</tt> and <tt>b</tt> name one and the same
- * peer/resource/contact; <tt>false</tt>, otherwise
- */
- public static boolean addressesAreEqual(String a, String b)
- {
- if (a.equals(b))
- return true;
-
- int aProtocolIndex = a.indexOf(':');
- if(aProtocolIndex != -1)
- a = a.substring(aProtocolIndex + 1);
-
- int bProtocolIndex = b.indexOf(':');
- if(bProtocolIndex != -1)
- b = b.substring(bProtocolIndex + 1);
-
- if (a.equals(b))
- return true;
-
- int aServiceBegin = a.indexOf('@', aProtocolIndex);
- String aUserID;
- String aService;
-
- if (aServiceBegin != -1)
- {
- aUserID = a.substring(0, aServiceBegin);
- ++aServiceBegin;
-
- int aResourceBegin = a.indexOf('/', aServiceBegin);
- if (aResourceBegin != -1)
- aService = a.substring(aServiceBegin, aResourceBegin);
- else
- aService = a.substring(aServiceBegin);
- }
- else
- {
- aUserID = a;
- aService = null;
- }
-
- int bServiceBegin = b.indexOf('@', bProtocolIndex);
- String bUserID;
- String bService;
-
- if (bServiceBegin != -1)
- {
- bUserID = b.substring(0, bServiceBegin);
- ++bServiceBegin;
-
- int bResourceBegin = b.indexOf('/', bServiceBegin);
- if (bResourceBegin != -1)
- bService = b.substring(bServiceBegin, bResourceBegin);
- else
- bService = b.substring(bServiceBegin);
- }
- else
- {
- bUserID = b;
- bService = null;
- }
-
- boolean userIDsAreEqual;
-
- if ((aUserID == null) || (aUserID.length() < 1))
- userIDsAreEqual = ((bUserID == null) || (bUserID.length() < 1));
- else
- userIDsAreEqual = aUserID.equals(bUserID);
- if (!userIDsAreEqual)
- return false;
-
- boolean servicesAreEqual;
-
- /*
- * It's probably a veeery long shot but it's assumed here that an
- * unspecified service is equal to any service. Such a case is, for
- * example, RegistrarLess SIP.
- */
- if (((aService == null) || (aService.length() < 1))
- || ((bService == null) || (bService.length() < 1)))
- servicesAreEqual = true;
- else
- servicesAreEqual = aService.equals(bService);
-
- return servicesAreEqual;
- }
-
- /**
- * Indicates if the given <tt>ConferenceMember</tt> corresponds to the local
- * user.
- *
- * @param conferenceMember the conference member to check
- * @return <tt>true</tt> if the given <tt>conferenceMember</tt> is the local
- * user, <tt>false</tt> - otherwise
- */
- public static boolean isLocalUser(ConferenceMember conferenceMember)
- {
- String localUserAddress
- = conferenceMember.getConferenceFocusCallPeer()
- .getProtocolProvider().getAccountID().getAccountAddress();
-
- return CallManager.addressesAreEqual(
- conferenceMember.getAddress(), localUserAddress);
- }
-
- /**
- * Adds a missed call notification.
- *
- * @param peerName the name of the peer
- * @param callTime the time of the call
- */
- private static void addMissedCallNotification(String peerName, long callTime)
- {
- if (missedCallGroup == null)
- {
- missedCallGroup
- = new UINotificationGroup(
- "MissedCalls",
- GuiActivator.getResources().getI18NString(
- "service.gui.MISSED_CALLS_TOOL_TIP"));
- }
-
- UINotificationManager.addNotification(
- new UINotification(peerName, callTime, missedCallGroup));
- }
-
- /**
- * Returns of supported/enabled list of audio formats for a provider.
- * @param device the <tt>MediaDevice</tt>, which audio formats we're
- * looking for
- * @param protocolProvider the provider to check.
- * @return list of supported/enabled auido formats or empty list
- * otherwise.
- */
- private static List<MediaFormat> getAudioFormats(
- MediaDevice device,
- ProtocolProviderService protocolProvider)
- {
- List<MediaFormat> res = new ArrayList<MediaFormat>();
-
- Map<String, String> accountProperties
- = protocolProvider.getAccountID().getAccountProperties();
- String overrideEncodings
- = accountProperties.get(ProtocolProviderFactory.OVERRIDE_ENCODINGS);
-
- List<MediaFormat> formats;
- if(Boolean.parseBoolean(overrideEncodings))
- {
- /*
- * The account properties associated with account
- * override the global EncodingConfiguration.
- */
- EncodingConfiguration encodingConfiguration
- = ProtocolMediaActivator.getMediaService()
- .createEmptyEncodingConfiguration();
-
- encodingConfiguration.loadProperties(
- accountProperties,
- ProtocolProviderFactory.ENCODING_PROP_PREFIX);
-
- formats = device.getSupportedFormats(
- null, null, encodingConfiguration);
- }
- else /* The global EncodingConfiguration is in effect. */
- {
- formats = device.getSupportedFormats();
- }
-
- // skip the special telephony event
- for(MediaFormat format : formats)
- {
- if(!format.getEncoding().equals(Constants.TELEPHONE_EVENT))
- res.add(format);
- }
-
- return res;
- }
-
- /**
- * Creates a new (audio-only or video) <tt>Call</tt> to a contact specified
- * as a <tt>Contact</tt> instance or a <tt>String</tt> contact
- * address/identifier.
- */
- private static class CreateCallThread
- extends Thread
- {
- /**
- * The contact to call.
- */
- private final Contact contact;
-
- /**
- * The specific contact resource to call.
- */
- private final ContactResource contactResource;
-
- /**
- * The <tt>UIContactImpl</tt> we're calling.
- */
- private final UIContactImpl uiContact;
-
- /**
- * The protocol provider through which the call goes.
- */
- private final ProtocolProviderService protocolProvider;
-
- /**
- * The string to call.
- */
- private final String stringContact;
-
- /**
- * The description of a conference to call, if any.
- */
- private final ConferenceDescription conferenceDescription;
-
- /**
- * The indicator which determines whether this instance is to create a
- * new video (as opposed to audio-only) <tt>Call</tt>.
- */
- private final boolean video;
-
- /**
- * The chat room associated with the call.
- */
- private final ChatRoom chatRoom;
-
- /**
- * Creates an instance of <tt>CreateCallThread</tt>.
- *
- * @param protocolProvider the protocol provider through which the call
- * is going.
- * @param contact the contact to call
- * @param contactResource the specific <tt>ContactResource</tt> we're
- * calling
- * @param video indicates if this is a video call
- */
- public CreateCallThread(
- ProtocolProviderService protocolProvider,
- Contact contact,
- ContactResource contactResource,
- boolean video)
- {
- this(protocolProvider, contact, contactResource, null, null, null,
- null, video);
- }
-
- /**
- * Creates an instance of <tt>CreateCallThread</tt>.
- *
- * @param protocolProvider the protocol provider through which the call
- * is going.
- * @param contact the contact to call
- * @param video indicates if this is a video call
- */
- public CreateCallThread(
- ProtocolProviderService protocolProvider,
- String contact,
- boolean video)
- {
- this(protocolProvider, null, null, null, contact, null, null, video);
- }
-
- /**
- * Initializes a new <tt>CreateCallThread</tt> instance which is to
- * create a new <tt>Call</tt> to a conference specified via a
- * <tt>ConferenceDescription</tt>.
- * @param protocolProvider the <tt>ProtocolProviderService</tt> which is
- * to perform the establishment of the new <tt>Call</tt>.
- * @param conferenceDescription the description of the conference to
- * call.
- * @param chatRoom the chat room associated with the call.
- */
- public CreateCallThread(
- ProtocolProviderService protocolProvider,
- ConferenceDescription conferenceDescription,
- ChatRoom chatRoom)
- {
- this(protocolProvider, null, null, null, null,
- conferenceDescription, chatRoom,
- false /* video */);
- }
-
- /**
- * Initializes a new <tt>CreateCallThread</tt> instance which is to
- * create a new <tt>Call</tt> to a contact specified either as a
- * <tt>Contact</tt> instance or as a <tt>String</tt> contact
- * address/identifier.
- * <p>
- * The constructor is private because it relies on its arguments being
- * validated prior to its invocation.
- * </p>
- *
- * @param protocolProvider the <tt>ProtocolProviderService</tt> which is
- * to perform the establishment of the new <tt>Call</tt>
- * @param contact the contact to call
- * @param contactResource the specific contact resource to call
- * @param uiContact the ui contact we're calling
- * @param stringContact the string to call
- * @param video <tt>true</tt> if this instance is to create a new video
- * (as opposed to audio-only) <tt>Call</tt>
- * @param conferenceDescription the description of a conference to call
- * @param chatRoom the chat room associated with the call.
- */
- public CreateCallThread(
- ProtocolProviderService protocolProvider,
- Contact contact,
- ContactResource contactResource,
- UIContactImpl uiContact,
- String stringContact,
- ConferenceDescription conferenceDescription,
- ChatRoom chatRoom,
- boolean video)
- {
- this.protocolProvider = protocolProvider;
- this.contact = contact;
- this.contactResource = contactResource;
- this.uiContact = uiContact;
- this.stringContact = stringContact;
- this.video = video;
- this.conferenceDescription = conferenceDescription;
- this.chatRoom = chatRoom;
- }
-
- @Override
- public void run()
- {
- if(!video)
- {
- // if it is not video let's check for available audio codecs
- // and available audio devices
- MediaService mediaService = GuiActivator.getMediaService();
- MediaDevice dev = mediaService.getDefaultDevice(
- MediaType.AUDIO, MediaUseCase.CALL);
-
- List<MediaFormat> formats
- = getAudioFormats(dev, protocolProvider);
-
- String errorMsg = null;
-
- if(!dev.getDirection().allowsSending())
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.CALL_NO_AUDIO_DEVICE");
- else if(formats.isEmpty())
- {
- errorMsg = GuiActivator.getResources().getI18NString(
- "service.gui.CALL_NO_AUDIO_CODEC");
- }
-
- if(errorMsg != null)
- {
- if(GuiActivator.getUIService()
- .getPopupDialog().showConfirmPopupDialog(
- errorMsg + " " +
- GuiActivator.getResources().getI18NString(
- "service.gui.CALL_NO_DEVICE_CODECS_Q"),
- GuiActivator.getResources().getI18NString(
- "service.gui.CALL"),
- PopupDialog.YES_NO_OPTION,
- PopupDialog.QUESTION_MESSAGE)
- == PopupDialog.NO_OPTION)
- {
- return;
- }
- }
- }
-
- Contact contact = this.contact;
- String stringContact = this.stringContact;
-
- if (ConfigurationUtils.isNormalizePhoneNumber())
- {
- if (contact != null)
- {
- stringContact = contact.getAddress();
- contact = null;
- }
-
- if (stringContact != null)
- {
- stringContact
- = PhoneNumberI18nService.normalize(stringContact);
- }
- }
-
- try
- {
- if (conferenceDescription != null)
- {
- internalCall( protocolProvider,
- conferenceDescription,
- chatRoom);
- }
- else
- {
- if (video)
- {
- internalCallVideo( protocolProvider,
- contact,
- uiContact,
- stringContact);
- }
- else
- {
- internalCall( protocolProvider,
- contact,
- stringContact,
- contactResource,
- uiContact);
- }
- }
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
-
- logger.error("The call could not be created: ", t);
-
- String message = GuiActivator.getResources()
- .getI18NString("servoce.gui.CREATE_CALL_FAILED");
-
- if (t.getMessage() != null)
- message += " " + t.getMessage();
-
- new ErrorDialog(
- null,
- GuiActivator.getResources().getI18NString(
- "service.gui.ERROR"),
- message,
- t)
- .showDialog();
- }
- }
- }
-
- /**
- * Creates a video call through the given <tt>protocolProvider</tt>.
- *
- * @param protocolProvider the <tt>ProtocolProviderService</tt> through
- * which to make the call
- * @param contact the <tt>Contact</tt> to call
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- * @param stringContact the contact string to call
- *
- * @throws OperationFailedException thrown if the call operation fails
- * @throws ParseException thrown if the contact string is malformated
- */
- private static void internalCallVideo(
- ProtocolProviderService protocolProvider,
- Contact contact,
- UIContactImpl uiContact,
- String stringContact)
- throws OperationFailedException,
- ParseException
- {
- OperationSetVideoTelephony telephony
- = protocolProvider.getOperationSet(
- OperationSetVideoTelephony.class);
-
- Call createdCall = null;
- if (telephony != null)
- {
- if (contact != null)
- {
- createdCall = telephony.createVideoCall(contact);
- }
- else if (stringContact != null)
- createdCall = telephony.createVideoCall(stringContact);
- }
-
- if (uiContact != null && createdCall != null)
- addUIContactCall(uiContact, createdCall);
- }
-
- /**
- * Creates a call through the given <tt>protocolProvider</tt>.
- *
- * @param protocolProvider the <tt>ProtocolProviderService</tt> through
- * which to make the call
- * @param contact the <tt>Contact</tt> to call
- * @param stringContact the contact string to call
- * @param contactResource the specific <tt>ContactResource</tt> to call
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- *
- * @throws OperationFailedException thrown if the call operation fails
- * @throws ParseException thrown if the contact string is malformated
- */
- private static void internalCall(
- ProtocolProviderService protocolProvider,
- Contact contact,
- String stringContact,
- ContactResource contactResource,
- UIContactImpl uiContact)
- throws OperationFailedException,
- ParseException
- {
- OperationSetBasicTelephony<?> telephony
- = protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
-
- OperationSetResourceAwareTelephony resourceTelephony
- = protocolProvider.getOperationSet(
- OperationSetResourceAwareTelephony.class);
-
- Call createdCall = null;
-
- if (resourceTelephony != null && contactResource != null)
- {
- if (contact != null)
- createdCall
- = resourceTelephony.createCall(contact, contactResource);
- else if (!StringUtils.isNullOrEmpty(stringContact))
- createdCall = resourceTelephony.createCall(
- stringContact, contactResource.getResourceName());
- }
- else if (telephony != null)
- {
- if (contact != null)
- {
- createdCall = telephony.createCall(contact);
- }
- else if (!StringUtils.isNullOrEmpty(stringContact))
- createdCall = telephony.createCall(stringContact);
- }
-
- if (uiContact != null && createdCall != null)
- addUIContactCall(uiContact, createdCall);
- }
-
- /**
- * Creates a call through the given <tt>protocolProvider</tt>.
- *
- * @param protocolProvider the <tt>ProtocolProviderService</tt> through
- * which to make the call
- * @param conferenceDescription the description of the conference to call
- * @param chatRoom the chat room associated with the call.
- */
- private static void internalCall(ProtocolProviderService protocolProvider,
- ConferenceDescription conferenceDescription,
- ChatRoom chatRoom)
- throws OperationFailedException
- {
- OperationSetBasicTelephony<?> telephony
- = protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
-
- if (telephony != null)
- {
- telephony.createCall(conferenceDescription, chatRoom);
- }
- }
-
- /**
- * Returns the <tt>MetaContact</tt>, to which the given <tt>Call</tt>
- * was initially created.
- *
- * @param call the <tt>Call</tt>, which corresponding <tt>MetaContact</tt>
- * we're looking for
- * @return the <tt>UIContactImpl</tt>, to which the given <tt>Call</tt>
- * was initially created
- */
- public static UIContactImpl getCallUIContact(Call call)
- {
- if (uiContactCalls != null)
- return uiContactCalls.get(call);
- return null;
- }
-
- /**
- * Adds a call for a <tt>metaContact</tt>.
- *
- * @param uiContact the <tt>UIContact</tt> corresponding to the call
- * @param call the <tt>Call</tt> corresponding to the <tt>MetaContact</tt>
- */
- private static void addUIContactCall( UIContactImpl uiContact,
- Call call)
- {
- if (uiContactCalls == null)
- uiContactCalls = new WeakHashMap<Call, UIContactImpl>();
-
- uiContactCalls.put(call, uiContact);
- }
-
- /**
- * Creates a desktop sharing session with the given Contact or a given
- * String.
- */
- private static class CreateDesktopSharingThread
- extends Thread
- {
- /**
- * The string contact to share the desktop with.
- */
- private final String stringContact;
-
- /**
- * The protocol provider through which we share our desktop.
- */
- private final ProtocolProviderService protocolProvider;
-
- /**
- * The media device corresponding to the screen we would like to share.
- */
- private final MediaDevice mediaDevice;
-
- /**
- * The <tt>UIContactImpl</tt> we're calling.
- */
- private final UIContactImpl uiContact;
-
- /**
- * Creates a desktop sharing session thread.
- *
- * @param protocolProvider protocol provider through which we share our
- * desktop
- * @param contact the contact to share the desktop with
- * @param uiContact the <tt>UIContact</tt>, which initiated the desktop
- * sharing session
- * @param mediaDevice the media device corresponding to the screen we
- * would like to share
- */
- public CreateDesktopSharingThread(
- ProtocolProviderService protocolProvider,
- String contact,
- UIContactImpl uiContact,
- MediaDevice mediaDevice)
- {
- this.protocolProvider = protocolProvider;
- this.stringContact = contact;
- this.uiContact = uiContact;
- this.mediaDevice = mediaDevice;
- }
-
- @Override
- public void run()
- {
- OperationSetDesktopSharingServer desktopSharingOpSet
- = protocolProvider.getOperationSet(
- OperationSetDesktopSharingServer.class);
-
- /*
- * XXX If we are here and we just discover that
- * OperationSetDesktopSharingServer is not supported, then we're
- * already in trouble - we've already started a whole new thread
- * just to check that a reference is null.
- */
- if (desktopSharingOpSet == null)
- return;
-
- Throwable exception = null;
-
- Call createdCall = null;
- try
- {
- if (mediaDevice != null)
- {
- createdCall = desktopSharingOpSet.createVideoCall(
- stringContact,
- mediaDevice);
- }
- else
- createdCall
- = desktopSharingOpSet.createVideoCall(stringContact);
- }
- catch (OperationFailedException e)
- {
- exception = e;
- }
- catch (ParseException e)
- {
- exception = e;
- }
- if (exception != null)
- {
- logger.error("The call could not be created: ", exception);
-
- new ErrorDialog(
- null,
- GuiActivator.getResources().getI18NString(
- "service.gui.ERROR"),
- exception.getMessage(),
- ErrorDialog.ERROR)
- .showDialog();
- }
-
- if (uiContact != null && createdCall != null)
- addUIContactCall(uiContact, createdCall);
- }
- }
-
- /**
- * Answers to all <tt>CallPeer</tt>s associated with a specific
- * <tt>Call</tt> and, optionally, does that in a telephony conference with
- * an existing <tt>Call</tt>.
- */
- private static class AnswerCallThread
- extends Thread
- {
- /**
- * The <tt>Call</tt> which is to be answered.
- */
- private final Call call;
-
- /**
- * The existing <tt>Call</tt>, if any, which represents a telephony
- * conference in which {@link #call} is to be answered.
- */
- private final Call existingCall;
-
- /**
- * The indicator which determines whether this instance is to answer
- * {@link #call} with video.
- */
- private final boolean video;
-
- public AnswerCallThread(Call call, Call existingCall, boolean video)
- {
- this.call = call;
- this.existingCall = existingCall;
- this.video = video;
- }
-
- @Override
- public void run()
- {
- if (existingCall != null)
- call.setConference(existingCall.getConference());
-
- ProtocolProviderService pps = call.getProtocolProvider();
- Iterator<? extends CallPeer> peers = call.getCallPeers();
-
- while (peers.hasNext())
- {
- CallPeer peer = peers.next();
-
- if (video)
- {
- OperationSetVideoTelephony telephony
- = pps.getOperationSet(OperationSetVideoTelephony.class);
-
- try
- {
- telephony.answerVideoCallPeer(peer);
- }
- catch (OperationFailedException ofe)
- {
- logger.error(
- "Could not answer "
- + peer
- + " with video"
- + " because of the following exception: "
- + ofe);
- }
- }
- else
- {
- OperationSetBasicTelephony<?> telephony
- = pps.getOperationSet(OperationSetBasicTelephony.class);
-
- try
- {
- telephony.answerCallPeer(peer);
- }
- catch (OperationFailedException ofe)
- {
- logger.error(
- "Could not answer "
- + peer
- + " because of the following exception: ",
- ofe);
- }
- }
- }
- }
- }
-
- /**
- * Invites a list of callees to a conference <tt>Call</tt>. If the specified
- * <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony conference.
- */
- private static class InviteToConferenceCallThread
- extends Thread
- {
- /**
- * The addresses of the callees to be invited into the telephony
- * conference to be organized by this instance. For further details,
- * refer to the documentation on the <tt>callees</tt> parameter of the
- * respective <tt>InviteToConferenceCallThread</tt> constructor.
- */
- private final Map<ProtocolProviderService, List<String>>
- callees;
-
- /**
- * The <tt>Call</tt>, if any, into the telephony conference of which
- * {@link #callees} are to be invited. If non-<tt>null</tt>, its
- * <tt>CallConference</tt> state will be shared with all <tt>Call</tt>s
- * established by this instance for the purposes of having the
- * <tt>callees</tt> into the same telephony conference.
- */
- private final Call call;
-
- /**
- * Initializes a new <tt>InviteToConferenceCallThread</tt> instance
- * which is to invite a list of callees to a conference <tt>Call</tt>.
- * If the specified <tt>call</tt> is <tt>null</tt>, creates a brand new
- * telephony conference.
- *
- * @param callees the addresses of the callees to be invited into a
- * telephony conference. The addresses are provided in multiple
- * <tt>List&lt;String&gt;</tt>s. Each such list of addresses is mapped
- * by the <tt>ProtocolProviderService</tt> through which they are to be
- * invited into the telephony conference. If there are multiple
- * <tt>ProtocolProviderService</tt>s in the specified <tt>Map</tt>, the
- * resulting telephony conference is known by the name
- * &quot;cross-protocol&quot;. It is also allowed to have a list of
- * addresses mapped to <tt>null</tt> which means that the new instance
- * will automatically choose a <tt>ProtocolProviderService</tt> to
- * invite the respective callees into the telephony conference.
- * @param call the <tt>Call</tt> to invite the specified
- * <tt>callees</tt> into. If <tt>null</tt>, this instance will create a
- * brand new telephony conference. Technically, a <tt>Call</tt> instance
- * is protocol/account-specific and it is possible to have
- * cross-protocol/account telephony conferences. That's why the
- * specified <tt>callees</tt> are invited into one and the same
- * <tt>CallConference</tt>: the one in which the specified <tt>call</tt>
- * is participating or a new one if <tt>call</tt> is <tt>null</tt>. Of
- * course, an attempt is made to have all callees from one and the same
- * protocol/account into one <tt>Call</tt> instance.
- */
- public InviteToConferenceCallThread(
- Map<ProtocolProviderService, List<String>> callees,
- Call call)
- {
- this.callees = callees;
- this.call = call;
- }
-
- /**
- * Invites {@link #callees} into a telephony conference which is
- * optionally specified by {@link #call}.
- */
- @Override
- public void run()
- {
- CallConference conference
- = (call == null) ? null : call.getConference();
-
- for(Map.Entry<ProtocolProviderService, List<String>> entry
- : callees.entrySet())
- {
- ProtocolProviderService pps = entry.getKey();
-
- /*
- * We'd like to allow specifying callees without specifying an
- * associated ProtocolProviderService.
- */
- if (pps != null)
- {
- OperationSetBasicTelephony<?> basicTelephony
- = pps.getOperationSet(OperationSetBasicTelephony.class);
-
- if(basicTelephony == null)
- continue;
- }
-
- List<String> contactList = entry.getValue();
- String[] contactArray
- = contactList.toArray(new String[contactList.size()]);
-
- if (ConfigurationUtils.isNormalizePhoneNumber())
- normalizePhoneNumbers(contactArray);
-
- /* Try to have a single Call per ProtocolProviderService. */
- Call ppsCall;
-
- if ((call != null) && call.getProtocolProvider().equals(pps))
- ppsCall = call;
- else
- {
- ppsCall = null;
- if (conference != null)
- {
- List<Call> conferenceCalls = conference.getCalls();
-
- if (pps == null)
- {
- /*
- * We'd like to allow specifying callees without
- * specifying an associated ProtocolProviderService.
- * The simplest approach is to just choose the first
- * ProtocolProviderService involved in the telephony
- * conference.
- */
- if (call == null)
- {
- if (!conferenceCalls.isEmpty())
- {
- ppsCall = conferenceCalls.get(0);
- pps = ppsCall.getProtocolProvider();
- }
- }
- else
- {
- ppsCall = call;
- pps = ppsCall.getProtocolProvider();
- }
- }
- else
- {
- for (Call conferenceCall : conferenceCalls)
- {
- if (pps.equals(
- conferenceCall.getProtocolProvider()))
- {
- ppsCall = conferenceCall;
- break;
- }
- }
- }
- }
- }
-
- OperationSetTelephonyConferencing telephonyConferencing
- = pps.getOperationSet(
- OperationSetTelephonyConferencing.class);
-
- try
- {
- if (ppsCall == null)
- {
- ppsCall
- = telephonyConferencing.createConfCall(
- contactArray,
- conference);
- if (conference == null)
- conference = ppsCall.getConference();
- }
- else
- {
- for (String contact : contactArray)
- {
- telephonyConferencing.inviteCalleeToCall(
- contact,
- ppsCall);
- }
- }
- }
- catch(Exception e)
- {
- logger.error(
- "Failed to invite callees: "
- + Arrays.toString(contactArray),
- e);
- new ErrorDialog(
- null,
- GuiActivator.getResources().getI18NString(
- "service.gui.ERROR"),
- e.getMessage(),
- ErrorDialog.ERROR)
- .showDialog();
- }
- }
- }
- }
-
- /**
- * Invites a list of callees to a specific conference <tt>Call</tt>. If the
- * specified <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony
- * conference.
- */
- private static class InviteToConferenceBridgeThread
- extends Thread
- {
- private final ProtocolProviderService callProvider;
-
- private final String[] callees;
-
- private final Call call;
-
- public InviteToConferenceBridgeThread(
- ProtocolProviderService callProvider,
- String[] callees,
- Call call)
- {
- this.callProvider = callProvider;
- this.callees = callees;
- this.call = call;
- }
-
- @Override
- public void run()
- {
- OperationSetVideoBridge opSetVideoBridge
- = callProvider.getOperationSet(
- OperationSetVideoBridge.class);
-
- // Normally if this method is called then this should not happen
- // but we check in order to be sure to be able to proceed.
- if (opSetVideoBridge == null || !opSetVideoBridge.isActive())
- return;
-
- if (ConfigurationUtils.isNormalizePhoneNumber())
- normalizePhoneNumbers(callees);
-
- try
- {
- if (call == null)
- {
- opSetVideoBridge.createConfCall(callees);
- }
- else
- {
- for (String contact : callees)
- opSetVideoBridge.inviteCalleeToCall(contact, call);
- }
- }
- catch(Exception e)
- {
- logger.error(
- "Failed to invite callees: "
- + Arrays.toString(callees),
- e);
- new ErrorDialog(
- null,
- GuiActivator.getResources().getI18NString(
- "service.gui.ERROR"),
- e.getMessage(),
- e)
- .showDialog();
- }
- }
- }
-
- /**
- * Hangs up a specific <tt>Call</tt> (i.e. all <tt>CallPeer</tt>s associated
- * with a <tt>Call</tt>), <tt>CallConference</tt> (i.e. all <tt>Call</tt>s
- * participating in a <tt>CallConference</tt>), or <tt>CallPeer</tt>.
- */
- private static class HangupCallThread
- extends Thread
- {
- private final Call call;
-
- private final CallConference conference;
-
- private final CallPeer peer;
-
- /**
- * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
- * up a specific <tt>Call</tt> i.e. all <tt>CallPeer</tt>s associated
- * with the <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are
- * to be hanged up
- */
- public HangupCallThread(Call call)
- {
- this(call, null, null);
- }
-
- /**
- * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
- * up a specific <tt>CallConference</tt> i.e. all <tt>Call</tt>s
- * participating in the <tt>CallConference</tt>.
- *
- * @param conference the <tt>CallConference</tt> whose participating
- * <tt>Call</tt>s re to be hanged up
- */
- public HangupCallThread(CallConference conference)
- {
- this(null, conference, null);
- }
-
- /**
- * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
- * up a specific <tt>CallPeer</tt>.
- *
- * @param peer the <tt>CallPeer</tt> to hang up
- */
- public HangupCallThread(CallPeer peer)
- {
- this(null, null, peer);
- }
-
- /**
- * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
- * up a specific <tt>Call</tt>, <tt>CallConference</tt>, or
- * <tt>CallPeer</tt>.
- *
- * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are
- * to be hanged up
- * @param conference the <tt>CallConference</tt> whose participating
- * <tt>Call</tt>s re to be hanged up
- * @param peer the <tt>CallPeer</tt> to hang up
- */
- private HangupCallThread(
- Call call,
- CallConference conference,
- CallPeer peer)
- {
- this.call = call;
- this.conference = conference;
- this.peer = peer;
- }
-
- @Override
- public void run()
- {
- /*
- * There is only an OperationSet which hangs up a CallPeer at a time
- * so prepare a list of all CallPeers to be hanged up.
- */
- Set<CallPeer> peers = new HashSet<CallPeer>();
-
- if (call != null)
- {
- Iterator<? extends CallPeer> peerIter = call.getCallPeers();
-
- while (peerIter.hasNext())
- peers.add(peerIter.next());
- }
-
- if (conference != null)
- peers.addAll(conference.getCallPeers());
-
- if (peer != null)
- peers.add(peer);
-
- for (CallPeer peer : peers)
- {
- OperationSetBasicTelephony<?> basicTelephony
- = peer.getProtocolProvider().getOperationSet(
- OperationSetBasicTelephony.class);
-
- try
- {
- basicTelephony.hangupCallPeer(peer);
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Could not hang up: " + peer, ofe);
- }
- }
- }
- }
-
- /**
- * Creates the enable local video call thread.
- */
- private static class EnableLocalVideoThread
- extends Thread
- {
- private final Call call;
-
- private final boolean enable;
-
- /**
- * Creates the enable local video call thread.
- *
- * @param call the call, for which to enable/disable
- * @param enable
- */
- public EnableLocalVideoThread(Call call, boolean enable)
- {
- this.call = call;
- this.enable = enable;
- }
-
- @Override
- public void run()
- {
- OperationSetVideoTelephony videoTelephony
- = call.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
- boolean enableSucceeded = false;
-
- if (videoTelephony != null)
- {
- // First make sure the desktop sharing is disabled.
- if (enable && isDesktopSharingEnabled(call))
- {
- JFrame frame = DesktopSharingFrame.getFrameForCall(call);
-
- if (frame != null)
- frame.dispose();
- }
-
- try
- {
- videoTelephony.setLocalVideoAllowed(call, enable);
- enableSucceeded = true;
- }
- catch (OperationFailedException ex)
- {
- logger.error(
- "Failed to toggle the streaming of local video.",
- ex);
-
- ResourceManagementService r = GuiActivator.getResources();
- String title
- = r.getI18NString(
- "service.gui.LOCAL_VIDEO_ERROR_TITLE");
- String message
- = r.getI18NString(
- "service.gui.LOCAL_VIDEO_ERROR_MESSAGE");
-
- GuiActivator.getAlertUIService().showAlertPopup(
- title,
- message,
- ex);
- }
- }
-
- // If the operation didn't succeeded for some reason, make sure the
- // video button doesn't remain selected.
- if (enable && !enableSucceeded)
- getActiveCallContainer(call).setVideoButtonSelected(false);
- }
- }
-
- /**
- * Puts on hold the given <tt>CallPeer</tt>.
- */
- private static class PutOnHoldCallPeerThread
- extends Thread
- {
- private final CallPeer callPeer;
-
- private final boolean isOnHold;
-
- public PutOnHoldCallPeerThread(CallPeer callPeer, boolean isOnHold)
- {
- this.callPeer = callPeer;
- this.isOnHold = isOnHold;
- }
-
- @Override
- public void run()
- {
- OperationSetBasicTelephony<?> telephony
- = callPeer.getProtocolProvider().getOperationSet(
- OperationSetBasicTelephony.class);
-
- try
- {
- if (isOnHold)
- telephony.putOnHold(callPeer);
- else
- telephony.putOffHold(callPeer);
- }
- catch (OperationFailedException ex)
- {
- logger.error(
- "Failed to put"
- + callPeer.getAddress()
- + (isOnHold ? " on hold." : " off hold."),
- ex);
- }
- }
- }
-
- /**
- * Merges specific existing <tt>Call</tt>s into a specific telephony
- * conference.
- */
- private static class MergeExistingCalls
- extends Thread
- {
- /**
- * The telephony conference in which {@link #calls} are to be merged.
- */
- private final CallConference conference;
-
- /**
- * Second call.
- */
- private final Collection<Call> calls;
-
- /**
- * Initializes a new <tt>MergeExistingCalls</tt> instance which is to
- * merge specific existing <tt>Call</tt>s into a specific telephony
- * conference.
- *
- * @param conference the telephony conference in which the specified
- * <tt>Call</tt>s are to be merged
- * @param calls the <tt>Call</tt>s to be merged into the specified
- * telephony conference
- */
- public MergeExistingCalls(
- CallConference conference,
- Collection<Call> calls)
- {
- this.conference = conference;
- this.calls = calls;
- }
-
- /**
- * Puts off hold the <tt>CallPeer</tt>s of a specific <tt>Call</tt>
- * which are locally on hold.
- *
- * @param call the <tt>Call</tt> which is to have its <tt>CallPeer</tt>s
- * put off hold
- */
- private void putOffHold(Call call)
- {
- Iterator<? extends CallPeer> peers = call.getCallPeers();
- OperationSetBasicTelephony<?> telephony
- = call.getProtocolProvider().getOperationSet(
- OperationSetBasicTelephony.class);
-
- while (peers.hasNext())
- {
- CallPeer callPeer = peers.next();
- boolean putOffHold = true;
-
- if(callPeer instanceof MediaAwareCallPeer)
- {
- putOffHold
- = ((MediaAwareCallPeer<?,?,?>) callPeer)
- .getMediaHandler()
- .isLocallyOnHold();
- }
- if(putOffHold)
- {
- try
- {
- telephony.putOffHold(callPeer);
- Thread.sleep(400);
- }
- catch(Exception ofe)
- {
- logger.error("Failed to put off hold.", ofe);
- }
- }
- }
- }
-
- @Override
- public void run()
- {
- // conference
- for (Call call : conference.getCalls())
- putOffHold(call);
-
- // calls
- if (!calls.isEmpty())
- {
- for(Call call : calls)
- {
- if (conference.containsCall(call))
- continue;
-
- putOffHold(call);
-
- /*
- * Dispose of the CallPanel associated with the Call which
- * is to be merged.
- */
- closeCallContainerIfNotNecessary(conference, false);
-
- call.setConference(conference);
- }
- }
- }
- }
-
- /**
- * Shows a warning window to warn the user that she's about to start a
- * desktop sharing session.
- *
- * @return <tt>true</tt> if the user has accepted the desktop sharing
- * session; <tt>false</tt>, otherwise
- */
- private static boolean showDesktopSharingWarning()
- {
- Boolean isWarningEnabled
- = GuiActivator.getConfigurationService().getBoolean(
- desktopSharingWarningProperty,
- true);
-
- if (isWarningEnabled.booleanValue())
- {
- ResourceManagementService resources = GuiActivator.getResources();
- MessageDialog warningDialog
- = new MessageDialog(
- null,
- resources.getI18NString("service.gui.WARNING"),
- resources.getI18NString(
- "service.gui.DESKTOP_SHARING_WARNING"),
- true);
-
- switch (warningDialog.showDialog())
- {
- case MessageDialog.OK_RETURN_CODE:
- return true;
- case MessageDialog.CANCEL_RETURN_CODE:
- return false;
- case MessageDialog.OK_DONT_ASK_CODE:
- GuiActivator.getConfigurationService().setProperty(
- desktopSharingWarningProperty,
- false);
- return true;
- }
- }
-
- return true;
- }
-
- /**
- * Normalizes the phone numbers (if any) in a list of <tt>String</tt>
- * contact addresses or phone numbers.
- *
- * @param callees the list of contact addresses or phone numbers to be
- * normalized
- */
- private static void normalizePhoneNumbers(String callees[])
- {
- for (int i = 0 ; i < callees.length ; i++)
- callees[i] = PhoneNumberI18nService.normalize(callees[i]);
- }
-
- /**
- * Throws a <tt>RuntimeException</tt> if the current thread is not the AWT
- * event dispatching thread.
- */
- public static void assertIsEventDispatchingThread()
- {
- if (!SwingUtilities.isEventDispatchThread())
- {
- throw new RuntimeException(
- "The methon can be called only on the AWT event dispatching"
- + " thread.");
- }
- }
-
- /**
- * Finds the <tt>CallPanel</tt>, if any, which depicts a specific
- * <tt>CallConference</tt>.
- * <p>
- * <b>Note</b>: The method can be called only on the AWT event dispatching
- * thread.
- * </p>
- *
- * @param conference the <tt>CallConference</tt> to find the depicting
- * <tt>CallPanel</tt> of
- * @return the <tt>CallPanel</tt> which depicts the specified
- * <tt>CallConference</tt> if such a <tt>CallPanel</tt> exists; otherwise,
- * <tt>null</tt>
- * @throws RuntimeException if the method is not called on the AWT event
- * dispatching thread
- */
- private static CallPanel findCallPanel(CallConference conference)
- {
- synchronized (callPanels)
- {
- return callPanels.get(conference);
- }
- }
-
- /**
- * Notifies {@link #callPanels} about a specific <tt>CallEvent</tt> received
- * by <tt>CallManager</tt> (because they may need to update their UI, for
- * example).
- * <p>
- * <b>Note</b>: The method can be called only on the AWT event dispatching
- * thread.
- * </p>
- *
- * @param ev the <tt>CallEvent</tt> received by <tt>CallManager</tt> which
- * is to be forwarded to <tt>callPanels</tt> for further
- * <tt>CallPanel</tt>-specific handling
- * @throws RuntimeException if the method is not called on the AWT event
- * dispatching thread
- */
- private static void forwardCallEventToCallPanels(CallEvent ev)
- {
- assertIsEventDispatchingThread();
-
- CallPanel[] callPanels;
-
- synchronized (CallManager.callPanels)
- {
- Collection<CallPanel> values = CallManager.callPanels.values();
-
- callPanels = values.toArray(new CallPanel[values.size()]);
- }
-
- for (CallPanel callPanel : callPanels)
- {
- try
- {
- callPanel.onCallEvent(ev);
- }
- catch (Exception ex)
- {
- /*
- * There is no practical reason while the failure of a CallPanel
- * to handle the CallEvent should cause the other CallPanels to
- * be left out-of-date.
- */
- logger.error("A CallPanel failed to handle a CallEvent", ex);
- }
- }
- }
-
- /**
- * Creates a call for the supplied operation set.
- * @param opSetClass the operation set to use to create call.
- * @param protocolProviderService the protocol provider
- * @param contact the contact address to call
- */
- static void createCall(Class<? extends OperationSet> opSetClass,
- ProtocolProviderService protocolProviderService,
- String contact)
- {
- createCall(opSetClass, protocolProviderService, contact, null);
- }
-
- /**
- * Creates a call for the supplied operation set.
- * @param opSetClass the operation set to use to create call.
- * @param protocolProviderService the protocol provider
- * @param contact the contact address to call
- * @param uiContact the <tt>MetaContact</tt> we're calling
- */
- static void createCall(
- Class<? extends OperationSet> opSetClass,
- ProtocolProviderService protocolProviderService,
- String contact,
- UIContactImpl uiContact)
- {
- if (opSetClass.equals(OperationSetBasicTelephony.class))
- {
- createCall(protocolProviderService, contact, uiContact);
- }
- else if (opSetClass.equals(OperationSetVideoTelephony.class))
- {
- createVideoCall(protocolProviderService, contact, uiContact);
- }
- else if (opSetClass.equals(
- OperationSetDesktopSharingServer.class))
- {
- createDesktopSharing(
- protocolProviderService, contact, uiContact);
- }
- }
-
- /**
- * Creates a call for the default contact of the metacontact
- *
- * @param metaContact the metacontact that will be called.
- * @param isVideo if <tt>true</tt> will create video call.
- * @param isDesktop if <tt>true</tt> will share the desktop.
- * @param shareRegion if <tt>true</tt> will share a region of the desktop.
- */
- public static void call(MetaContact metaContact,
- boolean isVideo,
- boolean isDesktop,
- boolean shareRegion)
- {
- Contact contact = metaContact
- .getDefaultContact(getOperationSetForCall(isVideo, isDesktop));
-
- call(contact, isVideo, isDesktop, shareRegion);
- }
-
- /**
- * A particular contact has been selected no options to select
- * will just call it.
- * @param contact the contact to call
- * @param contactResource the specific contact resource
- * @param isVideo is video enabled
- * @param isDesktop is desktop sharing enabled
- * @param shareRegion is sharing the whole desktop or just a region.
- */
- public static void call(Contact contact,
- ContactResource contactResource,
- boolean isVideo,
- boolean isDesktop,
- boolean shareRegion)
- {
- if(isDesktop)
- {
- if(shareRegion)
- {
- createRegionDesktopSharing(
- contact.getProtocolProvider(),
- contact.getAddress(),
- null);
- }
- else
- createDesktopSharing(contact.getProtocolProvider(),
- contact.getAddress(),
- null);
- }
- else
- {
- new CreateCallThread(
- contact.getProtocolProvider(),
- contact,
- contactResource,
- isVideo).start();
- }
- }
-
- /**
- * Creates a call to the conference described in
- * <tt>conferenceDescription</tt> through <tt>protocolProvider</tt>
- *
- * @param protocolProvider the protocol provider through which to create
- * the call
- * @param conferenceDescription the description of the conference to call
- * @param chatRoom the chat room associated with the call.
- */
- public static void call(ProtocolProviderService protocolProvider,
- ConferenceDescription conferenceDescription,
- ChatRoom chatRoom)
- {
- new CreateCallThread(protocolProvider, conferenceDescription, chatRoom)
- .start();
- }
-
- /**
- * A particular contact has been selected no options to select
- * will just call it.
- * @param contact the contact to call
- * @param isVideo is video enabled
- * @param isDesktop is desktop sharing enabled
- * @param shareRegion is sharing the whole desktop or just a region.
- */
- public static void call(Contact contact,
- boolean isVideo,
- boolean isDesktop,
- boolean shareRegion)
- {
- if(isDesktop)
- {
- if(shareRegion)
- {
- createRegionDesktopSharing(
- contact.getProtocolProvider(),
- contact.getAddress(),
- null);
- }
- else
- createDesktopSharing(contact.getProtocolProvider(),
- contact.getAddress(),
- null);
- }
- else
- {
- new CreateCallThread( contact.getProtocolProvider(),
- contact,
- null,
- isVideo).start();
- }
- }
-
- /**
- * Calls a phone showing a dialog to choose a provider.
- * @param phone phone to call
- * @param isVideo if <tt>true</tt> will create video call.
- * @param isDesktop if <tt>true</tt> will share the desktop.
- * @param shareRegion if <tt>true</tt> will share a region of the desktop.
- */
- public static void call(final String phone,
- boolean isVideo,
- boolean isDesktop,
- final boolean shareRegion)
- {
- call(phone, null, isVideo, isDesktop, shareRegion);
- }
-
- /**
- * Calls a phone showing a dialog to choose a provider.
- * @param phone phone to call
- * @param uiContact the <tt>UIContactImpl</tt> we're calling
- * @param isVideo if <tt>true</tt> will create video call.
- * @param isDesktop if <tt>true</tt> will share the desktop.
- * @param shareRegion if <tt>true</tt> will share a region of the desktop.
- */
- public static void call(final String phone,
- final UIContactImpl uiContact,
- boolean isVideo,
- boolean isDesktop,
- final boolean shareRegion)
- {
- List<ProtocolProviderService> providers =
- CallManager.getTelephonyProviders();
-
- if(providers.size() > 1)
- {
- ChooseCallAccountDialog chooseAccount =
- new ChooseCallAccountDialog(
- phone,
- getOperationSetForCall(isVideo, isDesktop),
- providers)
- {
- @Override
- public void callButtonPressed()
- {
- if(shareRegion)
- {
- createRegionDesktopSharing(
- getSelectedProvider(), phone, uiContact);
- }
- else
- super.callButtonPressed();
- }
- };
- chooseAccount.setUIContact(uiContact);
- chooseAccount.setVisible(true);
- }
- else
- {
- createCall(providers.get(0), phone, uiContact);
- }
- }
-
- /**
- * Obtain operation set checking the params.
- * @param isVideo if <tt>true</tt> use OperationSetVideoTelephony.
- * @param isDesktop if <tt>true</tt> use OperationSetDesktopSharingServer.
- * @return the operation set, default is OperationSetBasicTelephony.
- */
- private static Class<? extends OperationSet> getOperationSetForCall(
- boolean isVideo, boolean isDesktop)
- {
- if(isVideo)
- {
- if(isDesktop)
- return OperationSetDesktopSharingServer.class;
- else
- return OperationSetVideoTelephony.class;
- }
- else
- return OperationSetBasicTelephony.class;
- }
-
- /**
- * Call any of the supplied details.
- *
- * @param uiContactDetailList the list with details to choose for calling
- * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled,
- * available.
- * @param isVideo if <tt>true</tt> will create video call.
- * @param isDesktop if <tt>true</tt> will share the desktop.
- * @param invoker the invoker component
- * @param location the location where this was invoked.
- */
- public static void call(List<UIContactDetail> uiContactDetailList,
- UIContactImpl uiContact,
- boolean isVideo,
- boolean isDesktop,
- JComponent invoker,
- Point location)
- {
- call(uiContactDetailList,
- uiContact,
- isVideo,
- isDesktop,
- invoker,
- location,
- true);
- }
-
- /**
- * Call any of the supplied details.
- *
- * @param uiContactDetailList the list with details to choose for calling
- * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled,
- * available.
- * @param isVideo if <tt>true</tt> will create video call.
- * @param isDesktop if <tt>true</tt> will share the desktop.
- * @param invoker the invoker component
- * @param location the location where this was invoked.
- * @param usePreferredProvider whether to use the <tt>uiContact</tt>
- * preferredProvider if provided.
- */
- private static void call(List<UIContactDetail> uiContactDetailList,
- UIContactImpl uiContact,
- boolean isVideo,
- boolean isDesktop,
- JComponent invoker,
- Point location,
- boolean usePreferredProvider)
- {
- ChooseCallAccountPopupMenu chooseCallAccountPopupMenu = null;
-
- Class<? extends OperationSet> opsetClass =
- getOperationSetForCall(isVideo, isDesktop);
-
- UIPhoneUtil contactPhoneUtil = null;
- if (uiContact != null
- && uiContact.getDescriptor() instanceof MetaContact)
- contactPhoneUtil = UIPhoneUtil
- .getPhoneUtil((MetaContact) uiContact.getDescriptor());
-
- if(contactPhoneUtil != null)
- {
- boolean addAdditionalNumbers = false;
- if(!isVideo
- || ConfigurationUtils
- .isRouteVideoAndDesktopUsingPhoneNumberEnabled())
- {
- addAdditionalNumbers = true;
- }
- else
- {
- if(isVideo && contactPhoneUtil != null)
- {
- // lets check is video enabled in additional numbers
- addAdditionalNumbers =
- contactPhoneUtil.isVideoCallEnabled() ?
- isDesktop ?
- contactPhoneUtil.isDesktopSharingEnabled()
- : true
- : false;
- }
- }
-
- if(addAdditionalNumbers)
- {
- uiContactDetailList.addAll(
- contactPhoneUtil.getAdditionalNumbers());
- }
- }
-
- if (uiContactDetailList.size() == 1)
- {
- UIContactDetail detail = uiContactDetailList.get(0);
-
- ProtocolProviderService preferredProvider = null;
-
- if(usePreferredProvider)
- preferredProvider =
- detail.getPreferredProtocolProvider(opsetClass);
-
- List<ProtocolProviderService> providers = null;
- String protocolName = null;
-
- if (preferredProvider != null)
- {
- if (preferredProvider.isRegistered())
- {
- createCall(opsetClass,
- preferredProvider,
- detail.getAddress(),
- uiContact);
- }
-
- // If we have a provider, but it's not registered we try to
- // obtain all registered providers for the same protocol as the
- // given preferred provider.
- else
- {
- protocolName = preferredProvider.getProtocolName();
- providers = AccountUtils.getRegisteredProviders(protocolName,
- opsetClass);
- }
- }
- // If we don't have a preferred provider we try to obtain a
- // preferred protocol name and all registered providers for it.
- else
- {
- protocolName = detail.getPreferredProtocol(opsetClass);
-
- if (protocolName != null)
- providers
- = AccountUtils.getRegisteredProviders(protocolName,
- opsetClass);
- else
- providers
- = AccountUtils.getRegisteredProviders(opsetClass);
- }
-
- // If our call didn't succeed, try to call through one of the other
- // protocol providers obtained above.
- if (providers != null)
- {
- int providersCount = providers.size();
-
- if (providersCount <= 0)
- {
- new ErrorDialog(null,
- GuiActivator.getResources().getI18NString(
- "service.gui.CALL_FAILED"),
- GuiActivator.getResources().getI18NString(
- "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
- .showDialog();
- }
- else if (providersCount == 1)
- {
- createCall(
- opsetClass,
- providers.get(0),
- detail.getAddress(),
- uiContact);
- }
- else if (providersCount > 1)
- {
- chooseCallAccountPopupMenu =
- new ChooseCallAccountPopupMenu(
- invoker, detail.getAddress(), providers,
- opsetClass);
- }
- }
- }
- else if (uiContactDetailList.size() > 1)
- {
- chooseCallAccountPopupMenu
- = new ChooseCallAccountPopupMenu(invoker, uiContactDetailList,
- opsetClass);
- }
-
- // If the choose dialog is created we're going to show it.
- if (chooseCallAccountPopupMenu != null)
- {
- if (uiContact != null)
- chooseCallAccountPopupMenu.setUIContact(uiContact);
-
- chooseCallAccountPopupMenu.showPopupMenu(location.x, location.y);
- }
- }
-
-
- /**
- * Call the ui contact.
- *
- * @param uiContact the contact to call.
- * @param isVideo if <tt>true</tt> will create video call.
- * @param isDesktop if <tt>true</tt> will share the desktop.
- * @param invoker the invoker component
- * @param location the location where this was invoked.
- */
- public static void call(UIContact uiContact,
- boolean isVideo,
- boolean isDesktop,
- JComponent invoker,
- Point location)
- {
- UIContactImpl uiContactImpl = null;
- if(uiContact instanceof UIContactImpl)
- {
- uiContactImpl = (UIContactImpl) uiContact;
- }
-
- List<UIContactDetail> telephonyContacts
- = uiContact.getContactDetailsForOperationSet(
- getOperationSetForCall(isVideo, isDesktop));
-
- boolean ignorePreferredProvider =
- GuiActivator.getConfigurationService().getBoolean(
- IGNORE_PREFERRED_PROVIDER_PROP, false);
-
- call( telephonyContacts,
- uiContactImpl,
- isVideo,
- isDesktop,
- invoker,
- location,
- !ignorePreferredProvider);
- }
-
- /**
- * Tries to resolves a peer address into a display name, by reqesting the
- * <tt>ContactSourceService</tt>s. This function returns only the
- * first match.
- *
- * @param peerAddress The peer address.
- *
- * @return The corresponding display name, if there is a match. Null
- * otherwise.
- */
- private static String resolveContactSource(String peerAddress)
- {
- String displayName = null;
-
- if(!StringUtils.isNullOrEmpty(peerAddress))
- {
- Vector<ResolveAddressToDisplayNameContactQueryListener> resolvers
- = new Vector<ResolveAddressToDisplayNameContactQueryListener>
- (1, 1);
-
- // will strip the @server-address part, as the regular expression
- // will match it
- int index = peerAddress.indexOf("@");
- String peerUserID =
- (index > -1) ? peerAddress.substring(0, index) : peerAddress;
-
- // searches for the whole number/username or with the @serverpart
- peerUserID = Pattern.quote(peerUserID);
- Pattern pattern = Pattern.compile(
- "^(" + peerUserID + "|" + peerUserID + "@.*)$");
-
- // Queries all available resolvers
- for(ContactSourceService contactSourceService:
- GuiActivator.getContactSources())
- {
- if(!(contactSourceService
- instanceof ExtendedContactSourceService))
- continue;
-
- // use the pattern method of (ExtendedContactSourceService)
- ContactQuery query
- = ((ExtendedContactSourceService)contactSourceService)
- .queryContactSource(pattern);
-
- resolvers.add(
- new ResolveAddressToDisplayNameContactQueryListener(
- query));
- }
-
- long startTime = System.currentTimeMillis();
- long currentTime = startTime;
- // The detault timeout is set to 500ms.
- long timeout = 500;
- // Loops until we found a valid display name, or waits for timeout.
- while(displayName == null
- && currentTime - startTime < timeout)
- {
- for(int i = 0; i < resolvers.size() && displayName == null; ++i)
- {
- ResolveAddressToDisplayNameContactQueryListener resolver
- = resolvers.get(i);
- if(!resolver.isRunning())
- {
- if(resolver.isFound())
- {
- displayName = resolver.getResolvedName();
- // If this is the same result as the peer address,
- // then that is not what we are looking for. Then,
- // continue the search.
- if(displayName.equals(peerAddress))
- {
- displayName = null;
- }
- }
- }
- }
- Thread.yield();
- currentTime = System.currentTimeMillis();
- }
-
- // Free lasting resolvers.
- for(int i = 0; i < resolvers.size(); ++i)
- {
- ResolveAddressToDisplayNameContactQueryListener resolver
- = resolvers.get(i);
- if(resolver.isRunning())
- {
- resolver.stop();
- }
- }
- }
-
- return displayName;
- }
-}
+/*
+ * 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.gui.main.call;
+
+import java.awt.*;
+import java.lang.ref.*;
+import java.text.*;
+import java.util.*;
+import java.util.List;
+import java.util.regex.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.impl.gui.customcontrols.*;
+import net.java.sip.communicator.impl.gui.main.*;
+import net.java.sip.communicator.impl.gui.main.contactlist.*;
+import net.java.sip.communicator.plugin.desktoputil.*;
+import net.java.sip.communicator.plugin.desktoputil.transparent.*;
+import net.java.sip.communicator.service.contactlist.*;
+import net.java.sip.communicator.service.contactsource.*;
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.Logger;
+import net.java.sip.communicator.util.account.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.codec.*;
+import org.jitsi.service.neomedia.device.*;
+import org.jitsi.service.neomedia.format.*;
+import org.jitsi.service.resources.*;
+import org.jitsi.util.*;
+
+/**
+ * The <tt>CallManager</tt> is the one that handles calls. It contains also
+ * the "Call" and "Hang up" buttons panel. Here are handles incoming and
+ * outgoing calls from and to the call operation set.
+ *
+ * @author Yana Stamcheva
+ * @author Lyubomir Marinov
+ * @author Boris Grozev
+ */
+public class CallManager
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>CallManager</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger = Logger.getLogger(CallManager.class);
+
+ /**
+ * The name of the property which indicates whether the user should be
+ * warned when starting a desktop sharing session.
+ */
+ private static final String desktopSharingWarningProperty
+ = "net.java.sip.communicator.impl.gui.main"
+ + ".call.SHOW_DESKTOP_SHARING_WARNING";
+
+ /**
+ * The name of the property which indicates whether the preferred provider
+ * will be used when calling UIContact (call history).
+ */
+ private static final String IGNORE_PREFERRED_PROVIDER_PROP
+ = "net.java.sip.communicator.impl.gui.main"
+ + ".call.IGNORE_PREFERRED_PROVIDER_PROP";
+
+ /**
+ * The <tt>CallPanel</tt>s opened by <tt>CallManager</tt> (because
+ * <tt>CallContainer</tt> does not give access to such lists.)
+ */
+ private static final Map<CallConference, CallPanel> callPanels
+ = new HashMap<CallConference, CallPanel>();
+
+ /**
+ * A map of active outgoing calls per <tt>UIContactImpl</tt>.
+ */
+ private static Map<Call, UIContactImpl> uiContactCalls;
+
+ /**
+ * The group of notifications dedicated to missed calls.
+ */
+ private static UINotificationGroup missedCallGroup;
+
+ /**
+ * A <tt>CallListener</tt>.
+ */
+ public static class GuiCallListener
+ extends SwingCallListener
+ {
+ /**
+ * Maps for incoming call handlers. The handlers needs to be created
+ * in the protocol thread while their method
+ * incomingCallReceivedInEventDispatchThread will be called on EDT.
+ * On the protocol thread a call state changed listener is added,
+ * if this is done on the EDT there is a almost no gap between incoming
+ * CallEvent and call state changed when doing auto answer and we
+ * end up with call answered and dialog for incoming call.
+ */
+ private Map<CallEvent,WeakReference<IncomingCallHandler>>
+ inCallHandlers = Collections.synchronizedMap(
+ new WeakHashMap<CallEvent,
+ WeakReference<IncomingCallHandler>>());
+
+ /**
+ * Delivers the <tt>CallEvent</tt> in the protocol thread.
+ */
+ public void incomingCallReceived(CallEvent ev)
+ {
+ inCallHandlers.put(
+ ev,
+ new WeakReference<IncomingCallHandler>(
+ new IncomingCallHandler(ev.getSourceCall())));
+
+ super.incomingCallReceived(ev);
+ }
+
+ /**
+ * Implements {@link CallListener#incomingCallReceived(CallEvent)}. When
+ * a call is received, creates a <tt>ReceivedCallDialog</tt> and plays
+ * the ring phone sound to the user.
+ *
+ * @param ev the <tt>CallEvent</tt>
+ */
+ @Override
+ public void incomingCallReceivedInEventDispatchThread(CallEvent ev)
+ {
+ WeakReference<IncomingCallHandler> ihRef
+ = inCallHandlers.remove(ev);
+
+ if(ihRef != null)
+ {
+ ihRef.get().incomingCallReceivedInEventDispatchThread(ev);
+ }
+ }
+
+ /**
+ * Implements CallListener.callEnded. Stops sounds that are playing at
+ * the moment if there're any. Removes the <tt>CallPanel</tt> and
+ * disables the hang-up button.
+ *
+ * @param ev the <tt>CallEvent</tt> which specifies the <tt>Call</tt>
+ * that has ended
+ */
+ @Override
+ public void callEndedInEventDispatchThread(CallEvent ev)
+ {
+ CallConference callConference = ev.getCallConference();
+
+ closeCallContainerIfNotNecessary(callConference);
+
+ /*
+ * Notify the existing CallPanels about the CallEvent (in case
+ * they need to update their UI, for example).
+ */
+ forwardCallEventToCallPanels(ev);
+
+ // If we're currently in the call history view, refresh
+ // it.
+ TreeContactList contactList
+ = GuiActivator.getContactList();
+
+ if (contactList.getCurrentFilter().equals(
+ TreeContactList.historyFilter))
+ {
+ contactList.applyFilter(
+ TreeContactList.historyFilter);
+ }
+ }
+
+ /**
+ * Creates and opens a call dialog. Implements
+ * {@link CallListener#outgoingCallCreated(CallEvent)}.
+ *
+ * @param ev the <tt>CallEvent</tt>
+ */
+ @Override
+ public void outgoingCallCreatedInEventDispatchThread(CallEvent ev)
+ {
+ Call sourceCall = ev.getSourceCall();
+
+ openCallContainerIfNecessary(sourceCall);
+
+ /*
+ * Notify the existing CallPanels about the CallEvent (in case they
+ * need to update their UI, for example).
+ */
+ forwardCallEventToCallPanels(ev);
+ }
+ }
+
+ /**
+ * Handles incoming calls. Must be created on the protocol thread while the
+ * method incomingCallReceivedInEventDispatchThread is executed on the EDT.
+ */
+ private static class IncomingCallHandler
+ extends CallChangeAdapter
+ {
+ /**
+ * The dialog shown
+ */
+ private ReceivedCallDialog receivedCallDialog;
+
+ /**
+ * Peer name.
+ */
+ private String peerName;
+
+ /**
+ * The time of the incoming call.
+ */
+ private long callTime;
+
+ /**
+ * Construct
+ * @param sourceCall
+ */
+ IncomingCallHandler(Call sourceCall)
+ {
+ Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();
+
+ if(!peerIter.hasNext())
+ {
+ return;
+ }
+
+ peerName = peerIter.next().getDisplayName();
+ callTime = System.currentTimeMillis();
+
+ sourceCall.addCallChangeListener(this);
+ }
+
+ /**
+ * State has changed.
+ * @param ev
+ */
+ @Override
+ public void callStateChanged(final CallChangeEvent ev)
+ {
+ if(!SwingUtilities.isEventDispatchThread())
+ {
+ SwingUtilities.invokeLater(
+ new Runnable()
+ {
+ public void run()
+ {
+ callStateChanged(ev);
+ }
+ });
+ return;
+ }
+ if (!CallChangeEvent.CALL_STATE_CHANGE
+ .equals(ev.getPropertyName()))
+ return;
+
+ // When the call state changes, we ensure here that the
+ // received call notification dialog is closed.
+ if (receivedCallDialog != null && receivedCallDialog.isVisible())
+ receivedCallDialog.setVisible(false);
+
+ // Ensure that the CallDialog is created, because it is the
+ // one that listens for CallPeers.
+ Object newValue = ev.getNewValue();
+ Call call = ev.getSourceCall();
+
+ if (CallState.CALL_INITIALIZATION.equals(newValue)
+ || CallState.CALL_IN_PROGRESS.equals(newValue))
+ {
+ openCallContainerIfNecessary(call);
+ }
+ else if (CallState.CALL_ENDED.equals(newValue))
+ {
+ if (ev.getOldValue().equals(
+ CallState.CALL_INITIALIZATION))
+ {
+ // If the call was answered elsewhere, don't mark it
+ // as missed.
+ CallPeerChangeEvent cause = ev.getCause();
+
+ if ((cause == null)
+ || (cause.getReasonCode()
+ != CallPeerChangeEvent
+ .NORMAL_CALL_CLEARING))
+ {
+ addMissedCallNotification(peerName, callTime);
+ }
+ }
+
+ call.removeCallChangeListener(this);
+ }
+ }
+
+ /**
+ * Executed on EDT cause will create dialog and will show it.
+ * @param ev
+ */
+ public void incomingCallReceivedInEventDispatchThread(CallEvent ev)
+ {
+ Call sourceCall = ev.getSourceCall();
+ boolean isVideoCall
+ = ev.isVideoCall()
+ && ConfigurationUtils.hasEnabledVideoFormat(
+ sourceCall.getProtocolProvider());
+ receivedCallDialog = new ReceivedCallDialog(
+ sourceCall,
+ isVideoCall,
+ (CallManager.getInProgressCalls().size() > 0));
+
+ receivedCallDialog.setVisible(true);
+
+ Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();
+
+ if(!peerIter.hasNext())
+ {
+ if (receivedCallDialog.isVisible())
+ receivedCallDialog.setVisible(false);
+ return;
+ }
+
+ /*
+ * Notify the existing CallPanels about the CallEvent (in case they
+ * need to update their UI, for example).
+ */
+ forwardCallEventToCallPanels(ev);
+ }
+ }
+
+ /**
+ * Answers the given call.
+ *
+ * @param call the call to answer
+ */
+ public static void answerCall(Call call)
+ {
+ answerCall(call, null, false /* without video */);
+ }
+
+ /**
+ * Answers a specific <tt>Call</tt> with or without video and, optionally,
+ * does that in a telephony conference with an existing <tt>Call</tt>.
+ *
+ * @param call
+ * @param existingCall
+ * @param video
+ */
+ private static void answerCall(Call call, Call existingCall, boolean video)
+ {
+ if (existingCall == null)
+ openCallContainerIfNecessary(call);
+
+ new AnswerCallThread(call, existingCall, video).start();
+ }
+
+ /**
+ * Answers the given call in an existing call. It will end up with a
+ * conference call.
+ *
+ * @param call the call to answer
+ */
+ public static void answerCallInFirstExistingCall(Call call)
+ {
+ // Find the first existing call.
+ Iterator<Call> existingCallIter = getInProgressCalls().iterator();
+ Call existingCall
+ = existingCallIter.hasNext() ? existingCallIter.next() : null;
+
+ answerCall(call, existingCall, false /* without video */);
+ }
+
+ /**
+ * Merges specific existing <tt>Call</tt>s into a specific telephony
+ * conference.
+ *
+ * @param conference the conference
+ * @param calls list of calls
+ */
+ public static void mergeExistingCalls(
+ CallConference conference,
+ Collection<Call> calls)
+ {
+ new MergeExistingCalls(conference, calls).start();
+ }
+
+ /**
+ * Answers the given call with video.
+ *
+ * @param call the call to answer
+ */
+ public static void answerVideoCall(Call call)
+ {
+ answerCall(call, null, true /* with video */);
+ }
+
+ /**
+ * Hang ups the given call.
+ *
+ * @param call the call to hang up
+ */
+ public static void hangupCall(Call call)
+ {
+ new HangupCallThread(call).start();
+ }
+
+ /**
+ * Hang ups the given <tt>callPeer</tt>.
+ *
+ * @param peer the <tt>CallPeer</tt> to hang up
+ */
+ public static void hangupCallPeer(CallPeer peer)
+ {
+ new HangupCallThread(peer).start();
+ }
+
+ /**
+ * Asynchronously hangs up the <tt>Call</tt>s participating in a specific
+ * <tt>CallConference</tt>.
+ *
+ * @param conference the <tt>CallConference</tt> whose participating
+ * <tt>Call</tt>s are to be hanged up
+ */
+ public static void hangupCalls(CallConference conference)
+ {
+ new HangupCallThread(conference).start();
+ }
+
+ /**
+ * Creates a call to the contact represented by the given string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ */
+ public static void createCall( ProtocolProviderService protocolProvider,
+ String contact)
+ {
+ new CreateCallThread(protocolProvider, contact, false /* audio-only */)
+ .start();
+ }
+
+ /**
+ * Creates a call to the contact represented by the given string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ * @param uiContact the meta contact we're calling
+ */
+ public static void createCall( ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact)
+ {
+ new CreateCallThread(protocolProvider, null, null, uiContact,
+ contact, null, null, false /* audio-only */).start();
+ }
+
+ /**
+ * Creates a video call to the contact represented by the given string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ */
+ public static void createVideoCall(ProtocolProviderService protocolProvider,
+ String contact)
+ {
+ new CreateCallThread(protocolProvider, contact, true /* video */)
+ .start();
+ }
+
+ /**
+ * Creates a video call to the contact represented by the given string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ */
+ public static void createVideoCall( ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact)
+ {
+ new CreateCallThread(protocolProvider, null, null, uiContact,
+ contact, null, null, true /* video */).start();
+ }
+
+ /**
+ * Enables/disables local video for a specific <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> to enable/disable to local video for
+ * @param enable <tt>true</tt> to enable the local video; otherwise,
+ * <tt>false</tt>
+ */
+ public static void enableLocalVideo(Call call, boolean enable)
+ {
+ new EnableLocalVideoThread(call, enable).start();
+ }
+
+ /**
+ * Indicates if the desktop sharing is currently enabled for the given
+ * <tt>call</tt>.
+ *
+ * @param call the <tt>Call</tt>, for which we would to check if the desktop
+ * sharing is currently enabled
+ * @return <tt>true</tt> if the desktop sharing is currently enabled for the
+ * given <tt>call</tt>, <tt>false</tt> otherwise
+ */
+ public static boolean isLocalVideoEnabled(Call call)
+ {
+ OperationSetVideoTelephony telephony
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetVideoTelephony.class);
+
+ return (telephony != null) && telephony.isLocalVideoAllowed(call);
+ }
+
+ /**
+ * Creates a desktop sharing call to the contact represented by the given
+ * string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ */
+ private static void createDesktopSharing(
+ ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact)
+ {
+ // If the user presses cancel on the desktop sharing warning then we
+ // have nothing more to do here.
+ if (!showDesktopSharingWarning())
+ return;
+
+ MediaService mediaService = GuiActivator.getMediaService();
+ List<MediaDevice> desktopDevices
+ = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
+ int deviceNumber = desktopDevices.size();
+
+ if (deviceNumber == 1)
+ {
+ createDesktopSharing(
+ protocolProvider,
+ contact,
+ uiContact,
+ desktopDevices.get(0));
+ }
+ else if (deviceNumber > 1)
+ {
+ SelectScreenDialog selectDialog
+ = new SelectScreenDialog(desktopDevices);
+
+ selectDialog.setVisible(true);
+ if (selectDialog.getSelectedDevice() != null)
+ createDesktopSharing(
+ protocolProvider,
+ contact,
+ uiContact,
+ selectDialog.getSelectedDevice());
+ }
+ }
+
+ /**
+ * Creates a region desktop sharing through the given
+ * <tt>protocolProvider</tt> with the given <tt>contact</tt>.
+ *
+ * @param protocolProvider the <tt>ProtocolProviderService</tt>, through
+ * which the sharing session will be established
+ * @param contact the address of the contact recipient
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ */
+ private static void createRegionDesktopSharing(
+ ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact)
+ {
+ if (showDesktopSharingWarning())
+ {
+ TransparentFrame frame = DesktopSharingFrame
+ .createTransparentFrame(
+ protocolProvider, contact, uiContact, true);
+
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ }
+ }
+
+ /**
+ * Creates a desktop sharing call to the contact represented by the given
+ * string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ * @param uiContact the <tt>MetaContact</tt> we're calling
+ * @param x the x coordinate of the shared region
+ * @param y the y coordinated of the shared region
+ * @param width the width of the shared region
+ * @param height the height of the shared region
+ */
+ public static void createRegionDesktopSharing(
+ ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact,
+ int x,
+ int y,
+ int width,
+ int height)
+ {
+ MediaService mediaService = GuiActivator.getMediaService();
+
+ List<MediaDevice> desktopDevices = mediaService.getDevices(
+ MediaType.VIDEO, MediaUseCase.DESKTOP);
+
+ int deviceNumber = desktopDevices.size();
+
+ if (deviceNumber > 0)
+ {
+ createDesktopSharing(
+ protocolProvider,
+ contact,
+ uiContact,
+ mediaService.getMediaDeviceForPartialDesktopStreaming(
+ width,
+ height,
+ x,
+ y));
+ }
+ }
+
+ /**
+ * Creates a desktop sharing call to the contact represented by the given
+ * string.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param contact the contact to call to
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ * @param mediaDevice the media device corresponding to the screen to share
+ */
+ private static void createDesktopSharing(
+ ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact,
+ MediaDevice mediaDevice)
+ {
+ new CreateDesktopSharingThread( protocolProvider,
+ contact,
+ uiContact,
+ mediaDevice).start();
+ }
+
+ /**
+ * Enables the desktop sharing in an existing <tt>call</tt>.
+ *
+ * @param call the call for which desktop sharing should be enabled
+ * @param enable indicates if the desktop sharing should be enabled or
+ * disabled
+ */
+ public static void enableDesktopSharing(Call call, boolean enable)
+ {
+ if (!enable)
+ enableDesktopSharing(call, null, enable);
+ else if (showDesktopSharingWarning())
+ {
+ MediaService mediaService = GuiActivator.getMediaService();
+ List<MediaDevice> desktopDevices
+ = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
+ int deviceNumber = desktopDevices.size();
+
+ if (deviceNumber == 1)
+ enableDesktopSharing(call, null, enable);
+ else if (deviceNumber > 1)
+ {
+ SelectScreenDialog selectDialog
+ = new SelectScreenDialog(desktopDevices);
+
+ selectDialog.setVisible(true);
+
+ if (selectDialog.getSelectedDevice() != null)
+ enableDesktopSharing(
+ call, selectDialog.getSelectedDevice(), enable);
+ }
+ }
+
+ // in case we switch to video, disable remote control if it was
+ // enabled
+ enableDesktopRemoteControl(call.getCallPeers().next(), false);
+ }
+
+ /**
+ * Enables the region desktop sharing for the given call.
+ *
+ * @param call the call, for which the region desktop sharing should be
+ * enabled
+ * @param enable indicates if the desktop sharing should be enabled or
+ * disabled
+ */
+ public static void enableRegionDesktopSharing(Call call, boolean enable)
+ {
+ if (!enable)
+ enableDesktopSharing(call, null, enable);
+ else if (showDesktopSharingWarning())
+ {
+ TransparentFrame frame
+ = DesktopSharingFrame.createTransparentFrame(call, true);
+
+ frame.setVisible(true);
+ }
+ }
+
+ /**
+ * Creates a desktop sharing call to the contact represented by the given
+ * string.
+ *
+ * @param call the call for which desktop sharing should be enabled
+ * @param x the x coordinate of the shared region
+ * @param y the y coordinated of the shared region
+ * @param width the width of the shared region
+ * @param height the height of the shared region
+ */
+ public static void enableRegionDesktopSharing(
+ Call call,
+ int x,
+ int y,
+ int width,
+ int height)
+ {
+ // Use the default media device corresponding to the screen to share
+ MediaService mediaService = GuiActivator.getMediaService();
+
+ List<MediaDevice> desktopDevices = mediaService.getDevices(
+ MediaType.VIDEO, MediaUseCase.DESKTOP);
+
+ int deviceNumber = desktopDevices.size();
+
+ if (deviceNumber > 0)
+ {
+ boolean succeed = enableDesktopSharing(
+ call,
+ mediaService.getMediaDeviceForPartialDesktopStreaming(
+ width,
+ height,
+ x,
+ y),
+ true);
+ // If the region sharing succeed, then display the frame of the
+ // current region shared.
+ if(succeed)
+ {
+ TransparentFrame frame
+ = DesktopSharingFrame.createTransparentFrame(call, false);
+
+ frame.setVisible(true);
+ }
+ }
+
+ // in case we switch to video, disable remote control if it was
+ // enabled
+ enableDesktopRemoteControl(call.getCallPeers().next(), false);
+ }
+
+ /**
+ * Enables the desktop sharing in an existing <tt>call</tt>.
+ *
+ * @param call the call for which desktop sharing should be enabled
+ * @param mediaDevice the media device corresponding to the screen to share
+ * @param enable indicates if the desktop sharing should be enabled or
+ * disabled
+ *
+ * @return True if the desktop sharing succeed (we are currently sharing the
+ * whole or a part of the desktop). False, otherwise.
+ */
+ private static boolean enableDesktopSharing(Call call,
+ MediaDevice mediaDevice,
+ boolean enable)
+ {
+ OperationSetDesktopSharingServer desktopOpSet
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetDesktopSharingServer.class);
+ boolean enableSucceeded = false;
+
+ // This shouldn't happen at this stage, because we disable the button
+ // if the operation set isn't available.
+ if (desktopOpSet != null)
+ {
+ // First make sure the local video button is disabled.
+ if (enable && isLocalVideoEnabled(call))
+ getActiveCallContainer(call).setVideoButtonSelected(false);
+
+ try
+ {
+ if (mediaDevice != null)
+ {
+ desktopOpSet.setLocalVideoAllowed(
+ call,
+ mediaDevice,
+ enable);
+ }
+ else
+ desktopOpSet.setLocalVideoAllowed(call, enable);
+
+ enableSucceeded = true;
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error(
+ "Failed to toggle the streaming of local video.",
+ ex);
+ }
+ }
+
+ return (enable && enableSucceeded);
+ }
+
+ /**
+ * Indicates if the desktop sharing is currently enabled for the given
+ * <tt>call</tt>.
+ *
+ * @param call the <tt>Call</tt>, for which we would to check if the desktop
+ * sharing is currently enabled
+ * @return <tt>true</tt> if the desktop sharing is currently enabled for the
+ * given <tt>call</tt>, <tt>false</tt> otherwise
+ */
+ public static boolean isDesktopSharingEnabled(Call call)
+ {
+ OperationSetDesktopSharingServer desktopOpSet
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetDesktopSharingServer.class);
+
+ if (desktopOpSet != null
+ && desktopOpSet.isLocalVideoAllowed(call))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Indicates if the desktop sharing is currently enabled for the given
+ * <tt>call</tt>.
+ *
+ * @param call the <tt>Call</tt>, for which we would to check if the desktop
+ * sharing is currently enabled
+ * @return <tt>true</tt> if the desktop sharing is currently enabled for the
+ * given <tt>call</tt>, <tt>false</tt> otherwise
+ */
+ public static boolean isRegionDesktopSharingEnabled(Call call)
+ {
+ OperationSetDesktopSharingServer desktopOpSet
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetDesktopSharingServer.class);
+
+ if (desktopOpSet != null
+ && desktopOpSet.isPartialStreaming(call))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Enables/disables remote control when in a desktop sharing session with
+ * the given <tt>callPeer</tt>.
+ *
+ * @param callPeer the call peer for which we enable/disable remote control
+ * @param isEnable indicates if the remote control should be enabled
+ */
+ public static void enableDesktopRemoteControl( CallPeer callPeer,
+ boolean isEnable)
+ {
+ OperationSetDesktopSharingServer sharingOpSet
+ = callPeer.getProtocolProvider().getOperationSet(
+ OperationSetDesktopSharingServer.class);
+
+ if (sharingOpSet == null)
+ return;
+
+ if (isEnable)
+ sharingOpSet.enableRemoteControl(callPeer);
+ else
+ sharingOpSet.disableRemoteControl(callPeer);
+ }
+
+ /**
+ * Creates a call to the given call string. The given component indicates
+ * where should be shown the "call via" menu if needed.
+ *
+ * @param callString the string to call
+ * @param c the component, which indicates where should be shown the "call
+ * via" menu if needed
+ */
+ public static void createCall( String callString,
+ JComponent c)
+ {
+ createCall(callString, c, null);
+ }
+
+ /**
+ * Creates a call to the given call string. The given component indicates
+ * where should be shown the "call via" menu if needed.
+ *
+ * @param callString the string to call
+ * @param c the component, which indicates where should be shown the "call
+ * via" menu if needed
+ * @param l listener that is notified when the call interface has been
+ * started after call was created
+ */
+ public static void createCall( String callString,
+ JComponent c,
+ CallInterfaceListener l)
+ {
+ callString = callString.trim();
+
+ // Removes special characters from phone numbers.
+ if (ConfigurationUtils.isNormalizePhoneNumber())
+ callString = PhoneNumberI18nService.normalize(callString);
+
+ List<ProtocolProviderService> telephonyProviders
+ = CallManager.getTelephonyProviders();
+
+ if (telephonyProviders.size() == 1)
+ {
+ CallManager.createCall(
+ telephonyProviders.get(0), callString);
+
+ if (l != null)
+ l.callInterfaceStarted();
+ }
+ else if (telephonyProviders.size() > 1)
+ {
+ /*
+ * Allow plugins which do not have a (Jitsi) UI to create calls by
+ * automagically picking up a telephony provider.
+ */
+ if (c == null)
+ {
+ ProtocolProviderService preferredTelephonyProvider = null;
+
+ for (ProtocolProviderService telephonyProvider
+ : telephonyProviders)
+ {
+ try
+ {
+ OperationSetPresence presenceOpSet
+ = telephonyProvider.getOperationSet(
+ OperationSetPresence.class);
+
+ if ((presenceOpSet != null)
+ && (presenceOpSet.findContactByID(callString)
+ != null))
+ {
+ preferredTelephonyProvider = telephonyProvider;
+ break;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof ThreadDeath)
+ throw (ThreadDeath) t;
+ }
+ }
+ if (preferredTelephonyProvider == null)
+ preferredTelephonyProvider = telephonyProviders.get(0);
+
+ CallManager.createCall(preferredTelephonyProvider, callString);
+ if (l != null)
+ l.callInterfaceStarted();
+ }
+ else
+ {
+ ChooseCallAccountPopupMenu chooseAccountDialog
+ = new ChooseCallAccountPopupMenu(
+ c,
+ callString,
+ telephonyProviders,
+ l);
+
+ chooseAccountDialog.setLocation(c.getLocation());
+ chooseAccountDialog.showPopupMenu();
+ }
+ }
+ else
+ {
+ ResourceManagementService resources = GuiActivator.getResources();
+
+ new ErrorDialog(
+ null,
+ resources.getI18NString("service.gui.WARNING"),
+ resources.getI18NString(
+ "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
+ .showDialog();
+ }
+ }
+
+ /**
+ * Creates a call to the given list of contacts.
+ *
+ * @param protocolProvider the protocol provider to which this call belongs.
+ * @param callees the list of contacts to call to
+ */
+ public static void createConferenceCall(
+ String[] callees,
+ ProtocolProviderService protocolProvider)
+ {
+ Map<ProtocolProviderService, List<String>> crossProtocolCallees
+ = new HashMap<ProtocolProviderService, List<String>>();
+
+ crossProtocolCallees.put(protocolProvider, Arrays.asList(callees));
+ createConferenceCall(crossProtocolCallees);
+ }
+
+ /**
+ * Invites the given list of <tt>callees</tt> to the given conference
+ * <tt>call</tt>.
+ *
+ * @param callees the list of contacts to invite
+ * @param call the protocol provider to which this call belongs
+ */
+ public static void inviteToConferenceCall(String[] callees, Call call)
+ {
+ Map<ProtocolProviderService, List<String>> crossProtocolCallees
+ = new HashMap<ProtocolProviderService, List<String>>();
+
+ crossProtocolCallees.put(
+ call.getProtocolProvider(),
+ Arrays.asList(callees));
+ inviteToConferenceCall(crossProtocolCallees, call);
+ }
+
+ /**
+ * Invites the given list of <tt>callees</tt> to the given conference
+ * <tt>call</tt>.
+ *
+ * @param callees the list of contacts to invite
+ * @param call existing call
+ */
+ public static void inviteToConferenceCall(
+ Map<ProtocolProviderService, List<String>> callees,
+ Call call)
+ {
+ new InviteToConferenceCallThread(callees, call).start();
+ }
+
+ /**
+ * Invites specific <tt>callees</tt> to a specific telephony conference.
+ *
+ * @param callees the list of contacts to invite
+ * @param conference the telephony conference to invite the specified
+ * <tt>callees</tt> into
+ */
+ public static void inviteToConferenceCall(
+ Map<ProtocolProviderService, List<String>> callees,
+ CallConference conference)
+ {
+ /*
+ * InviteToConferenceCallThread takes a specific Call but actually
+ * invites to the telephony conference associated with the specified
+ * Call (if any). In order to not change the signature of its
+ * constructor at this time, just pick up a Call participating in the
+ * specified telephony conference (if any).
+ */
+ Call call = null;
+
+ if (conference != null)
+ {
+ List<Call> calls = conference.getCalls();
+
+ if (!calls.isEmpty())
+ call = calls.get(0);
+ }
+
+ new InviteToConferenceCallThread(callees, call).start();
+ }
+
+ /**
+ * Asynchronously creates a new conference <tt>Call</tt> with a specific
+ * list of participants/callees.
+ *
+ * @param callees the list of participants/callees to invite to a
+ * newly-created conference <tt>Call</tt>
+ */
+ public static void createConferenceCall(
+ Map<ProtocolProviderService, List<String>> callees)
+ {
+ new InviteToConferenceCallThread(callees, null).start();
+ }
+
+ /**
+ * Asynchronously creates a new video bridge conference <tt>Call</tt> with
+ * a specific list of participants/callees.
+ *
+ * @param callProvider the <tt>ProtocolProviderService</tt> to use for
+ * creating the call
+ * @param callees the list of participants/callees to invite to the
+ * newly-created video bridge conference <tt>Call</tt>
+ */
+ public static void createJitsiVideobridgeConfCall(
+ ProtocolProviderService callProvider,
+ String[] callees)
+ {
+ new InviteToConferenceBridgeThread(callProvider, callees, null).start();
+ }
+
+ /**
+ * Invites the given list of <tt>callees</tt> to the given conference
+ * <tt>call</tt>.
+ *
+ * @param callees the list of contacts to invite
+ * @param call the protocol provider to which this call belongs
+ */
+ public static void inviteToJitsiVideobridgeConfCall(String[] callees, Call call)
+ {
+ new InviteToConferenceBridgeThread( call.getProtocolProvider(),
+ callees,
+ call).start();
+ }
+
+ /**
+ * Puts on or off hold the given <tt>callPeer</tt>.
+ * @param callPeer the peer to put on/off hold
+ * @param isOnHold indicates the action (on hold or off hold)
+ */
+ public static void putOnHold(CallPeer callPeer, boolean isOnHold)
+ {
+ new PutOnHoldCallPeerThread(callPeer, isOnHold).start();
+ }
+
+ /**
+ * Transfers the given <tt>peer</tt> to the given <tt>target</tt>.
+ * @param peer the <tt>CallPeer</tt> to transfer
+ * @param target the <tt>CallPeer</tt> target to transfer to
+ */
+ public static void transferCall(CallPeer peer, CallPeer target)
+ {
+ OperationSetAdvancedTelephony<?> telephony
+ = peer.getCall().getProtocolProvider()
+ .getOperationSet(OperationSetAdvancedTelephony.class);
+
+ if (telephony != null)
+ {
+ try
+ {
+ telephony.transfer(peer, target);
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error("Failed to transfer " + peer.getAddress()
+ + " to " + target, ex);
+ }
+ }
+ }
+
+ /**
+ * Transfers the given <tt>peer</tt> to the given <tt>target</tt>.
+ * @param peer the <tt>CallPeer</tt> to transfer
+ * @param target the target of the transfer
+ */
+ public static void transferCall(CallPeer peer, String target)
+ {
+ OperationSetAdvancedTelephony<?> telephony
+ = peer.getCall().getProtocolProvider()
+ .getOperationSet(OperationSetAdvancedTelephony.class);
+
+ if (telephony != null)
+ {
+ try
+ {
+ telephony.transfer(peer, target);
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error("Failed to transfer " + peer.getAddress()
+ + " to " + target, ex);
+ }
+ }
+ }
+
+ /**
+ * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no
+ * longer necessary (i.e. is not used by other <tt>Call</tt>s participating
+ * in the same telephony conference as the specified <tt>Call</tt>.)
+ *
+ * @param callConference The <tt>CallConference</tt> which is to have its
+ * associated <tt>CallPanel</tt>, if any
+ */
+ private static void closeCallContainerIfNotNecessary(
+ final CallConference callConference)
+ {
+ CallPanel callPanel = callPanels.get(callConference);
+
+ if (callPanel != null)
+ closeCallContainerIfNotNecessary(
+ callConference, callPanel.isCloseWaitAfterHangup());
+ }
+
+ /**
+ * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no
+ * longer necessary (i.e. is not used by other <tt>Call</tt>s participating
+ * in the same telephony conference as the specified <tt>Call</tt>.)
+ *
+ * @param callConference The <tt>CallConference</tt> which is to have its
+ * associated <tt>CallPanel</tt>, if any, closed
+ * @param wait <tt>true</tt> to set <tt>delay</tt> param of
+ * {@link CallContainer#close(CallPanel, boolean)} (CallPanel)}
+ */
+ private static void closeCallContainerIfNotNecessary(
+ final CallConference callConference,
+ final boolean wait)
+ {
+ if (!SwingUtilities.isEventDispatchThread())
+ {
+ SwingUtilities.invokeLater(
+ new Runnable()
+ {
+ public void run()
+ {
+ closeCallContainerIfNotNecessary(
+ callConference,
+ wait);
+ }
+ });
+ return;
+ }
+
+ /*
+ * XXX The integrity of the execution of the method may be compromised
+ * if it is not invoked on the AWT event dispatching thread because
+ * findCallPanel and callPanels.remove must be atomically executed. The
+ * uninterrupted execution (with respect to the synchronization) is
+ * guaranteed by requiring all modifications to callPanels to be made on
+ * the AWT event dispatching thread.
+ */
+
+ for (Iterator<Map.Entry<CallConference, CallPanel>> entryIter
+ = callPanels.entrySet().iterator();
+ entryIter.hasNext();)
+ {
+ Map.Entry<CallConference, CallPanel> entry = entryIter.next();
+ CallConference aConference = entry.getKey();
+ boolean notNecessary = aConference.isEnded();
+
+ if (notNecessary)
+ {
+ CallPanel aCallPanel = entry.getValue();
+ CallContainer window = aCallPanel.getCallWindow();
+
+ try
+ {
+ window.close(
+ aCallPanel,
+ wait && (aConference == callConference));
+ }
+ finally
+ {
+ /*
+ * We allow non-modifications i.e. reads of callPanels on
+ * threads other than the AWT event dispatching thread so we
+ * have to make sure that we will not cause
+ * ConcurrentModificationException.
+ */
+ synchronized (callPanels)
+ {
+ entryIter.remove();
+ }
+
+ aCallPanel.dispose();
+ }
+ }
+ }
+ }
+
+ /**
+ * Opens a <tt>CallPanel</tt> for a specific <tt>Call</tt> if there is none.
+ * <p>
+ * <b>Note</b>: The method can be called only on the AWT event dispatching
+ * thread.
+ * </p>
+ *
+ * @param call the <tt>Call</tt> to open a <tt>CallPanel</tt> for
+ * @return the <tt>CallPanel</tt> associated with the <tt>Call</tt>
+ * @throws RuntimeException if the method is not called on the AWT event
+ * dispatching thread
+ */
+ private static CallPanel openCallContainerIfNecessary(Call call)
+ {
+ /*
+ * XXX The integrity of the execution of the method may be compromised
+ * if it is not invoked on the AWT event dispatching thread because
+ * findCallPanel and callPanels.put must be atomically executed. The
+ * uninterrupted execution (with respect to the synchronization) is
+ * guaranteed by requiring all modifications to callPanels to be made on
+ * the AWT event dispatching thread.
+ */
+ assertIsEventDispatchingThread();
+
+ /*
+ * CallPanel displays a CallConference (which may contain multiple
+ * Calls.)
+ */
+ CallConference conference = call.getConference();
+ CallPanel callPanel = findCallPanel(conference);
+
+ if (callPanel == null)
+ {
+ // If we're in single-window mode, the single window is the
+ // CallContainer.
+ CallContainer callContainer
+ = GuiActivator.getUIService().getSingleWindowContainer();
+
+ // If we're in multi-window mode, we create the CallDialog.
+ if (callContainer == null)
+ callContainer = new CallDialog();
+
+ callPanel = new CallPanel(conference, callContainer);
+ callContainer.addCallPanel(callPanel);
+
+ synchronized (callPanels)
+ {
+ callPanels.put(conference, callPanel);
+ }
+ }
+
+ return callPanel;
+ }
+
+ /**
+ * Returns a list of all currently registered telephony providers.
+ * @return a list of all currently registered telephony providers
+ */
+ public static List<ProtocolProviderService> getTelephonyProviders()
+ {
+ return AccountUtils
+ .getRegisteredProviders(OperationSetBasicTelephony.class);
+ }
+
+ /**
+ * Returns a list of all currently registered telephony providers supporting
+ * conferencing.
+ *
+ * @return a list of all currently registered telephony providers supporting
+ * conferencing
+ */
+ public static List<ProtocolProviderService>
+ getTelephonyConferencingProviders()
+ {
+ return AccountUtils
+ .getRegisteredProviders(OperationSetTelephonyConferencing.class);
+ }
+
+ /**
+ * Returns a list of all currently active calls.
+ *
+ * @return a list of all currently active calls
+ */
+ private static List<Call> getActiveCalls()
+ {
+ CallConference[] conferences;
+
+ synchronized (callPanels)
+ {
+ Set<CallConference> keySet = callPanels.keySet();
+
+ conferences = keySet.toArray(new CallConference[keySet.size()]);
+ }
+
+ List<Call> calls = new ArrayList<Call>();
+
+ for (CallConference conference : conferences)
+ {
+ for (Call call : conference.getCalls())
+ {
+ if (call.getCallState() == CallState.CALL_IN_PROGRESS)
+ calls.add(call);
+ }
+ }
+ return calls;
+ }
+
+ /**
+ * Returns a collection of all currently in progress calls. A call is active
+ * if it is in progress so the method merely delegates to
+ *
+ * @return a collection of all currently in progress calls.
+ */
+ public static Collection<Call> getInProgressCalls()
+ {
+ return getActiveCalls();
+ }
+
+ /**
+ * Returns the <tt>CallContainer</tt> corresponding to the given
+ * <tt>call</tt>. If the call has been finished and no active
+ * <tt>CallContainer</tt> could be found it returns null.
+ *
+ * @param call the <tt>Call</tt>, which dialog we're looking for
+ * @return the <tt>CallContainer</tt> corresponding to the given
+ * <tt>call</tt>
+ */
+ public static CallPanel getActiveCallContainer(Call call)
+ {
+ return findCallPanel(call.getConference());
+ }
+
+ /**
+ * A informative text to show for the peer. If display name is missing
+ * return the address.
+ * @param peer the peer.
+ * @return the text contain display name.
+ */
+ public static String getPeerDisplayName(CallPeer peer)
+ {
+ String displayName = null;
+
+ // We try to find the <tt>UIContact</tt>, to which the call was
+ // created if this was an outgoing call.
+ UIContactImpl uiContact
+ = CallManager.getCallUIContact(peer.getCall());
+
+ if(uiContact != null)
+ {
+ if(uiContact.getDescriptor() instanceof SourceContact)
+ {
+ // if it is source contact (history record)
+ // search for cusax contact match
+ Contact contact = getPeerCusaxContact(peer.getAddress(),
+ (SourceContact)uiContact.getDescriptor());
+ if(contact != null)
+ displayName = contact.getDisplayName();
+ }
+
+ if(StringUtils.isNullOrEmpty(displayName, true))
+ displayName = uiContact.getDisplayName();
+ }
+
+ // We search for a contact corresponding to this call peer and
+ // try to get its display name.
+ if (StringUtils.isNullOrEmpty(displayName, true)
+ && peer.getContact() != null)
+ {
+ displayName = peer.getContact().getDisplayName();
+ }
+
+ // We try to find the an alternative peer address.
+ if (StringUtils.isNullOrEmpty(displayName, true))
+ {
+ String imppAddress = peer.getAlternativeIMPPAddress();
+
+ if (!StringUtils.isNullOrEmpty(imppAddress))
+ {
+ int protocolPartIndex = imppAddress.indexOf(":");
+
+ imppAddress = (protocolPartIndex >= 0)
+ ? imppAddress.substring(protocolPartIndex + 1)
+ : imppAddress;
+
+ Collection<ProtocolProviderService> cusaxProviders
+ = AccountUtils.getRegisteredProviders(
+ OperationSetCusaxUtils.class);
+
+ if (cusaxProviders != null && cusaxProviders.size() > 0)
+ {
+ Contact contact = getPeerContact(
+ peer,
+ cusaxProviders.iterator().next(),
+ imppAddress);
+
+ displayName = (contact != null)
+ ? contact.getDisplayName() : null;
+ }
+ else
+ {
+ MetaContact metaContact
+ = getPeerMetaContact(peer, imppAddress);
+
+ displayName = (metaContact != null)
+ ? metaContact.getDisplayName() : null;
+ }
+ }
+ }
+
+ if (StringUtils.isNullOrEmpty(displayName, true))
+ {
+ displayName = (!StringUtils.isNullOrEmpty
+ (peer.getDisplayName(), true))
+ ? peer.getDisplayName()
+ : peer.getAddress();
+
+ // Try to resolve the display name
+ String resolvedName = resolveContactSource(displayName);
+ if(resolvedName != null)
+ {
+ displayName = resolvedName;
+ }
+ }
+
+ return displayName;
+ }
+
+ /**
+ * Returns the image corresponding to the given <tt>peer</tt>.
+ *
+ * @param peer the call peer, for which we're returning an image
+ * @return the peer image
+ */
+ public static byte[] getPeerImage(CallPeer peer)
+ {
+ byte[] image = null;
+ // We search for a contact corresponding to this call peer and
+ // try to get its image.
+ if (peer.getContact() != null)
+ {
+ image = getContactImage(peer.getContact());
+ }
+
+ // We try to find the <tt>UIContact</tt>, to which the call was
+ // created if this was an outgoing call.
+ if (image == null || image.length == 0)
+ {
+ UIContactImpl uiContact
+ = CallManager.getCallUIContact(peer.getCall());
+
+ if (uiContact != null)
+ {
+ if(uiContact.getDescriptor() instanceof SourceContact
+ && ((SourceContact)uiContact.getDescriptor())
+ .isDefaultImage())
+ {
+ // if it is source contact (history record)
+ // search for cusax contact match
+ Contact contact = getPeerCusaxContact(peer.getAddress(),
+ (SourceContact)uiContact.getDescriptor());
+
+ if(contact != null)
+ image = contact.getImage();
+ }
+ else
+ image = uiContact.getAvatar();
+ }
+ }
+
+ // We try to find the an alternative peer address.
+ if (image == null || image.length == 0)
+ {
+ String imppAddress = peer.getAlternativeIMPPAddress();
+
+ if (!StringUtils.isNullOrEmpty(imppAddress))
+ {
+ int protocolPartIndex = imppAddress.indexOf(":");
+
+ imppAddress = (protocolPartIndex >= 0)
+ ? imppAddress.substring(protocolPartIndex + 1)
+ : imppAddress;
+
+ Collection<ProtocolProviderService> cusaxProviders
+ = AccountUtils.getRegisteredProviders(
+ OperationSetCusaxUtils.class);
+
+ if (cusaxProviders != null && cusaxProviders.size() > 0)
+ {
+ Contact contact = getPeerContact(
+ peer,
+ cusaxProviders.iterator().next(),
+ imppAddress);
+
+ image = (contact != null) ? getContactImage(contact) : null;
+ }
+ else
+ {
+ MetaContact metaContact
+ = getPeerMetaContact(peer, imppAddress);
+
+ image = (metaContact != null)
+ ? metaContact.getAvatar() : null;
+ }
+ }
+ }
+
+ // If the icon is still null we try to get an image from the call
+ // peer.
+ if ((image == null || image.length == 0)
+ && peer.getImage() != null)
+ image = peer.getImage();
+
+ return image;
+ }
+
+ /**
+ * Searches the cusax enabled providers for a contact with
+ * the detail (address) of the call peer if found and the contact
+ * is provided by a provider which is IM capable, return the contact.
+ * @param peer the peer we are calling.
+ * @return the im capable contact corresponding the <tt>peer</tt>.
+ */
+ public static Contact getIMCapableCusaxContact(CallPeer peer)
+ {
+ // We try to find the <tt>UIContact</tt>, to which the call was
+ // created if this was an outgoing call.
+ UIContactImpl uiContact
+ = CallManager.getCallUIContact(peer.getCall());
+
+ if (uiContact != null)
+ {
+ if(uiContact.getDescriptor() instanceof MetaContact)
+ {
+ MetaContact metaContact =
+ (MetaContact)uiContact.getDescriptor();
+ Iterator<Contact> iter = metaContact.getContacts();
+ while(iter.hasNext())
+ {
+ Contact contact = iter.next();
+ if(contact.getProtocolProvider()
+ .getOperationSet(
+ OperationSetBasicInstantMessaging.class) != null)
+ return contact;
+ }
+ }
+ else if(uiContact.getDescriptor() instanceof SourceContact)
+ {
+ // if it is source contact (history record)
+ // search for cusax contact match
+ Contact contact = getPeerCusaxContact(peer.getAddress(),
+ (SourceContact)uiContact.getDescriptor());
+ if(contact != null
+ && contact.getProtocolProvider().getOperationSet(
+ OperationSetBasicInstantMessaging.class) != null)
+ return contact;
+ }
+ }
+
+ // We try to find the an alternative peer address.
+ String imppAddress = peer.getAlternativeIMPPAddress();
+
+ if (!StringUtils.isNullOrEmpty(imppAddress))
+ {
+ int protocolPartIndex = imppAddress.indexOf(":");
+
+ imppAddress = (protocolPartIndex >= 0)
+ ? imppAddress.substring(protocolPartIndex + 1)
+ : imppAddress;
+
+ Collection<ProtocolProviderService> cusaxProviders
+ = AccountUtils.getRegisteredProviders(
+ OperationSetCusaxUtils.class);
+
+ if (cusaxProviders != null && cusaxProviders.size() > 0)
+ {
+ ProtocolProviderService cusaxProvider
+ = cusaxProviders.iterator().next();
+
+ Contact contact = getPeerContact(
+ peer,
+ cusaxProvider,
+ imppAddress);
+
+ if(contact != null
+ && cusaxProvider.getOperationSet(
+ OperationSetBasicInstantMessaging.class) != null)
+ {
+ return contact;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Find is there a linked cusax protocol provider for this source contact,
+ * if it exist we try to resolve current peer to one of its contacts
+ * or details of a contact (numbers).
+ * @param peerAddress the address of the peer to check
+ * @param sourceContact the currently selected source contact.
+ * @return matching cusax contact.
+ */
+ private static Contact getPeerCusaxContact(
+ String peerAddress, SourceContact sourceContact)
+ {
+ ProtocolProviderService linkedCusaxProvider = null;
+ for(ContactDetail detail : sourceContact.getContactDetails())
+ {
+ ProtocolProviderService pps
+ = detail.getPreferredProtocolProvider(
+ OperationSetBasicTelephony.class);
+
+ if(pps != null)
+ {
+ OperationSetCusaxUtils cusaxOpSet =
+ pps.getOperationSet(OperationSetCusaxUtils.class);
+
+ if(cusaxOpSet != null)
+ {
+ linkedCusaxProvider
+ = cusaxOpSet.getLinkedCusaxProvider();
+ break;
+ }
+ }
+ }
+
+ if(linkedCusaxProvider != null)
+ {
+ OperationSetPersistentPresence opSetPersistentPresence
+ = linkedCusaxProvider.getOperationSet(
+ OperationSetPersistentPresence.class);
+
+ if(opSetPersistentPresence != null)
+ {
+ // will strip the @server-address part, as the regular expression
+ // will match it
+ int index = peerAddress.indexOf("@");
+ String peerUserID =
+ (index > -1) ? peerAddress.substring(0, index) : peerAddress;
+
+ // searches for the whole number/username or with the @serverpart
+ String peerUserIDQ = Pattern.quote(peerUserID);
+
+ Pattern pattern = Pattern.compile(
+ "^(" + peerUserIDQ + "|" + peerUserIDQ + "@.*)$");
+
+ return findContactByPeer(
+ peerUserID,
+ pattern,
+ opSetPersistentPresence.getServerStoredContactListRoot(),
+ linkedCusaxProvider.getOperationSet(
+ OperationSetCusaxUtils.class));
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds a matching cusax contact.
+ * @param peerUserID the userID of the call peer to search for
+ * @param searchPattern the pattern (userID | userID@...)
+ * @param parent the parent group of the groups and contacts to search in
+ * @param cusaxOpSet the opset of the provider which will be used to match
+ * contact's details to peer userID (stored numbers).
+ * @return a cusax matching contac
+ */
+ private static Contact findContactByPeer(
+ String peerUserID,
+ Pattern searchPattern,
+ ContactGroup parent,
+ OperationSetCusaxUtils cusaxOpSet)
+ {
+ Iterator<Contact> contactIterator = parent.contacts();
+ while(contactIterator.hasNext())
+ {
+ Contact contact = contactIterator.next();
+
+ if(searchPattern.matcher(contact.getAddress()).find()
+ || cusaxOpSet.doesDetailBelong(contact, peerUserID))
+ {
+ return contact;
+ }
+ }
+
+ Iterator<ContactGroup> groupsIterator = parent.subgroups();
+ while(groupsIterator.hasNext())
+ {
+ ContactGroup gr = groupsIterator.next();
+ Contact contact = findContactByPeer(
+ peerUserID, searchPattern, gr, cusaxOpSet);
+ if(contact != null)
+ return contact;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the image for the given contact.
+ *
+ * @param contact the <tt>Contact</tt>, which image we're looking for
+ * @return the array of bytes representing the image for the given contact
+ * or null if such image doesn't exist
+ */
+ private static byte[] getContactImage(Contact contact)
+ {
+ MetaContact metaContact = GuiActivator.getContactListService()
+ .findMetaContactByContact(contact);
+
+ if (metaContact != null)
+ return metaContact.getAvatar();
+
+ return null;
+ }
+
+ /**
+ * Returns the image for the given <tt>alternativePeerAddress</tt> by
+ * checking the if the <tt>callPeer</tt> exists as a detail in the given
+ * <tt>cusaxProvider</tt>.
+ *
+ * @param callPeer the <tt>CallPeer</tt> to check in the cusax provider
+ * details
+ * @param cusaxProvider the linked cusax <tt>ProtocolProviderService</tt>
+ * @param alternativePeerAddress the alternative peer address to obtain the
+ * image from
+ * @return the protocol <tt>Contact</tt> corresponding to the given
+ * <tt>alternativePeerAddress</tt>
+ */
+ private static Contact getPeerContact( CallPeer callPeer,
+ ProtocolProviderService cusaxProvider,
+ String alternativePeerAddress)
+ {
+ OperationSetPresence presenceOpSet
+ = cusaxProvider.getOperationSet(OperationSetPresence.class);
+
+ if (presenceOpSet == null)
+ return null;
+
+ Contact contact = presenceOpSet.findContactByID(alternativePeerAddress);
+
+ if (contact == null)
+ return null;
+
+ OperationSetCusaxUtils cusaxOpSet
+ = cusaxProvider.getOperationSet(OperationSetCusaxUtils.class);
+
+ if (cusaxOpSet != null && cusaxOpSet.doesDetailBelong(
+ contact, callPeer.getAddress()))
+ return contact;
+
+ return null;
+ }
+
+ /**
+ * Returns the metacontact for the given <tt>CallPeer</tt> by
+ * checking the if the <tt>callPeer</tt> contact exists, if not checks the
+ * contacts in our contact list that are provided by cusax enabled
+ * providers.
+ *
+ * @param peer the <tt>CallPeer</tt> to check in contact details
+ * @return the <tt>MetaContact</tt> corresponding to the given
+ * <tt>peer</tt>.
+ */
+ public static MetaContact getPeerMetaContact(CallPeer peer)
+ {
+ if(peer == null)
+ return null;
+
+ if(peer.getContact() != null)
+ return GuiActivator.getContactListService()
+ .findMetaContactByContact(peer.getContact());
+
+ // We try to find the <tt>UIContact</tt>, to which the call was
+ // created if this was an outgoing call.
+ UIContactImpl uiContact
+ = CallManager.getCallUIContact(peer.getCall());
+
+ if (uiContact != null)
+ {
+ if(uiContact.getDescriptor() instanceof MetaContact)
+ {
+ return (MetaContact)uiContact.getDescriptor();
+ }
+ else if(uiContact.getDescriptor() instanceof SourceContact)
+ {
+ // if it is a source contact check for matching cusax contact
+ Contact contact = getPeerCusaxContact(peer.getAddress(),
+ (SourceContact)uiContact.getDescriptor());
+ if(contact != null)
+ return GuiActivator.getContactListService()
+ .findMetaContactByContact(contact);
+ }
+ }
+
+ String imppAddress = peer.getAlternativeIMPPAddress();
+
+ if (!StringUtils.isNullOrEmpty(imppAddress))
+ {
+ int protocolPartIndex = imppAddress.indexOf(":");
+
+ imppAddress = (protocolPartIndex >= 0)
+ ? imppAddress.substring(protocolPartIndex + 1)
+ : imppAddress;
+
+ Collection<ProtocolProviderService> cusaxProviders
+ = AccountUtils.getRegisteredProviders(
+ OperationSetCusaxUtils.class);
+
+ if (cusaxProviders != null && cusaxProviders.size() > 0)
+ {
+ Contact contact = getPeerContact(
+ peer,
+ cusaxProviders.iterator().next(),
+ imppAddress);
+
+ return GuiActivator.getContactListService()
+ .findMetaContactByContact(contact);
+ }
+ else
+ {
+ return getPeerMetaContact(peer, imppAddress);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the image for the given <tt>alternativePeerAddress</tt> by
+ * checking the if the <tt>callPeer</tt> exists as a detail in one of the
+ * contacts in our contact list.
+ *
+ * @param callPeer the <tt>CallPeer</tt> to check in contact details
+ * @param alternativePeerAddress the alternative peer address to obtain the
+ * image from
+ * @return the <tt>MetaContact</tt> corresponding to the given
+ * <tt>alternativePeerAddress</tt>
+ */
+ private static MetaContact getPeerMetaContact(
+ CallPeer callPeer,
+ String alternativePeerAddress)
+ {
+ Iterator<MetaContact> metaContacts
+ = GuiActivator.getContactListService()
+ .findAllMetaContactsForAddress(alternativePeerAddress);
+
+ while (metaContacts.hasNext())
+ {
+ MetaContact metaContact = metaContacts.next();
+
+ UIPhoneUtil phoneUtil
+ = UIPhoneUtil.getPhoneUtil(metaContact);
+
+ List<UIContactDetail> additionalNumbers
+ = phoneUtil.getAdditionalNumbers();
+
+ if (additionalNumbers == null || additionalNumbers.size() > 0)
+ continue;
+
+ Iterator<UIContactDetail> numbersIter
+ = additionalNumbers.iterator();
+ while (numbersIter.hasNext())
+ {
+ if (numbersIter.next().getAddress()
+ .equals(callPeer.getAddress()))
+ return metaContact;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Opens a call transfer dialog to transfer the given <tt>peer</tt>.
+ * @param peer the <tt>CallPeer</tt> to transfer
+ */
+ public static void openCallTransferDialog(CallPeer peer)
+ {
+ final TransferCallDialog dialog
+ = new TransferCallDialog(peer);
+
+ final Call call = peer.getCall();
+
+ /*
+ * Transferring a call works only when the call is in progress
+ * so close the dialog (if it's not already closed, of course)
+ * once the dialog ends.
+ */
+ CallChangeListener callChangeListener = new CallChangeAdapter()
+ {
+ /*
+ * Overrides
+ * CallChangeAdapter#callStateChanged(CallChangeEvent).
+ */
+ @Override
+ public void callStateChanged(CallChangeEvent evt)
+ {
+ // we are interested only in CALL_STATE_CHANGEs
+ if(!evt.getEventType().equals(
+ CallChangeEvent.CALL_STATE_CHANGE))
+ return;
+
+ if (!CallState.CALL_IN_PROGRESS.equals(call
+ .getCallState()))
+ {
+ dialog.setVisible(false);
+ dialog.dispose();
+ }
+ }
+ };
+ call.addCallChangeListener(callChangeListener);
+ try
+ {
+ dialog.pack();
+ dialog.setVisible(true);
+ }
+ finally
+ {
+ call.removeCallChangeListener(callChangeListener);
+ }
+ }
+
+ /**
+ * Checks whether the <tt>callPeer</tt> supports setting video
+ * quality presets. If quality controls is null, its not supported.
+ * @param callPeer the peer, which video quality we're checking
+ * @return whether call peer supports setting quality preset.
+ */
+ public static boolean isVideoQualityPresetSupported(CallPeer callPeer)
+ {
+ ProtocolProviderService provider = callPeer.getProtocolProvider();
+ OperationSetVideoTelephony videoOpSet
+ = provider.getOperationSet(OperationSetVideoTelephony.class);
+
+ if (videoOpSet == null)
+ return false;
+
+ return videoOpSet.getQualityControl(callPeer) != null;
+ }
+
+ /**
+ * Sets the given quality preset for the video of the given call peer.
+ *
+ * @param callPeer the peer, which video quality we're setting
+ * @param qualityPreset the new quality settings
+ */
+ public static void setVideoQualityPreset(final CallPeer callPeer,
+ final QualityPreset qualityPreset)
+ {
+ ProtocolProviderService provider = callPeer.getProtocolProvider();
+ final OperationSetVideoTelephony videoOpSet
+ = provider.getOperationSet(OperationSetVideoTelephony.class);
+
+ if (videoOpSet == null)
+ return;
+
+ final QualityControl qualityControl =
+ videoOpSet.getQualityControl(callPeer);
+
+ if (qualityControl != null)
+ {
+ new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ qualityControl.setPreferredRemoteSendMaxPreset(
+ qualityPreset);
+ }
+ catch(org.jitsi.service.protocol.OperationFailedException e)
+ {
+ logger.info("Unable to change video quality.", e);
+
+ ResourceManagementService resources
+ = GuiActivator.getResources();
+
+ new ErrorDialog(
+ null,
+ resources.getI18NString("service.gui.WARNING"),
+ resources.getI18NString(
+ "service.gui.UNABLE_TO_CHANGE_VIDEO_QUALITY"),
+ e)
+ .showDialog();
+ }
+ }
+ }).start();
+ }
+ }
+
+ /**
+ * Indicates if we have video streams to show in this interface.
+ *
+ * @param call the call to check for video streaming
+ * @return <tt>true</tt> if we have video streams to show in this interface;
+ * otherwise, <tt>false</tt>
+ */
+ public static boolean isVideoStreaming(Call call)
+ {
+ return isVideoStreaming(call.getConference());
+ }
+
+ /**
+ * Indicates if we have video streams to show in this interface.
+ *
+ * @param conference the conference we check for video streaming
+ * @return <tt>true</tt> if we have video streams to show in this interface;
+ * otherwise, <tt>false</tt>
+ */
+ public static boolean isVideoStreaming(CallConference conference)
+ {
+ for (Call call : conference.getCalls())
+ {
+ OperationSetVideoTelephony videoTelephony
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetVideoTelephony.class);
+
+ if (videoTelephony == null)
+ continue;
+
+ if (videoTelephony.isLocalVideoStreaming(call))
+ return true;
+
+ Iterator<? extends CallPeer> callPeers = call.getCallPeers();
+
+ while (callPeers.hasNext())
+ {
+ List<Component> remoteVideos
+ = videoTelephony.getVisualComponents(callPeers.next());
+
+ if ((remoteVideos != null) && (remoteVideos.size() > 0))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether two specific addresses refer to one and the same
+ * peer/resource/contact.
+ * <p>
+ * <b>Warning</b>: Use the functionality sparingly because it assumes that
+ * an unspecified service is equal to any service.
+ * </p>
+ *
+ * @param a one of the addresses to be compared
+ * @param b the other address to be compared to <tt>a</tt>
+ * @return <tt>true</tt> if <tt>a</tt> and <tt>b</tt> name one and the same
+ * peer/resource/contact; <tt>false</tt>, otherwise
+ */
+ public static boolean addressesAreEqual(String a, String b)
+ {
+ if (a.equals(b))
+ return true;
+
+ int aProtocolIndex = a.indexOf(':');
+ if(aProtocolIndex != -1)
+ a = a.substring(aProtocolIndex + 1);
+
+ int bProtocolIndex = b.indexOf(':');
+ if(bProtocolIndex != -1)
+ b = b.substring(bProtocolIndex + 1);
+
+ if (a.equals(b))
+ return true;
+
+ int aServiceBegin = a.indexOf('@', aProtocolIndex);
+ String aUserID;
+ String aService;
+
+ if (aServiceBegin != -1)
+ {
+ aUserID = a.substring(0, aServiceBegin);
+ ++aServiceBegin;
+
+ int aResourceBegin = a.indexOf('/', aServiceBegin);
+ if (aResourceBegin != -1)
+ aService = a.substring(aServiceBegin, aResourceBegin);
+ else
+ aService = a.substring(aServiceBegin);
+ }
+ else
+ {
+ aUserID = a;
+ aService = null;
+ }
+
+ int bServiceBegin = b.indexOf('@', bProtocolIndex);
+ String bUserID;
+ String bService;
+
+ if (bServiceBegin != -1)
+ {
+ bUserID = b.substring(0, bServiceBegin);
+ ++bServiceBegin;
+
+ int bResourceBegin = b.indexOf('/', bServiceBegin);
+ if (bResourceBegin != -1)
+ bService = b.substring(bServiceBegin, bResourceBegin);
+ else
+ bService = b.substring(bServiceBegin);
+ }
+ else
+ {
+ bUserID = b;
+ bService = null;
+ }
+
+ boolean userIDsAreEqual;
+
+ if ((aUserID == null) || (aUserID.length() < 1))
+ userIDsAreEqual = ((bUserID == null) || (bUserID.length() < 1));
+ else
+ userIDsAreEqual = aUserID.equals(bUserID);
+ if (!userIDsAreEqual)
+ return false;
+
+ boolean servicesAreEqual;
+
+ /*
+ * It's probably a veeery long shot but it's assumed here that an
+ * unspecified service is equal to any service. Such a case is, for
+ * example, RegistrarLess SIP.
+ */
+ if (((aService == null) || (aService.length() < 1))
+ || ((bService == null) || (bService.length() < 1)))
+ servicesAreEqual = true;
+ else
+ servicesAreEqual = aService.equals(bService);
+
+ return servicesAreEqual;
+ }
+
+ /**
+ * Indicates if the given <tt>ConferenceMember</tt> corresponds to the local
+ * user.
+ *
+ * @param conferenceMember the conference member to check
+ * @return <tt>true</tt> if the given <tt>conferenceMember</tt> is the local
+ * user, <tt>false</tt> - otherwise
+ */
+ public static boolean isLocalUser(ConferenceMember conferenceMember)
+ {
+ String localUserAddress
+ = conferenceMember.getConferenceFocusCallPeer()
+ .getProtocolProvider().getAccountID().getAccountAddress();
+
+ return CallManager.addressesAreEqual(
+ conferenceMember.getAddress(), localUserAddress);
+ }
+
+ /**
+ * Adds a missed call notification.
+ *
+ * @param peerName the name of the peer
+ * @param callTime the time of the call
+ */
+ private static void addMissedCallNotification(String peerName, long callTime)
+ {
+ if (missedCallGroup == null)
+ {
+ missedCallGroup
+ = new UINotificationGroup(
+ "MissedCalls",
+ GuiActivator.getResources().getI18NString(
+ "service.gui.MISSED_CALLS_TOOL_TIP"));
+ }
+
+ UINotificationManager.addNotification(
+ new UINotification(peerName, callTime, missedCallGroup));
+ }
+
+ /**
+ * Returns of supported/enabled list of audio formats for a provider.
+ * @param device the <tt>MediaDevice</tt>, which audio formats we're
+ * looking for
+ * @param protocolProvider the provider to check.
+ * @return list of supported/enabled auido formats or empty list
+ * otherwise.
+ */
+ private static List<MediaFormat> getAudioFormats(
+ MediaDevice device,
+ ProtocolProviderService protocolProvider)
+ {
+ List<MediaFormat> res = new ArrayList<MediaFormat>();
+
+ Map<String, String> accountProperties
+ = protocolProvider.getAccountID().getAccountProperties();
+ String overrideEncodings
+ = accountProperties.get(ProtocolProviderFactory.OVERRIDE_ENCODINGS);
+
+ List<MediaFormat> formats;
+ if(Boolean.parseBoolean(overrideEncodings))
+ {
+ /*
+ * The account properties associated with account
+ * override the global EncodingConfiguration.
+ */
+ EncodingConfiguration encodingConfiguration
+ = ProtocolMediaActivator.getMediaService()
+ .createEmptyEncodingConfiguration();
+
+ encodingConfiguration.loadProperties(
+ accountProperties,
+ ProtocolProviderFactory.ENCODING_PROP_PREFIX);
+
+ formats = device.getSupportedFormats(
+ null, null, encodingConfiguration);
+ }
+ else /* The global EncodingConfiguration is in effect. */
+ {
+ formats = device.getSupportedFormats();
+ }
+
+ // skip the special telephony event
+ for(MediaFormat format : formats)
+ {
+ if(!format.getEncoding().equals(Constants.TELEPHONE_EVENT))
+ res.add(format);
+ }
+
+ return res;
+ }
+
+ /**
+ * Creates a new (audio-only or video) <tt>Call</tt> to a contact specified
+ * as a <tt>Contact</tt> instance or a <tt>String</tt> contact
+ * address/identifier.
+ */
+ private static class CreateCallThread
+ extends Thread
+ {
+ /**
+ * The contact to call.
+ */
+ private final Contact contact;
+
+ /**
+ * The specific contact resource to call.
+ */
+ private final ContactResource contactResource;
+
+ /**
+ * The <tt>UIContactImpl</tt> we're calling.
+ */
+ private final UIContactImpl uiContact;
+
+ /**
+ * The protocol provider through which the call goes.
+ */
+ private final ProtocolProviderService protocolProvider;
+
+ /**
+ * The string to call.
+ */
+ private final String stringContact;
+
+ /**
+ * The description of a conference to call, if any.
+ */
+ private final ConferenceDescription conferenceDescription;
+
+ /**
+ * The indicator which determines whether this instance is to create a
+ * new video (as opposed to audio-only) <tt>Call</tt>.
+ */
+ private final boolean video;
+
+ /**
+ * The chat room associated with the call.
+ */
+ private final ChatRoom chatRoom;
+
+ /**
+ * Creates an instance of <tt>CreateCallThread</tt>.
+ *
+ * @param protocolProvider the protocol provider through which the call
+ * is going.
+ * @param contact the contact to call
+ * @param contactResource the specific <tt>ContactResource</tt> we're
+ * calling
+ * @param video indicates if this is a video call
+ */
+ public CreateCallThread(
+ ProtocolProviderService protocolProvider,
+ Contact contact,
+ ContactResource contactResource,
+ boolean video)
+ {
+ this(protocolProvider, contact, contactResource, null, null, null,
+ null, video);
+ }
+
+ /**
+ * Creates an instance of <tt>CreateCallThread</tt>.
+ *
+ * @param protocolProvider the protocol provider through which the call
+ * is going.
+ * @param contact the contact to call
+ * @param video indicates if this is a video call
+ */
+ public CreateCallThread(
+ ProtocolProviderService protocolProvider,
+ String contact,
+ boolean video)
+ {
+ this(protocolProvider, null, null, null, contact, null, null, video);
+ }
+
+ /**
+ * Initializes a new <tt>CreateCallThread</tt> instance which is to
+ * create a new <tt>Call</tt> to a conference specified via a
+ * <tt>ConferenceDescription</tt>.
+ * @param protocolProvider the <tt>ProtocolProviderService</tt> which is
+ * to perform the establishment of the new <tt>Call</tt>.
+ * @param conferenceDescription the description of the conference to
+ * call.
+ * @param chatRoom the chat room associated with the call.
+ */
+ public CreateCallThread(
+ ProtocolProviderService protocolProvider,
+ ConferenceDescription conferenceDescription,
+ ChatRoom chatRoom)
+ {
+ this(protocolProvider, null, null, null, null,
+ conferenceDescription, chatRoom,
+ false /* video */);
+ }
+
+ /**
+ * Initializes a new <tt>CreateCallThread</tt> instance which is to
+ * create a new <tt>Call</tt> to a contact specified either as a
+ * <tt>Contact</tt> instance or as a <tt>String</tt> contact
+ * address/identifier.
+ * <p>
+ * The constructor is private because it relies on its arguments being
+ * validated prior to its invocation.
+ * </p>
+ *
+ * @param protocolProvider the <tt>ProtocolProviderService</tt> which is
+ * to perform the establishment of the new <tt>Call</tt>
+ * @param contact the contact to call
+ * @param contactResource the specific contact resource to call
+ * @param uiContact the ui contact we're calling
+ * @param stringContact the string to call
+ * @param video <tt>true</tt> if this instance is to create a new video
+ * (as opposed to audio-only) <tt>Call</tt>
+ * @param conferenceDescription the description of a conference to call
+ * @param chatRoom the chat room associated with the call.
+ */
+ public CreateCallThread(
+ ProtocolProviderService protocolProvider,
+ Contact contact,
+ ContactResource contactResource,
+ UIContactImpl uiContact,
+ String stringContact,
+ ConferenceDescription conferenceDescription,
+ ChatRoom chatRoom,
+ boolean video)
+ {
+ this.protocolProvider = protocolProvider;
+ this.contact = contact;
+ this.contactResource = contactResource;
+ this.uiContact = uiContact;
+ this.stringContact = stringContact;
+ this.video = video;
+ this.conferenceDescription = conferenceDescription;
+ this.chatRoom = chatRoom;
+ }
+
+ @Override
+ public void run()
+ {
+ if(!video)
+ {
+ // if it is not video let's check for available audio codecs
+ // and available audio devices
+ MediaService mediaService = GuiActivator.getMediaService();
+ MediaDevice dev = mediaService.getDefaultDevice(
+ MediaType.AUDIO, MediaUseCase.CALL);
+
+ List<MediaFormat> formats
+ = getAudioFormats(dev, protocolProvider);
+
+ String errorMsg = null;
+
+ if(!dev.getDirection().allowsSending())
+ errorMsg = GuiActivator.getResources().getI18NString(
+ "service.gui.CALL_NO_AUDIO_DEVICE");
+ else if(formats.isEmpty())
+ {
+ errorMsg = GuiActivator.getResources().getI18NString(
+ "service.gui.CALL_NO_AUDIO_CODEC");
+ }
+
+ if(errorMsg != null)
+ {
+ if(GuiActivator.getUIService()
+ .getPopupDialog().showConfirmPopupDialog(
+ errorMsg + " " +
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CALL_NO_DEVICE_CODECS_Q"),
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CALL"),
+ PopupDialog.YES_NO_OPTION,
+ PopupDialog.QUESTION_MESSAGE)
+ == PopupDialog.NO_OPTION)
+ {
+ return;
+ }
+ }
+ }
+
+ Contact contact = this.contact;
+ String stringContact = this.stringContact;
+
+ if (ConfigurationUtils.isNormalizePhoneNumber())
+ {
+ if (contact != null)
+ {
+ stringContact = contact.getAddress();
+ contact = null;
+ }
+
+ if (stringContact != null)
+ {
+ stringContact
+ = PhoneNumberI18nService.normalize(stringContact);
+ }
+ }
+
+ try
+ {
+ if (conferenceDescription != null)
+ {
+ internalCall( protocolProvider,
+ conferenceDescription,
+ chatRoom);
+ }
+ else
+ {
+ if (video)
+ {
+ internalCallVideo( protocolProvider,
+ contact,
+ uiContact,
+ stringContact);
+ }
+ else
+ {
+ internalCall( protocolProvider,
+ contact,
+ stringContact,
+ contactResource,
+ uiContact);
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof ThreadDeath)
+ throw (ThreadDeath) t;
+
+ logger.error("The call could not be created: ", t);
+
+ String message = GuiActivator.getResources()
+ .getI18NString("servoce.gui.CREATE_CALL_FAILED");
+
+ if (t.getMessage() != null)
+ message += " " + t.getMessage();
+
+ new ErrorDialog(
+ null,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.ERROR"),
+ message,
+ t)
+ .showDialog();
+ }
+ }
+ }
+
+ /**
+ * Creates a video call through the given <tt>protocolProvider</tt>.
+ *
+ * @param protocolProvider the <tt>ProtocolProviderService</tt> through
+ * which to make the call
+ * @param contact the <tt>Contact</tt> to call
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ * @param stringContact the contact string to call
+ *
+ * @throws OperationFailedException thrown if the call operation fails
+ * @throws ParseException thrown if the contact string is malformated
+ */
+ private static void internalCallVideo(
+ ProtocolProviderService protocolProvider,
+ Contact contact,
+ UIContactImpl uiContact,
+ String stringContact)
+ throws OperationFailedException,
+ ParseException
+ {
+ OperationSetVideoTelephony telephony
+ = protocolProvider.getOperationSet(
+ OperationSetVideoTelephony.class);
+
+ Call createdCall = null;
+ if (telephony != null)
+ {
+ if (contact != null)
+ {
+ createdCall = telephony.createVideoCall(contact);
+ }
+ else if (stringContact != null)
+ createdCall = telephony.createVideoCall(stringContact);
+ }
+
+ if (uiContact != null && createdCall != null)
+ addUIContactCall(uiContact, createdCall);
+ }
+
+ /**
+ * Creates a call through the given <tt>protocolProvider</tt>.
+ *
+ * @param protocolProvider the <tt>ProtocolProviderService</tt> through
+ * which to make the call
+ * @param contact the <tt>Contact</tt> to call
+ * @param stringContact the contact string to call
+ * @param contactResource the specific <tt>ContactResource</tt> to call
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ *
+ * @throws OperationFailedException thrown if the call operation fails
+ * @throws ParseException thrown if the contact string is malformated
+ */
+ private static void internalCall(
+ ProtocolProviderService protocolProvider,
+ Contact contact,
+ String stringContact,
+ ContactResource contactResource,
+ UIContactImpl uiContact)
+ throws OperationFailedException,
+ ParseException
+ {
+ OperationSetBasicTelephony<?> telephony
+ = protocolProvider.getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ OperationSetResourceAwareTelephony resourceTelephony
+ = protocolProvider.getOperationSet(
+ OperationSetResourceAwareTelephony.class);
+
+ Call createdCall = null;
+
+ if (resourceTelephony != null && contactResource != null)
+ {
+ if (contact != null)
+ createdCall
+ = resourceTelephony.createCall(contact, contactResource);
+ else if (!StringUtils.isNullOrEmpty(stringContact))
+ createdCall = resourceTelephony.createCall(
+ stringContact, contactResource.getResourceName());
+ }
+ else if (telephony != null)
+ {
+ if (contact != null)
+ {
+ createdCall = telephony.createCall(contact);
+ }
+ else if (!StringUtils.isNullOrEmpty(stringContact))
+ createdCall = telephony.createCall(stringContact);
+ }
+
+ if (uiContact != null && createdCall != null)
+ addUIContactCall(uiContact, createdCall);
+ }
+
+ /**
+ * Creates a call through the given <tt>protocolProvider</tt>.
+ *
+ * @param protocolProvider the <tt>ProtocolProviderService</tt> through
+ * which to make the call
+ * @param conferenceDescription the description of the conference to call
+ * @param chatRoom the chat room associated with the call.
+ */
+ private static void internalCall(ProtocolProviderService protocolProvider,
+ ConferenceDescription conferenceDescription,
+ ChatRoom chatRoom)
+ throws OperationFailedException
+ {
+ OperationSetBasicTelephony<?> telephony
+ = protocolProvider.getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ if (telephony != null)
+ {
+ telephony.createCall(conferenceDescription, chatRoom);
+ }
+ }
+
+ /**
+ * Returns the <tt>MetaContact</tt>, to which the given <tt>Call</tt>
+ * was initially created.
+ *
+ * @param call the <tt>Call</tt>, which corresponding <tt>MetaContact</tt>
+ * we're looking for
+ * @return the <tt>UIContactImpl</tt>, to which the given <tt>Call</tt>
+ * was initially created
+ */
+ public static UIContactImpl getCallUIContact(Call call)
+ {
+ if (uiContactCalls != null)
+ return uiContactCalls.get(call);
+ return null;
+ }
+
+ /**
+ * Adds a call for a <tt>metaContact</tt>.
+ *
+ * @param uiContact the <tt>UIContact</tt> corresponding to the call
+ * @param call the <tt>Call</tt> corresponding to the <tt>MetaContact</tt>
+ */
+ private static void addUIContactCall( UIContactImpl uiContact,
+ Call call)
+ {
+ if (uiContactCalls == null)
+ uiContactCalls = new WeakHashMap<Call, UIContactImpl>();
+
+ uiContactCalls.put(call, uiContact);
+ }
+
+ /**
+ * Creates a desktop sharing session with the given Contact or a given
+ * String.
+ */
+ private static class CreateDesktopSharingThread
+ extends Thread
+ {
+ /**
+ * The string contact to share the desktop with.
+ */
+ private final String stringContact;
+
+ /**
+ * The protocol provider through which we share our desktop.
+ */
+ private final ProtocolProviderService protocolProvider;
+
+ /**
+ * The media device corresponding to the screen we would like to share.
+ */
+ private final MediaDevice mediaDevice;
+
+ /**
+ * The <tt>UIContactImpl</tt> we're calling.
+ */
+ private final UIContactImpl uiContact;
+
+ /**
+ * Creates a desktop sharing session thread.
+ *
+ * @param protocolProvider protocol provider through which we share our
+ * desktop
+ * @param contact the contact to share the desktop with
+ * @param uiContact the <tt>UIContact</tt>, which initiated the desktop
+ * sharing session
+ * @param mediaDevice the media device corresponding to the screen we
+ * would like to share
+ */
+ public CreateDesktopSharingThread(
+ ProtocolProviderService protocolProvider,
+ String contact,
+ UIContactImpl uiContact,
+ MediaDevice mediaDevice)
+ {
+ this.protocolProvider = protocolProvider;
+ this.stringContact = contact;
+ this.uiContact = uiContact;
+ this.mediaDevice = mediaDevice;
+ }
+
+ @Override
+ public void run()
+ {
+ OperationSetDesktopSharingServer desktopSharingOpSet
+ = protocolProvider.getOperationSet(
+ OperationSetDesktopSharingServer.class);
+
+ /*
+ * XXX If we are here and we just discover that
+ * OperationSetDesktopSharingServer is not supported, then we're
+ * already in trouble - we've already started a whole new thread
+ * just to check that a reference is null.
+ */
+ if (desktopSharingOpSet == null)
+ return;
+
+ Throwable exception = null;
+
+ Call createdCall = null;
+ try
+ {
+ if (mediaDevice != null)
+ {
+ createdCall = desktopSharingOpSet.createVideoCall(
+ stringContact,
+ mediaDevice);
+ }
+ else
+ createdCall
+ = desktopSharingOpSet.createVideoCall(stringContact);
+ }
+ catch (OperationFailedException e)
+ {
+ exception = e;
+ }
+ catch (ParseException e)
+ {
+ exception = e;
+ }
+ if (exception != null)
+ {
+ logger.error("The call could not be created: ", exception);
+
+ new ErrorDialog(
+ null,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.ERROR"),
+ exception.getMessage(),
+ ErrorDialog.ERROR)
+ .showDialog();
+ }
+
+ if (uiContact != null && createdCall != null)
+ addUIContactCall(uiContact, createdCall);
+ }
+ }
+
+ /**
+ * Answers to all <tt>CallPeer</tt>s associated with a specific
+ * <tt>Call</tt> and, optionally, does that in a telephony conference with
+ * an existing <tt>Call</tt>.
+ */
+ private static class AnswerCallThread
+ extends Thread
+ {
+ /**
+ * The <tt>Call</tt> which is to be answered.
+ */
+ private final Call call;
+
+ /**
+ * The existing <tt>Call</tt>, if any, which represents a telephony
+ * conference in which {@link #call} is to be answered.
+ */
+ private final Call existingCall;
+
+ /**
+ * The indicator which determines whether this instance is to answer
+ * {@link #call} with video.
+ */
+ private final boolean video;
+
+ public AnswerCallThread(Call call, Call existingCall, boolean video)
+ {
+ this.call = call;
+ this.existingCall = existingCall;
+ this.video = video;
+ }
+
+ @Override
+ public void run()
+ {
+ if (existingCall != null)
+ call.setConference(existingCall.getConference());
+
+ ProtocolProviderService pps = call.getProtocolProvider();
+ Iterator<? extends CallPeer> peers = call.getCallPeers();
+
+ while (peers.hasNext())
+ {
+ CallPeer peer = peers.next();
+
+ if (video)
+ {
+ OperationSetVideoTelephony telephony
+ = pps.getOperationSet(OperationSetVideoTelephony.class);
+
+ try
+ {
+ telephony.answerVideoCallPeer(peer);
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error(
+ "Could not answer "
+ + peer
+ + " with video"
+ + " because of the following exception: "
+ + ofe);
+ }
+ }
+ else
+ {
+ OperationSetBasicTelephony<?> telephony
+ = pps.getOperationSet(OperationSetBasicTelephony.class);
+
+ try
+ {
+ telephony.answerCallPeer(peer);
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error(
+ "Could not answer "
+ + peer
+ + " because of the following exception: ",
+ ofe);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Invites a list of callees to a conference <tt>Call</tt>. If the specified
+ * <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony conference.
+ */
+ private static class InviteToConferenceCallThread
+ extends Thread
+ {
+ /**
+ * The addresses of the callees to be invited into the telephony
+ * conference to be organized by this instance. For further details,
+ * refer to the documentation on the <tt>callees</tt> parameter of the
+ * respective <tt>InviteToConferenceCallThread</tt> constructor.
+ */
+ private final Map<ProtocolProviderService, List<String>>
+ callees;
+
+ /**
+ * The <tt>Call</tt>, if any, into the telephony conference of which
+ * {@link #callees} are to be invited. If non-<tt>null</tt>, its
+ * <tt>CallConference</tt> state will be shared with all <tt>Call</tt>s
+ * established by this instance for the purposes of having the
+ * <tt>callees</tt> into the same telephony conference.
+ */
+ private final Call call;
+
+ /**
+ * Initializes a new <tt>InviteToConferenceCallThread</tt> instance
+ * which is to invite a list of callees to a conference <tt>Call</tt>.
+ * If the specified <tt>call</tt> is <tt>null</tt>, creates a brand new
+ * telephony conference.
+ *
+ * @param callees the addresses of the callees to be invited into a
+ * telephony conference. The addresses are provided in multiple
+ * <tt>List&lt;String&gt;</tt>s. Each such list of addresses is mapped
+ * by the <tt>ProtocolProviderService</tt> through which they are to be
+ * invited into the telephony conference. If there are multiple
+ * <tt>ProtocolProviderService</tt>s in the specified <tt>Map</tt>, the
+ * resulting telephony conference is known by the name
+ * &quot;cross-protocol&quot;. It is also allowed to have a list of
+ * addresses mapped to <tt>null</tt> which means that the new instance
+ * will automatically choose a <tt>ProtocolProviderService</tt> to
+ * invite the respective callees into the telephony conference.
+ * @param call the <tt>Call</tt> to invite the specified
+ * <tt>callees</tt> into. If <tt>null</tt>, this instance will create a
+ * brand new telephony conference. Technically, a <tt>Call</tt> instance
+ * is protocol/account-specific and it is possible to have
+ * cross-protocol/account telephony conferences. That's why the
+ * specified <tt>callees</tt> are invited into one and the same
+ * <tt>CallConference</tt>: the one in which the specified <tt>call</tt>
+ * is participating or a new one if <tt>call</tt> is <tt>null</tt>. Of
+ * course, an attempt is made to have all callees from one and the same
+ * protocol/account into one <tt>Call</tt> instance.
+ */
+ public InviteToConferenceCallThread(
+ Map<ProtocolProviderService, List<String>> callees,
+ Call call)
+ {
+ this.callees = callees;
+ this.call = call;
+ }
+
+ /**
+ * Invites {@link #callees} into a telephony conference which is
+ * optionally specified by {@link #call}.
+ */
+ @Override
+ public void run()
+ {
+ CallConference conference
+ = (call == null) ? null : call.getConference();
+
+ for(Map.Entry<ProtocolProviderService, List<String>> entry
+ : callees.entrySet())
+ {
+ ProtocolProviderService pps = entry.getKey();
+
+ /*
+ * We'd like to allow specifying callees without specifying an
+ * associated ProtocolProviderService.
+ */
+ if (pps != null)
+ {
+ OperationSetBasicTelephony<?> basicTelephony
+ = pps.getOperationSet(OperationSetBasicTelephony.class);
+
+ if(basicTelephony == null)
+ continue;
+ }
+
+ List<String> contactList = entry.getValue();
+ String[] contactArray
+ = contactList.toArray(new String[contactList.size()]);
+
+ if (ConfigurationUtils.isNormalizePhoneNumber())
+ normalizePhoneNumbers(contactArray);
+
+ /* Try to have a single Call per ProtocolProviderService. */
+ Call ppsCall;
+
+ if ((call != null) && call.getProtocolProvider().equals(pps))
+ ppsCall = call;
+ else
+ {
+ ppsCall = null;
+ if (conference != null)
+ {
+ List<Call> conferenceCalls = conference.getCalls();
+
+ if (pps == null)
+ {
+ /*
+ * We'd like to allow specifying callees without
+ * specifying an associated ProtocolProviderService.
+ * The simplest approach is to just choose the first
+ * ProtocolProviderService involved in the telephony
+ * conference.
+ */
+ if (call == null)
+ {
+ if (!conferenceCalls.isEmpty())
+ {
+ ppsCall = conferenceCalls.get(0);
+ pps = ppsCall.getProtocolProvider();
+ }
+ }
+ else
+ {
+ ppsCall = call;
+ pps = ppsCall.getProtocolProvider();
+ }
+ }
+ else
+ {
+ for (Call conferenceCall : conferenceCalls)
+ {
+ if (pps.equals(
+ conferenceCall.getProtocolProvider()))
+ {
+ ppsCall = conferenceCall;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ OperationSetTelephonyConferencing telephonyConferencing
+ = pps.getOperationSet(
+ OperationSetTelephonyConferencing.class);
+
+ try
+ {
+ if (ppsCall == null)
+ {
+ ppsCall
+ = telephonyConferencing.createConfCall(
+ contactArray,
+ conference);
+ if (conference == null)
+ conference = ppsCall.getConference();
+ }
+ else
+ {
+ for (String contact : contactArray)
+ {
+ telephonyConferencing.inviteCalleeToCall(
+ contact,
+ ppsCall);
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ logger.error(
+ "Failed to invite callees: "
+ + Arrays.toString(contactArray),
+ e);
+ new ErrorDialog(
+ null,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.ERROR"),
+ e.getMessage(),
+ ErrorDialog.ERROR)
+ .showDialog();
+ }
+ }
+ }
+ }
+
+ /**
+ * Invites a list of callees to a specific conference <tt>Call</tt>. If the
+ * specified <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony
+ * conference.
+ */
+ private static class InviteToConferenceBridgeThread
+ extends Thread
+ {
+ private final ProtocolProviderService callProvider;
+
+ private final String[] callees;
+
+ private final Call call;
+
+ public InviteToConferenceBridgeThread(
+ ProtocolProviderService callProvider,
+ String[] callees,
+ Call call)
+ {
+ this.callProvider = callProvider;
+ this.callees = callees;
+ this.call = call;
+ }
+
+ @Override
+ public void run()
+ {
+ OperationSetVideoBridge opSetVideoBridge
+ = callProvider.getOperationSet(
+ OperationSetVideoBridge.class);
+
+ // Normally if this method is called then this should not happen
+ // but we check in order to be sure to be able to proceed.
+ if (opSetVideoBridge == null || !opSetVideoBridge.isActive())
+ return;
+
+ if (ConfigurationUtils.isNormalizePhoneNumber())
+ normalizePhoneNumbers(callees);
+
+ try
+ {
+ if (call == null)
+ {
+ opSetVideoBridge.createConfCall(callees);
+ }
+ else
+ {
+ for (String contact : callees)
+ opSetVideoBridge.inviteCalleeToCall(contact, call);
+ }
+ }
+ catch(Exception e)
+ {
+ logger.error(
+ "Failed to invite callees: "
+ + Arrays.toString(callees),
+ e);
+ new ErrorDialog(
+ null,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.ERROR"),
+ e.getMessage(),
+ e)
+ .showDialog();
+ }
+ }
+ }
+
+ /**
+ * Hangs up a specific <tt>Call</tt> (i.e. all <tt>CallPeer</tt>s associated
+ * with a <tt>Call</tt>), <tt>CallConference</tt> (i.e. all <tt>Call</tt>s
+ * participating in a <tt>CallConference</tt>), or <tt>CallPeer</tt>.
+ */
+ private static class HangupCallThread
+ extends Thread
+ {
+ private final Call call;
+
+ private final CallConference conference;
+
+ private final CallPeer peer;
+
+ /**
+ * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
+ * up a specific <tt>Call</tt> i.e. all <tt>CallPeer</tt>s associated
+ * with the <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are
+ * to be hanged up
+ */
+ public HangupCallThread(Call call)
+ {
+ this(call, null, null);
+ }
+
+ /**
+ * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
+ * up a specific <tt>CallConference</tt> i.e. all <tt>Call</tt>s
+ * participating in the <tt>CallConference</tt>.
+ *
+ * @param conference the <tt>CallConference</tt> whose participating
+ * <tt>Call</tt>s re to be hanged up
+ */
+ public HangupCallThread(CallConference conference)
+ {
+ this(null, conference, null);
+ }
+
+ /**
+ * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
+ * up a specific <tt>CallPeer</tt>.
+ *
+ * @param peer the <tt>CallPeer</tt> to hang up
+ */
+ public HangupCallThread(CallPeer peer)
+ {
+ this(null, null, peer);
+ }
+
+ /**
+ * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
+ * up a specific <tt>Call</tt>, <tt>CallConference</tt>, or
+ * <tt>CallPeer</tt>.
+ *
+ * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are
+ * to be hanged up
+ * @param conference the <tt>CallConference</tt> whose participating
+ * <tt>Call</tt>s re to be hanged up
+ * @param peer the <tt>CallPeer</tt> to hang up
+ */
+ private HangupCallThread(
+ Call call,
+ CallConference conference,
+ CallPeer peer)
+ {
+ this.call = call;
+ this.conference = conference;
+ this.peer = peer;
+ }
+
+ @Override
+ public void run()
+ {
+ /*
+ * There is only an OperationSet which hangs up a CallPeer at a time
+ * so prepare a list of all CallPeers to be hanged up.
+ */
+ Set<CallPeer> peers = new HashSet<CallPeer>();
+
+ if (call != null)
+ {
+ Iterator<? extends CallPeer> peerIter = call.getCallPeers();
+
+ while (peerIter.hasNext())
+ peers.add(peerIter.next());
+ }
+
+ if (conference != null)
+ peers.addAll(conference.getCallPeers());
+
+ if (peer != null)
+ peers.add(peer);
+
+ for (CallPeer peer : peers)
+ {
+ OperationSetBasicTelephony<?> basicTelephony
+ = peer.getProtocolProvider().getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ try
+ {
+ basicTelephony.hangupCallPeer(peer);
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error("Could not hang up: " + peer, ofe);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the enable local video call thread.
+ */
+ private static class EnableLocalVideoThread
+ extends Thread
+ {
+ private final Call call;
+
+ private final boolean enable;
+
+ /**
+ * Creates the enable local video call thread.
+ *
+ * @param call the call, for which to enable/disable
+ * @param enable
+ */
+ public EnableLocalVideoThread(Call call, boolean enable)
+ {
+ this.call = call;
+ this.enable = enable;
+ }
+
+ @Override
+ public void run()
+ {
+ OperationSetVideoTelephony videoTelephony
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetVideoTelephony.class);
+ boolean enableSucceeded = false;
+
+ if (videoTelephony != null)
+ {
+ // First make sure the desktop sharing is disabled.
+ if (enable && isDesktopSharingEnabled(call))
+ {
+ JFrame frame = DesktopSharingFrame.getFrameForCall(call);
+
+ if (frame != null)
+ frame.dispose();
+ }
+
+ try
+ {
+ videoTelephony.setLocalVideoAllowed(call, enable);
+ enableSucceeded = true;
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error(
+ "Failed to toggle the streaming of local video.",
+ ex);
+
+ ResourceManagementService r = GuiActivator.getResources();
+ String title
+ = r.getI18NString(
+ "service.gui.LOCAL_VIDEO_ERROR_TITLE");
+ String message
+ = r.getI18NString(
+ "service.gui.LOCAL_VIDEO_ERROR_MESSAGE");
+
+ GuiActivator.getAlertUIService().showAlertPopup(
+ title,
+ message,
+ ex);
+ }
+ }
+
+ // If the operation didn't succeeded for some reason, make sure the
+ // video button doesn't remain selected.
+ if (enable && !enableSucceeded)
+ getActiveCallContainer(call).setVideoButtonSelected(false);
+ }
+ }
+
+ /**
+ * Puts on hold the given <tt>CallPeer</tt>.
+ */
+ private static class PutOnHoldCallPeerThread
+ extends Thread
+ {
+ private final CallPeer callPeer;
+
+ private final boolean isOnHold;
+
+ public PutOnHoldCallPeerThread(CallPeer callPeer, boolean isOnHold)
+ {
+ this.callPeer = callPeer;
+ this.isOnHold = isOnHold;
+ }
+
+ @Override
+ public void run()
+ {
+ OperationSetBasicTelephony<?> telephony
+ = callPeer.getProtocolProvider().getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ try
+ {
+ if (isOnHold)
+ telephony.putOnHold(callPeer);
+ else
+ telephony.putOffHold(callPeer);
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error(
+ "Failed to put"
+ + callPeer.getAddress()
+ + (isOnHold ? " on hold." : " off hold."),
+ ex);
+ }
+ }
+ }
+
+ /**
+ * Merges specific existing <tt>Call</tt>s into a specific telephony
+ * conference.
+ */
+ private static class MergeExistingCalls
+ extends Thread
+ {
+ /**
+ * The telephony conference in which {@link #calls} are to be merged.
+ */
+ private final CallConference conference;
+
+ /**
+ * Second call.
+ */
+ private final Collection<Call> calls;
+
+ /**
+ * Initializes a new <tt>MergeExistingCalls</tt> instance which is to
+ * merge specific existing <tt>Call</tt>s into a specific telephony
+ * conference.
+ *
+ * @param conference the telephony conference in which the specified
+ * <tt>Call</tt>s are to be merged
+ * @param calls the <tt>Call</tt>s to be merged into the specified
+ * telephony conference
+ */
+ public MergeExistingCalls(
+ CallConference conference,
+ Collection<Call> calls)
+ {
+ this.conference = conference;
+ this.calls = calls;
+ }
+
+ /**
+ * Puts off hold the <tt>CallPeer</tt>s of a specific <tt>Call</tt>
+ * which are locally on hold.
+ *
+ * @param call the <tt>Call</tt> which is to have its <tt>CallPeer</tt>s
+ * put off hold
+ */
+ private void putOffHold(Call call)
+ {
+ Iterator<? extends CallPeer> peers = call.getCallPeers();
+ OperationSetBasicTelephony<?> telephony
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ while (peers.hasNext())
+ {
+ CallPeer callPeer = peers.next();
+ boolean putOffHold = true;
+
+ if(callPeer instanceof MediaAwareCallPeer)
+ {
+ putOffHold
+ = ((MediaAwareCallPeer<?,?,?>) callPeer)
+ .getMediaHandler()
+ .isLocallyOnHold();
+ }
+ if(putOffHold)
+ {
+ try
+ {
+ telephony.putOffHold(callPeer);
+ Thread.sleep(400);
+ }
+ catch(Exception ofe)
+ {
+ logger.error("Failed to put off hold.", ofe);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ // conference
+ for (Call call : conference.getCalls())
+ putOffHold(call);
+
+ // calls
+ if (!calls.isEmpty())
+ {
+ for(Call call : calls)
+ {
+ if (conference.containsCall(call))
+ continue;
+
+ putOffHold(call);
+
+ /*
+ * Dispose of the CallPanel associated with the Call which
+ * is to be merged.
+ */
+ closeCallContainerIfNotNecessary(conference, false);
+
+ call.setConference(conference);
+ }
+ }
+ }
+ }
+
+ /**
+ * Shows a warning window to warn the user that she's about to start a
+ * desktop sharing session.
+ *
+ * @return <tt>true</tt> if the user has accepted the desktop sharing
+ * session; <tt>false</tt>, otherwise
+ */
+ private static boolean showDesktopSharingWarning()
+ {
+ Boolean isWarningEnabled
+ = GuiActivator.getConfigurationService().getBoolean(
+ desktopSharingWarningProperty,
+ true);
+
+ if (isWarningEnabled.booleanValue())
+ {
+ ResourceManagementService resources = GuiActivator.getResources();
+ MessageDialog warningDialog
+ = new MessageDialog(
+ null,
+ resources.getI18NString("service.gui.WARNING"),
+ resources.getI18NString(
+ "service.gui.DESKTOP_SHARING_WARNING"),
+ true);
+
+ switch (warningDialog.showDialog())
+ {
+ case MessageDialog.OK_RETURN_CODE:
+ return true;
+ case MessageDialog.CANCEL_RETURN_CODE:
+ return false;
+ case MessageDialog.OK_DONT_ASK_CODE:
+ GuiActivator.getConfigurationService().setProperty(
+ desktopSharingWarningProperty,
+ false);
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Normalizes the phone numbers (if any) in a list of <tt>String</tt>
+ * contact addresses or phone numbers.
+ *
+ * @param callees the list of contact addresses or phone numbers to be
+ * normalized
+ */
+ private static void normalizePhoneNumbers(String callees[])
+ {
+ for (int i = 0 ; i < callees.length ; i++)
+ callees[i] = PhoneNumberI18nService.normalize(callees[i]);
+ }
+
+ /**
+ * Throws a <tt>RuntimeException</tt> if the current thread is not the AWT
+ * event dispatching thread.
+ */
+ public static void assertIsEventDispatchingThread()
+ {
+ if (!SwingUtilities.isEventDispatchThread())
+ {
+ throw new RuntimeException(
+ "The methon can be called only on the AWT event dispatching"
+ + " thread.");
+ }
+ }
+
+ /**
+ * Finds the <tt>CallPanel</tt>, if any, which depicts a specific
+ * <tt>CallConference</tt>.
+ * <p>
+ * <b>Note</b>: The method can be called only on the AWT event dispatching
+ * thread.
+ * </p>
+ *
+ * @param conference the <tt>CallConference</tt> to find the depicting
+ * <tt>CallPanel</tt> of
+ * @return the <tt>CallPanel</tt> which depicts the specified
+ * <tt>CallConference</tt> if such a <tt>CallPanel</tt> exists; otherwise,
+ * <tt>null</tt>
+ * @throws RuntimeException if the method is not called on the AWT event
+ * dispatching thread
+ */
+ private static CallPanel findCallPanel(CallConference conference)
+ {
+ synchronized (callPanels)
+ {
+ return callPanels.get(conference);
+ }
+ }
+
+ /**
+ * Notifies {@link #callPanels} about a specific <tt>CallEvent</tt> received
+ * by <tt>CallManager</tt> (because they may need to update their UI, for
+ * example).
+ * <p>
+ * <b>Note</b>: The method can be called only on the AWT event dispatching
+ * thread.
+ * </p>
+ *
+ * @param ev the <tt>CallEvent</tt> received by <tt>CallManager</tt> which
+ * is to be forwarded to <tt>callPanels</tt> for further
+ * <tt>CallPanel</tt>-specific handling
+ * @throws RuntimeException if the method is not called on the AWT event
+ * dispatching thread
+ */
+ private static void forwardCallEventToCallPanels(CallEvent ev)
+ {
+ assertIsEventDispatchingThread();
+
+ CallPanel[] callPanels;
+
+ synchronized (CallManager.callPanels)
+ {
+ Collection<CallPanel> values = CallManager.callPanels.values();
+
+ callPanels = values.toArray(new CallPanel[values.size()]);
+ }
+
+ for (CallPanel callPanel : callPanels)
+ {
+ try
+ {
+ callPanel.onCallEvent(ev);
+ }
+ catch (Exception ex)
+ {
+ /*
+ * There is no practical reason while the failure of a CallPanel
+ * to handle the CallEvent should cause the other CallPanels to
+ * be left out-of-date.
+ */
+ logger.error("A CallPanel failed to handle a CallEvent", ex);
+ }
+ }
+ }
+
+ /**
+ * Creates a call for the supplied operation set.
+ * @param opSetClass the operation set to use to create call.
+ * @param protocolProviderService the protocol provider
+ * @param contact the contact address to call
+ */
+ static void createCall(Class<? extends OperationSet> opSetClass,
+ ProtocolProviderService protocolProviderService,
+ String contact)
+ {
+ createCall(opSetClass, protocolProviderService, contact, null);
+ }
+
+ /**
+ * Creates a call for the supplied operation set.
+ * @param opSetClass the operation set to use to create call.
+ * @param protocolProviderService the protocol provider
+ * @param contact the contact address to call
+ * @param uiContact the <tt>MetaContact</tt> we're calling
+ */
+ static void createCall(
+ Class<? extends OperationSet> opSetClass,
+ ProtocolProviderService protocolProviderService,
+ String contact,
+ UIContactImpl uiContact)
+ {
+ if (opSetClass.equals(OperationSetBasicTelephony.class))
+ {
+ createCall(protocolProviderService, contact, uiContact);
+ }
+ else if (opSetClass.equals(OperationSetVideoTelephony.class))
+ {
+ createVideoCall(protocolProviderService, contact, uiContact);
+ }
+ else if (opSetClass.equals(
+ OperationSetDesktopSharingServer.class))
+ {
+ createDesktopSharing(
+ protocolProviderService, contact, uiContact);
+ }
+ }
+
+ /**
+ * Creates a call for the default contact of the metacontact
+ *
+ * @param metaContact the metacontact that will be called.
+ * @param isVideo if <tt>true</tt> will create video call.
+ * @param isDesktop if <tt>true</tt> will share the desktop.
+ * @param shareRegion if <tt>true</tt> will share a region of the desktop.
+ */
+ public static void call(MetaContact metaContact,
+ boolean isVideo,
+ boolean isDesktop,
+ boolean shareRegion)
+ {
+ Contact contact = metaContact
+ .getDefaultContact(getOperationSetForCall(isVideo, isDesktop));
+
+ call(contact, isVideo, isDesktop, shareRegion);
+ }
+
+ /**
+ * A particular contact has been selected no options to select
+ * will just call it.
+ * @param contact the contact to call
+ * @param contactResource the specific contact resource
+ * @param isVideo is video enabled
+ * @param isDesktop is desktop sharing enabled
+ * @param shareRegion is sharing the whole desktop or just a region.
+ */
+ public static void call(Contact contact,
+ ContactResource contactResource,
+ boolean isVideo,
+ boolean isDesktop,
+ boolean shareRegion)
+ {
+ if(isDesktop)
+ {
+ if(shareRegion)
+ {
+ createRegionDesktopSharing(
+ contact.getProtocolProvider(),
+ contact.getAddress(),
+ null);
+ }
+ else
+ createDesktopSharing(contact.getProtocolProvider(),
+ contact.getAddress(),
+ null);
+ }
+ else
+ {
+ new CreateCallThread(
+ contact.getProtocolProvider(),
+ contact,
+ contactResource,
+ isVideo).start();
+ }
+ }
+
+ /**
+ * Creates a call to the conference described in
+ * <tt>conferenceDescription</tt> through <tt>protocolProvider</tt>
+ *
+ * @param protocolProvider the protocol provider through which to create
+ * the call
+ * @param conferenceDescription the description of the conference to call
+ * @param chatRoom the chat room associated with the call.
+ */
+ public static void call(ProtocolProviderService protocolProvider,
+ ConferenceDescription conferenceDescription,
+ ChatRoom chatRoom)
+ {
+ new CreateCallThread(protocolProvider, conferenceDescription, chatRoom)
+ .start();
+ }
+
+ /**
+ * A particular contact has been selected no options to select
+ * will just call it.
+ * @param contact the contact to call
+ * @param isVideo is video enabled
+ * @param isDesktop is desktop sharing enabled
+ * @param shareRegion is sharing the whole desktop or just a region.
+ */
+ public static void call(Contact contact,
+ boolean isVideo,
+ boolean isDesktop,
+ boolean shareRegion)
+ {
+ if(isDesktop)
+ {
+ if(shareRegion)
+ {
+ createRegionDesktopSharing(
+ contact.getProtocolProvider(),
+ contact.getAddress(),
+ null);
+ }
+ else
+ createDesktopSharing(contact.getProtocolProvider(),
+ contact.getAddress(),
+ null);
+ }
+ else
+ {
+ new CreateCallThread( contact.getProtocolProvider(),
+ contact,
+ null,
+ isVideo).start();
+ }
+ }
+
+ /**
+ * Calls a phone showing a dialog to choose a provider.
+ * @param phone phone to call
+ * @param isVideo if <tt>true</tt> will create video call.
+ * @param isDesktop if <tt>true</tt> will share the desktop.
+ * @param shareRegion if <tt>true</tt> will share a region of the desktop.
+ */
+ public static void call(final String phone,
+ boolean isVideo,
+ boolean isDesktop,
+ final boolean shareRegion)
+ {
+ call(phone, null, isVideo, isDesktop, shareRegion);
+ }
+
+ /**
+ * Calls a phone showing a dialog to choose a provider.
+ * @param phone phone to call
+ * @param uiContact the <tt>UIContactImpl</tt> we're calling
+ * @param isVideo if <tt>true</tt> will create video call.
+ * @param isDesktop if <tt>true</tt> will share the desktop.
+ * @param shareRegion if <tt>true</tt> will share a region of the desktop.
+ */
+ public static void call(final String phone,
+ final UIContactImpl uiContact,
+ boolean isVideo,
+ boolean isDesktop,
+ final boolean shareRegion)
+ {
+ List<ProtocolProviderService> providers =
+ CallManager.getTelephonyProviders();
+
+ if(providers.size() > 1)
+ {
+ ChooseCallAccountDialog chooseAccount =
+ new ChooseCallAccountDialog(
+ phone,
+ getOperationSetForCall(isVideo, isDesktop),
+ providers)
+ {
+ @Override
+ public void callButtonPressed()
+ {
+ if(shareRegion)
+ {
+ createRegionDesktopSharing(
+ getSelectedProvider(), phone, uiContact);
+ }
+ else
+ super.callButtonPressed();
+ }
+ };
+ chooseAccount.setUIContact(uiContact);
+ chooseAccount.setVisible(true);
+ }
+ else
+ {
+ createCall(providers.get(0), phone, uiContact);
+ }
+ }
+
+ /**
+ * Obtain operation set checking the params.
+ * @param isVideo if <tt>true</tt> use OperationSetVideoTelephony.
+ * @param isDesktop if <tt>true</tt> use OperationSetDesktopSharingServer.
+ * @return the operation set, default is OperationSetBasicTelephony.
+ */
+ private static Class<? extends OperationSet> getOperationSetForCall(
+ boolean isVideo, boolean isDesktop)
+ {
+ if(isVideo)
+ {
+ if(isDesktop)
+ return OperationSetDesktopSharingServer.class;
+ else
+ return OperationSetVideoTelephony.class;
+ }
+ else
+ return OperationSetBasicTelephony.class;
+ }
+
+ /**
+ * Call any of the supplied details.
+ *
+ * @param uiContactDetailList the list with details to choose for calling
+ * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled,
+ * available.
+ * @param isVideo if <tt>true</tt> will create video call.
+ * @param isDesktop if <tt>true</tt> will share the desktop.
+ * @param invoker the invoker component
+ * @param location the location where this was invoked.
+ */
+ public static void call(List<UIContactDetail> uiContactDetailList,
+ UIContactImpl uiContact,
+ boolean isVideo,
+ boolean isDesktop,
+ JComponent invoker,
+ Point location)
+ {
+ call(uiContactDetailList,
+ uiContact,
+ isVideo,
+ isDesktop,
+ invoker,
+ location,
+ true);
+ }
+
+ /**
+ * Call any of the supplied details.
+ *
+ * @param uiContactDetailList the list with details to choose for calling
+ * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled,
+ * available.
+ * @param isVideo if <tt>true</tt> will create video call.
+ * @param isDesktop if <tt>true</tt> will share the desktop.
+ * @param invoker the invoker component
+ * @param location the location where this was invoked.
+ * @param usePreferredProvider whether to use the <tt>uiContact</tt>
+ * preferredProvider if provided.
+ */
+ private static void call(List<UIContactDetail> uiContactDetailList,
+ UIContactImpl uiContact,
+ boolean isVideo,
+ boolean isDesktop,
+ JComponent invoker,
+ Point location,
+ boolean usePreferredProvider)
+ {
+ ChooseCallAccountPopupMenu chooseCallAccountPopupMenu = null;
+
+ Class<? extends OperationSet> opsetClass =
+ getOperationSetForCall(isVideo, isDesktop);
+
+ UIPhoneUtil contactPhoneUtil = null;
+ if (uiContact != null
+ && uiContact.getDescriptor() instanceof MetaContact)
+ contactPhoneUtil = UIPhoneUtil
+ .getPhoneUtil((MetaContact) uiContact.getDescriptor());
+
+ if(contactPhoneUtil != null)
+ {
+ boolean addAdditionalNumbers = false;
+ if(!isVideo
+ || ConfigurationUtils
+ .isRouteVideoAndDesktopUsingPhoneNumberEnabled())
+ {
+ addAdditionalNumbers = true;
+ }
+ else
+ {
+ if(isVideo && contactPhoneUtil != null)
+ {
+ // lets check is video enabled in additional numbers
+ addAdditionalNumbers =
+ contactPhoneUtil.isVideoCallEnabled() ?
+ isDesktop ?
+ contactPhoneUtil.isDesktopSharingEnabled()
+ : true
+ : false;
+ }
+ }
+
+ if(addAdditionalNumbers)
+ {
+ uiContactDetailList.addAll(
+ contactPhoneUtil.getAdditionalNumbers());
+ }
+ }
+
+ if (uiContactDetailList.size() == 1)
+ {
+ UIContactDetail detail = uiContactDetailList.get(0);
+
+ ProtocolProviderService preferredProvider = null;
+
+ if(usePreferredProvider)
+ preferredProvider =
+ detail.getPreferredProtocolProvider(opsetClass);
+
+ List<ProtocolProviderService> providers = null;
+ String protocolName = null;
+
+ if (preferredProvider != null)
+ {
+ if (preferredProvider.isRegistered())
+ {
+ createCall(opsetClass,
+ preferredProvider,
+ detail.getAddress(),
+ uiContact);
+ }
+
+ // If we have a provider, but it's not registered we try to
+ // obtain all registered providers for the same protocol as the
+ // given preferred provider.
+ else
+ {
+ protocolName = preferredProvider.getProtocolName();
+ providers = AccountUtils.getRegisteredProviders(protocolName,
+ opsetClass);
+ }
+ }
+ // If we don't have a preferred provider we try to obtain a
+ // preferred protocol name and all registered providers for it.
+ else
+ {
+ protocolName = detail.getPreferredProtocol(opsetClass);
+
+ if (protocolName != null)
+ providers
+ = AccountUtils.getRegisteredProviders(protocolName,
+ opsetClass);
+ else
+ providers
+ = AccountUtils.getRegisteredProviders(opsetClass);
+ }
+
+ // If our call didn't succeed, try to call through one of the other
+ // protocol providers obtained above.
+ if (providers != null)
+ {
+ int providersCount = providers.size();
+
+ if (providersCount <= 0)
+ {
+ new ErrorDialog(null,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CALL_FAILED"),
+ GuiActivator.getResources().getI18NString(
+ "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
+ .showDialog();
+ }
+ else if (providersCount == 1)
+ {
+ createCall(
+ opsetClass,
+ providers.get(0),
+ detail.getAddress(),
+ uiContact);
+ }
+ else if (providersCount > 1)
+ {
+ chooseCallAccountPopupMenu =
+ new ChooseCallAccountPopupMenu(
+ invoker, detail.getAddress(), providers,
+ opsetClass);
+ }
+ }
+ }
+ else if (uiContactDetailList.size() > 1)
+ {
+ chooseCallAccountPopupMenu
+ = new ChooseCallAccountPopupMenu(invoker, uiContactDetailList,
+ opsetClass);
+ }
+
+ // If the choose dialog is created we're going to show it.
+ if (chooseCallAccountPopupMenu != null)
+ {
+ if (uiContact != null)
+ chooseCallAccountPopupMenu.setUIContact(uiContact);
+
+ chooseCallAccountPopupMenu.showPopupMenu(location.x, location.y);
+ }
+ }
+
+
+ /**
+ * Call the ui contact.
+ *
+ * @param uiContact the contact to call.
+ * @param isVideo if <tt>true</tt> will create video call.
+ * @param isDesktop if <tt>true</tt> will share the desktop.
+ * @param invoker the invoker component
+ * @param location the location where this was invoked.
+ */
+ public static void call(UIContact uiContact,
+ boolean isVideo,
+ boolean isDesktop,
+ JComponent invoker,
+ Point location)
+ {
+ UIContactImpl uiContactImpl = null;
+ if(uiContact instanceof UIContactImpl)
+ {
+ uiContactImpl = (UIContactImpl) uiContact;
+ }
+
+ List<UIContactDetail> telephonyContacts
+ = uiContact.getContactDetailsForOperationSet(
+ getOperationSetForCall(isVideo, isDesktop));
+
+ boolean ignorePreferredProvider =
+ GuiActivator.getConfigurationService().getBoolean(
+ IGNORE_PREFERRED_PROVIDER_PROP, false);
+
+ call( telephonyContacts,
+ uiContactImpl,
+ isVideo,
+ isDesktop,
+ invoker,
+ location,
+ !ignorePreferredProvider);
+ }
+
+ /**
+ * Tries to resolves a peer address into a display name, by reqesting the
+ * <tt>ContactSourceService</tt>s. This function returns only the
+ * first match.
+ *
+ * @param peerAddress The peer address.
+ *
+ * @return The corresponding display name, if there is a match. Null
+ * otherwise.
+ */
+ private static String resolveContactSource(String peerAddress)
+ {
+ String displayName = null;
+
+ if(!StringUtils.isNullOrEmpty(peerAddress))
+ {
+ Vector<ResolveAddressToDisplayNameContactQueryListener> resolvers
+ = new Vector<ResolveAddressToDisplayNameContactQueryListener>
+ (1, 1);
+
+ // will strip the @server-address part, as the regular expression
+ // will match it
+ int index = peerAddress.indexOf("@");
+ String peerUserID =
+ (index > -1) ? peerAddress.substring(0, index) : peerAddress;
+
+ // searches for the whole number/username or with the @serverpart
+ peerUserID = Pattern.quote(peerUserID);
+ Pattern pattern = Pattern.compile(
+ "^(" + peerUserID + "|" + peerUserID + "@.*)$");
+
+ // Queries all available resolvers
+ for(ContactSourceService contactSourceService:
+ GuiActivator.getContactSources())
+ {
+ if(!(contactSourceService
+ instanceof ExtendedContactSourceService))
+ continue;
+
+ // use the pattern method of (ExtendedContactSourceService)
+ ContactQuery query
+ = ((ExtendedContactSourceService)contactSourceService)
+ .queryContactSource(pattern);
+
+ resolvers.add(
+ new ResolveAddressToDisplayNameContactQueryListener(
+ query));
+ }
+
+ long startTime = System.currentTimeMillis();
+ long currentTime = startTime;
+ // The detault timeout is set to 500ms.
+ long timeout = 500;
+ // Loops until we found a valid display name, or waits for timeout.
+ while(displayName == null
+ && currentTime - startTime < timeout)
+ {
+ for(int i = 0; i < resolvers.size() && displayName == null; ++i)
+ {
+ ResolveAddressToDisplayNameContactQueryListener resolver
+ = resolvers.get(i);
+ if(!resolver.isRunning())
+ {
+ if(resolver.isFound())
+ {
+ displayName = resolver.getResolvedName();
+ // If this is the same result as the peer address,
+ // then that is not what we are looking for. Then,
+ // continue the search.
+ if(displayName.equals(peerAddress))
+ {
+ displayName = null;
+ }
+ }
+ }
+ }
+ Thread.yield();
+ currentTime = System.currentTimeMillis();
+ }
+
+ // Free lasting resolvers.
+ for(int i = 0; i < resolvers.size(); ++i)
+ {
+ ResolveAddressToDisplayNameContactQueryListener resolver
+ = resolvers.get(i);
+ if(resolver.isRunning())
+ {
+ resolver.stop();
+ }
+ }
+ }
+
+ return displayName;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java
index 8515dfc..ec743cd 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java
@@ -1,2520 +1,2520 @@
-/*
- * 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.gui.main.call;
-
-import java.awt.*;
-import java.awt.Container;
-import java.awt.event.*;
-import java.beans.*;
-import java.util.*;
-import java.util.List;
-
-import javax.swing.*;
-import javax.swing.Timer;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.event.*;
-import net.java.sip.communicator.impl.gui.main.call.conference.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.impl.gui.utils.Constants;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.contactlist.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.gui.call.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.util.Logger;
-import net.java.sip.communicator.util.skin.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.util.*;
-import org.jitsi.util.event.*;
-import org.osgi.framework.*;
-
-/**
- * The dialog created for a given call.
- *
- * Ordered buttons we are adding/removing, numbers are the index we have set.
- * And the order that will be kept.
- * 0 dialButton
- * 1 conferenceButton
- * 2 holdButton
- * 3 recordButton
- * 4 mergeButton
- * 5 transferCallButton
- * 6 localLevel
- * 7 remoteLevel
- * 8 desktopSharingButton
- * 9 resizeVideoButton
- * 10 fullScreenButton
- * 11 videoButton
- * 12 showHideVideoButton
- * 19 chatButton
- * 20 infoButton
- *
- * @author Yana Stamcheva
- * @author Adam Netocny
- * @author Lyubomir Marinov
- * @author Hristo Terezov
- */
-public class CallPanel
- extends TransparentPanel
- implements ActionListener,
- PluginComponentListener,
- Skinnable,
- ConferencePeerViewListener,
- ContactPresenceStatusListener
-{
- /**
- * The chat button name.
- */
- private static final String CHAT_BUTTON = "CHAT_BUTTON";
-
- /**
- * The conference button name.
- */
- private static final String CONFERENCE_BUTTON = "CONFERENCE_BUTTON";
-
- /**
- * The dial button name.
- */
- private static final String DIAL_BUTTON = "DIAL_BUTTON";
-
- /**
- * The info button name.
- */
- private static final String INFO_BUTTON = "INFO_BUTTON";
-
- /**
- * The logger for this class.
- */
- private static final Logger logger = Logger.getLogger(CallDialog.class);
-
- /**
- * The hang up button name.
- */
- private static final String MERGE_BUTTON = "MERGE_BUTTON";
-
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * Property to disable the info button.
- */
- private static final String HIDE_CALL_INFO_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_INFO_BUTTON";
-
- /**
- * Property to disable the conference "add to call" button.
- */
- private static final String HIDE_CONFERENCE_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_CONFERENCE_BUTTON";
-
- /**
- * Property to disable the record button.
- */
- private static final String HIDE_CALL_RECORD_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_RECORD_BUTTON";
-
- /**
- * Property to disable the "call merge" button.
- */
- private static final String HIDE_CALL_MERGE_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_MERGE_BUTTON";
-
- /**
- * Property to disable the "call merge" button.
- */
- private static final String HIDE_CALL_TRANSFER_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_TRANSFER_BUTTON";
-
- /**
- * Property to disable the "hold" button.
- */
- private static final String HIDE_CALL_HOLD_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_HOLD_BUTTON";
-
- /**
- * Property to disable the dial button.
- */
- private static final String HIDE_DIAL_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_DIAL_BUTTON";
-
- /**
- * Property to disable the video button.
- */
- private static final String HIDE_VIDEO_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_VIDEO_BUTTON";
-
- /**
- * Property to disable the desktop sharing button.
- */
- private static final String HIDE_DESKTOP_SHARING_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_DESKTOP_SHARING_BUTTON";
-
- /**
- * Property to disable the full screen button.
- */
- private static final String HIDE_FULL_SCREEN_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_FULL_SCREEN_BUTTON";
-
- /**
- * Property to disable the "show/hide local video" button.
- */
- private static final String HIDE_TOGGLE_VIDEO_BUTON_PROP
- = "net.java.sip.communicator.impl.gui.main.call.HIDE_TOGGLE_VIDEO_BUTTON";
-
- /**
- * The <tt>Component</tt> which is at the bottom of this view and contains
- * {@link #settingsPanel}. It overrides the Swing-defined background on OS
- * X so it needs explicit updating upon switching between full-screen and
- * windowed mode in order to respect any background-related settings of the
- * ancestors such as black background in full-screen mode.
- */
- private JComponent bottomBar;
-
- /**
- * The {@link CallConference} instance depicted by this <tt>CallPanel</tt>.
- */
- private final CallConference callConference;
-
- /**
- * The listener which listens to events fired by the <tt>CallConference</tt>
- * depicted by this instance, the <tt>Call</tt>s participating in that
- * telephony conference, the <tt>CallPeer</tt>s associated with those
- * <tt>Call</tt>s and the <tt>ConferenceMember</tt>s participating in the
- * telephony conferences organized by those <tt>CallPeer</tt>s. Updates this
- * view i.e. CallPanel so that it depicts the current state of its model
- * i.e. {@link #callConference}.
- */
- private final CallConferenceListener callConferenceListener
- = new CallConferenceListener();
-
- /**
- * The time in milliseconds at which the telephony call/conference depicted
- * by this <tt>CallPanel</tt> (i.e. {@link #callConference}) has started.
- */
- private long callConferenceStartTime;
-
- /**
- * A timer to count call duration.
- */
- private Timer callDurationTimer;
-
- /**
- * The Frame used to display this call information statistics.
- */
- private CallInfoFrame callInfoFrame;
-
- /**
- * The panel representing the call. For conference calls this would be an
- * instance of <tt>ConferenceCallPanel</tt> and for one-to-one calls this
- * would be an instance of <tt>OneToOneCallPanel</tt>.
- */
- private JComponent callPanel;
-
- /**
- * Parent window.
- */
- private final CallContainer callWindow;
-
- /**
- * Chat button.
- */
- private SIPCommButton chatButton;
-
- /**
- * The operation set that will be used to update chatButton icon and
- * the corresponding contact.
- */
- private OperationSetPresence operationSetPresence;
-
- /**
- * The conference button.
- */
- private CallToolBarButton conferenceButton;
-
- /**
- * The desktop sharing button.
- */
- private DesktopSharingButton desktopSharingButton;
-
- /**
- * The dial button, which opens a keypad dialog.
- */
- private CallToolBarButton dialButton;
-
- /**
- * The dial pad dialog opened when the dial button is clicked.
- */
- private DialpadDialog dialpadDialog;
-
- /**
- * The indicator which determines whether {@link #dispose()} has already
- * been invoked on this instance. If <tt>true</tt>, this instance is
- * considered non-functional and is to be left to the garbage collector.
- */
- private boolean disposed = false;
-
- /**
- * The handler for DTMF tones.
- */
- private DTMFHandler dtmfHandler;
-
- /**
- * The full screen button.
- */
- private FullScreenButton fullScreenButton;
-
- /**
- * HangUp button.
- */
- private SIPCommButton hangupButton;
-
- /**
- * The hold button.
- */
- private HoldButton holdButton;
-
- /**
- * Info button.
- */
- private SIPCommButton infoButton;
-
- /**
- * Indicates if the call timer has been started.
- */
- private boolean isCallTimerStarted = false;
-
- /**
- * Sound local level label.
- */
- private InputVolumeControlButton localLevel;
-
- /**
- * Merge button.
- */
- private CallToolBarButton mergeButton;
-
- /**
- * The button which allows starting and stopping the recording of
- * {@link #callConference}.
- */
- private RecordButton recordButton;
-
- /**
- * Sound remote level label.
- */
- private Component remoteLevel;
-
- /**
- * The video resize button.
- */
- private ResizeVideoButton resizeVideoButton;
-
- /**
- * The panel containing call settings.
- */
- private CallToolBar settingsPanel;
-
- /**
- * The button responsible for hiding/showing the local video.
- */
- private ShowHideVideoButton showHideVideoButton;
-
- /**
- * The title of this call container.
- */
- private String title;
-
- /**
- * A collection of listeners, registered for call title change events.
- */
- private Collection<CallTitleListener> titleListeners
- = new Vector<CallTitleListener>();
-
- /**
- * The transfer call button.
- */
- private TransferCallButton transferCallButton;
-
- /**
- * The facility which aids this instance in the dealing with the
- * video-related information.
- */
- private final UIVideoHandler2 uiVideoHandler;
-
- /**
- * Indicates if this call panel should be closed immediately after hang up
- * or should wait some time so that the user can be notified of the last
- * state. By default we wait, so that the user can be notified of the
- * current state of the call.
- */
- private boolean isCloseWaitAfterHangup = true;
-
- /**
- * The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about
- * changes in the video-related information.
- */
- private final Observer uiVideoHandlerObserver
- = new Observer()
- {
- public void update(Observable o, Object arg)
- {
- uiVideoHandlerUpdate(arg);
- }
- };
-
- /**
- * The <tt>Runnable</tt> which is scheduled by
- * {@link #updateViewFromModel()} for execution in the AWT event dispatching
- * thread in order to invoke
- * {@link #updateViewFromModelInEventDispatchThread()}.
- */
- private final Runnable updateViewFromModelInEventDispatchThread
- = new Runnable()
- {
- public void run()
- {
- /*
- * We receive events/notifications from various threads and we
- * respond to them in the AWT event dispatching thread. It is
- * possible to first schedule an event to be brought to the AWT
- * event dispatching thread, then to have #dispose() invoked on
- * this instance and, finally, to receive the scheduled event in
- * the AWT event dispatching thread. In such a case, this
- * disposed instance should not respond to the event.
- */
- if (!disposed)
- updateViewFromModelInEventDispatchThread();
- }
- };
-
- /**
- * The video button.
- */
- private LocalVideoButton videoButton;
-
- /**
- * Initializes a new <tt>CallPanel</tt> which is to depict a specific
- * <tt>CallConference</tt>.
- *
- * @param callConference the <tt>CallConference</tt> to be depicted by the
- * new instance
- * @param callWindow the parent window in which the new instance will be
- * added
- */
- public CallPanel( CallConference callConference,
- CallContainer callWindow)
- {
- super(new BorderLayout());
-
- this.callConference = callConference;
- this.callWindow = callWindow;
-
- uiVideoHandler = new UIVideoHandler2(this.callConference);
-
- callDurationTimer
- = new Timer(
- 1000,
- new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- setCallTitle(callConferenceStartTime);
- }
- });
- callDurationTimer.setRepeats(true);
-
- // The call duration parameter is not known yet.
- setCallTitle(0);
-
- initializeUserInterfaceHierarchy();
-
- dtmfHandler = new DTMFHandler(this);
-
- /*
- * Adds the listeners which will observe the model and will trigger the
- * updates of this view from it.
- */
- this.callConference.addCallChangeListener(callConferenceListener);
- this.callConference.addCallPeerConferenceListener(
- callConferenceListener);
- this.callConference.addPropertyChangeListener(callConferenceListener);
- uiVideoHandler.addObserver(uiVideoHandlerObserver);
-
- callWindow.getFrame().addPropertyChangeListener(
- CallContainer.PROP_FULL_SCREEN,
- callConferenceListener);
-
- updateViewFromModel();
-
- initPluginComponents();
- }
-
- /**
- * Handles action events.
- * @param evt the <tt>ActionEvent</tt> that was triggered
- */
- public void actionPerformed(ActionEvent evt)
- {
- JButton button = (JButton) evt.getSource();
- String buttonName = button.getName();
-
- if (buttonName.equals(MERGE_BUTTON))
- {
- CallManager.mergeExistingCalls(
- callConference,
- CallManager.getInProgressCalls());
- }
- else if (buttonName.equals(DIAL_BUTTON))
- {
- if (dialpadDialog == null)
- dialpadDialog = this.getDialpadDialog();
-
- if(!dialpadDialog.isVisible())
- {
- dialpadDialog.pack();
-
- Point location = new Point( button.getX(),
- button.getY() + button.getHeight());
- SwingUtilities.convertPointToScreen(
- location, button.getParent());
-
- dialpadDialog.setLocation(
- (int) location.getX() + 2,
- (int) location.getY() + 2);
-
- dialpadDialog.addWindowFocusListener(dialpadDialog);
- dialpadDialog.setVisible(true);
- }
- else
- {
- dialpadDialog.removeWindowFocusListener(dialpadDialog);
- dialpadDialog.setVisible(false);
- }
- }
- else if (buttonName.equals(CONFERENCE_BUTTON))
- {
- ConferenceInviteDialog inviteDialog;
-
- if (callConference.isJitsiVideoBridge())
- {
- inviteDialog
- = new ConferenceInviteDialog(
- callConference,
- callConference.getCalls().get(0)
- .getProtocolProvider(),
- true);
- }
- else
- inviteDialog = new ConferenceInviteDialog(callConference);
-
- inviteDialog.setVisible(true);
- }
- else if (buttonName.equals(CHAT_BUTTON))
- {
- /*
- * If there is exactly 1 CallPeer capable of instant messaging, then
- * we'll try to start a chat with her.
- */
- /*
- * TODO The following is very likely to block the user interface in
- * a noticeable way sooner or later.
- */
- List<Contact> imCapableCallPeers = getIMCapableCallPeers(1);
-
- if (imCapableCallPeers.size() == 1)
- {
- Contact contact = imCapableCallPeers.get(0);
- MetaContact metaContact
- = GuiActivator.getContactListService()
- .findMetaContactByContact(contact);
- GuiActivator.getUIService().getChatWindowManager().startChat(
- metaContact);
- }
- }
- else if (buttonName.equals(INFO_BUTTON))
- {
- if (callInfoFrame == null)
- {
- callInfoFrame = new CallInfoFrame(callConference);
- addCallTitleListener(callInfoFrame);
- }
- callInfoFrame.setVisible(
- callInfoFrame.hasCallInfo() && !callInfoFrame.isVisible());
- }
- }
-
- /**
- * Executes the action associated with the "Hang up" button which may be
- * invoked by clicking the button in question or by closing this dialog.
- *
- * @param closeWait <tt>true</tt> to close this instance with a few seconds
- * of delay or <tt>false</tt> to close it immediately
- */
- public void actionPerformedOnHangupButton(boolean closeWait)
- {
- isCloseWaitAfterHangup = closeWait;
-
- this.disposeCallInfoFrame();
-
- /*
- * It is the responsibility of CallManager to close this CallPanel
- * when a Call is ended.
- */
- if (callConference.getCallCount() > 0)
- CallManager.hangupCalls(callConference);
- /*
- * If however there are no more calls related to this panel we will
- * close the window directly. This could happen in the case, where
- * the other side has already hanged up the call, the call window shows
- * the state disconnected and we press the hang up button. In this
- * case the contained call is already null and we should only close the
- * call window.
- */
- else
- callWindow.close(this, false);
- }
-
- /**
- * Indicates if this call panel should be closed immediately after hang up
- * or should wait some time so that the user can be notified of the last
- * state.
- *
- * @return <tt>true</tt> to indicate that when hanged up this call panel
- * should not be closed immediately, <tt>false</tt> - otherwise
- */
- public boolean isCloseWaitAfterHangup()
- {
- return isCloseWaitAfterHangup;
- }
-
- /**
- * Adds the given <tt>CallTitleListener</tt> to the list of listeners,
- * notified for call title changes.
- *
- * @param l the <tt>CallTitleListener</tt> to add
- */
- public void addCallTitleListener(CallTitleListener l)
- {
- synchronized (titleListeners)
- {
- titleListeners.add(l);
- }
- }
-
- /**
- * Adds remote video specific components.
- *
- * @param callPeer the <tt>CallPeer</tt>
- */
- public void addRemoteVideoSpecificComponents(CallPeer callPeer)
- {
- if(CallManager.isVideoQualityPresetSupported(callPeer))
- {
- if(resizeVideoButton == null)
- {
- resizeVideoButton = new ResizeVideoButton(callPeer.getCall());
- resizeVideoButton.setIndex(9);
- }
-
- if(resizeVideoButton.countAvailableOptions() > 1)
- {
- settingsPanel.add(resizeVideoButton);
- settingsPanel.revalidate();
- settingsPanel.repaint();
- }
- }
- }
-
- /**
- * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
- * {@link #callWindow}.
- *
- * @param ev the <tt>PropertyChangeEvent</tt> fired by <tt>callWindow</tt>
- * to notify this instance about
- */
- private void callWindowPropertyChange(PropertyChangeEvent ev)
- {
- /*
- * We are registered for CallContainer#PROP_FULL_SCREEN only. This
- * instance will fire the notification as its own to allow listeners to
- * register with a source which is more similar to them with respect to
- * life span.
- */
- try
- {
- if (OSUtils.IS_MAC && (bottomBar != null))
- bottomBar.setOpaque(!isFullScreen());
- }
- finally
- {
- firePropertyChange(
- ev.getPropertyName(),
- ev.getOldValue(), ev.getNewValue());
- }
- }
-
- /**
- * Count the number of the buttons in the supplied components.
- * @param cs the components to search for buttons.
- * @return number of buttons.
- */
- private int countButtons(Component[] cs)
- {
- int count = 0;
-
- for(Component c : cs)
- {
- if(c instanceof SIPCommButton || c instanceof SIPCommToggleButton)
- count++;
- if(c instanceof Container)
- count += countButtons(((Container)c).getComponents());
- }
-
- return count;
- }
-
- /**
- * Creates the bottom bar panel for this <tt>CallPanel</tt>.
- *
- * @return a new bottom bar panel for this <tt>CallPanel</tt>
- */
- private JComponent createBottomBar()
- {
- bottomBar
- = new TransparentPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
- bottomBar.setBorder(BorderFactory.createEmptyBorder(0, 30, 2, 30));
-
- /*
- * The bottomBar on OS X overrides the Swing-defined background.
- * However, full-screen display usually uses a black background. The
- * black background will be set elsewhere on an ancestor but we have to
- * make sure that bottomBar's background does not interfere with the
- * setting.
- */
- if (OSUtils.IS_MAC)
- {
- bottomBar.setOpaque(!isFullScreen());
- bottomBar.setBackground(
- new Color(GuiActivator.getResources().getColor(
- "service.gui.MAC_PANEL_BACKGROUND")));
- }
-
- bottomBar.add(settingsPanel);
-
- return bottomBar;
- }
-
- /**
- * Releases the resources acquired by this instance which require explicit
- * disposal (e.g. any listeners added to the depicted
- * <tt>CallConference</tt>, the participating <tt>Call</tt>s, and their
- * associated <tt>CallPeer</tt>s). Invoked by <tt>CallManager</tt> when it
- * determines that this <tt>CallPanel</tt> is no longer necessary.
- */
- void dispose()
- {
- disposed = true;
-
- callConference.removeCallChangeListener(callConferenceListener);
- callConference.removeCallPeerConferenceListener(callConferenceListener);
- callConference.removePropertyChangeListener(callConferenceListener);
-
- uiVideoHandler.deleteObserver(uiVideoHandlerObserver);
- uiVideoHandler.dispose();
-
- callWindow.getFrame().removePropertyChangeListener(
- CallContainer.PROP_FULL_SCREEN,
- callConferenceListener);
-
- if (callPanel != null)
- {
- if(callPanel instanceof BasicConferenceCallPanel)
- {
- ((BasicConferenceCallPanel) callPanel)
- .removePeerViewListener(this);
- }
- ((CallRenderer) callPanel).dispose();
- }
-
- // clears the contact status listener
- if(operationSetPresence != null)
- {
- operationSetPresence.removeContactPresenceStatusListener(this);
- }
- }
-
- /**
- * Disposes the call info frame if it exists.
- */
- public void disposeCallInfoFrame()
- {
- if (callInfoFrame != null)
- callInfoFrame.dispose();
- }
-
- /**
- * Updates {@link #settingsPanel} from the model of this view. The update is
- * performed in the AWT event dispatching thread.
- * <p>
- * The center of this view is occupied by {@link #callPanel}, the bottom of
- * this view is dedicated to <tt>settingsPanel</tt>. The method
- * {@link #updateViewFromModelInEventDispatchThread()} updates
- * <tt>callPanel</tt> from the model of this view and then invokes the
- * method <tt>updateSettingsPanelInEventDispatchThread()</tt>. Thus this
- * whole view is updated so that it depicts the current state of its model.
- * </p>
- *
- * @param callConferenceIsEnded <tt>true</tt> if the method
- * <tt>updateViewFromModelInEventDispatchThread()</tt> considers the
- * {@link #callConference} ended; otherwise, <tt>false</tt>. When the
- * <tt>callConference</tt> is considered ended, the <tt>callPanel</tt>
- * instance will not be switched to a specific type (one-to-one, audio-only,
- * or audio/video) because, otherwise, the switch will leave it
- * <tt>null</tt> and this view will remain blank. In such a case,
- * <tt>settingsPanel</tt> may wish to do pretty much the same but disable
- * and/or hide the buttons it contains.
- */
- private void doUpdateSettingsPanelInEventDispatchThread(
- boolean callConferenceIsEnded)
- {
- settingsPanel.setFullScreen(isFullScreen());
-
- boolean isConference = (callPanel instanceof BasicConferenceCallPanel);
-
- /*
- * For whatever reason, we're treating the localLevel and the
- * remoteLevel buttons differently and we're adding and removing them in
- * accord with the conference state of the user interface.
- */
- if (isConference)
- {
- settingsPanel.add(localLevel);
- settingsPanel.add(remoteLevel);
- }
- else
- {
- settingsPanel.remove(localLevel);
- settingsPanel.remove(remoteLevel);
- }
-
- /*
- * We do not support chat conferencing with the participants in a
- * telephony conference at this time so we do not want the chatButton
- * visible in such a scenario.
- */
- List<Contact> imContacts = getIMCapableCallPeers(1);
- chatButton.setVisible(
- !isConference && (imContacts.size() == 1));
- if(chatButton.isVisible() && operationSetPresence == null)
- {
- Contact contact = imContacts.get(0);
- operationSetPresence =
- contact.getProtocolProvider()
- .getOperationSet(OperationSetPresence.class);
- if(operationSetPresence != null)
- operationSetPresence.addContactPresenceStatusListener(this);
-
- chatButton.setIconImage(
- Constants.getMessageStatusIcon(contact.getPresenceStatus()));
- chatButton.repaint();
- }
-
- updateHoldButtonState();
- updateMergeButtonState();
-
- List<Call> calls = callConference.getCalls();
- /*
- * OperationSetAdvancedTelephony implements call transfer. The feature
- * is not supported if the local user/peer is a conference focus.
- * Instead of disabling the transferCallButton in this case though, we
- * want it hidden.
- */
- boolean advancedTelephony = !calls.isEmpty();
- boolean telephonyConferencing = false;
- boolean videoTelephony = false;
- boolean videoTelephonyIsLocalVideoAllowed = false;
- boolean videoTelephonyIsLocalVideoStreaming = false;
- boolean desktopSharing = false;
- boolean desktopSharingIsStreamed = false;
- boolean allCallsConnected = true;
-
- for (Call call : calls)
- {
- ProtocolProviderService pps = call.getProtocolProvider();
-
- /*
- * The transferCallButton requires OperationSetAdvancedTelephony
- * for all Calls.
- */
- if (advancedTelephony)
- {
- OperationSetAdvancedTelephony<?> osat
- = pps.getOperationSet(OperationSetAdvancedTelephony.class);
-
- if (osat == null)
- advancedTelephony = false;
- }
-
- /*
- * The conferenceButton needs at least one Call with
- * OperationSetTelephonyConferencing,
- */
- if (!telephonyConferencing)
- {
- OperationSetTelephonyConferencing ostc
- = pps.getOperationSet(
- OperationSetTelephonyConferencing.class);
-
- if (ostc != null)
- telephonyConferencing = true;
- }
-
- if (!videoTelephony
- || !videoTelephonyIsLocalVideoAllowed
- || !videoTelephonyIsLocalVideoStreaming)
- {
- OperationSetVideoTelephony osvt
- = pps.getOperationSet(OperationSetVideoTelephony.class);
-
- if (osvt != null)
- {
- if (!videoTelephony)
- videoTelephony = true;
- if (!videoTelephonyIsLocalVideoAllowed
- && osvt.isLocalVideoAllowed(call))
- videoTelephonyIsLocalVideoAllowed = true;
- if (!videoTelephonyIsLocalVideoStreaming
- && osvt.isLocalVideoStreaming(call))
- videoTelephonyIsLocalVideoStreaming = true;
- }
- }
-
- if(!desktopSharing)
- {
- OperationSetDesktopStreaming osds
- = pps.getOperationSet(
- OperationSetDesktopStreaming.class);
- if(osds != null)
- {
- desktopSharing = true;
-
- if(videoTelephonyIsLocalVideoStreaming
- && call instanceof MediaAwareCall
- && ((MediaAwareCall<?,?,?>) call).getMediaUseCase()
- == MediaUseCase.DESKTOP)
- {
- desktopSharingIsStreamed = true;
- }
- }
- }
-
- if (CallState.CALL_IN_PROGRESS != call.getCallState())
- {
- allCallsConnected = false;
- }
- }
-
- if(conferenceButton != null)
- conferenceButton.setEnabled(telephonyConferencing);
-
- if(transferCallButton != null)
- {
- transferCallButton.setEnabled(advancedTelephony);
- transferCallButton.setVisible(!callConference.isConferenceFocus());
- }
-
- /*
- * The videoButton is a beast of its own kind because it depends not
- * only on the state of the depicted telephony conference but also on
- * the global application state.
- */
- if(videoButton != null)
- {
- videoButton.setEnabled(allCallsConnected && videoTelephony);
- videoButton.setSelected(videoTelephonyIsLocalVideoAllowed);
-
- /*
- * Consequently, the showHideVideoButton which depends on videoButton
- * has to be updated depending on the state of the videoButton as well.
- */
- if(showHideVideoButton != null)
- {
- showHideVideoButton.setEnabled(
- videoButton.isEnabled()
- && videoTelephonyIsLocalVideoAllowed);
- showHideVideoButton.setSelected(
- showHideVideoButton.isEnabled()
- && uiVideoHandler.isLocalVideoVisible());
- showHideVideoButton.setVisible(showHideVideoButton.isEnabled());
- }
- }
-
- // The desktop sharing button depends on the operation set desktop
- // sharing server.
- if(desktopSharingButton != null)
- {
- desktopSharingButton.setEnabled(desktopSharing);
- desktopSharingButton.setSelected(desktopSharingIsStreamed);
- }
-
- if (callPanel instanceof OneToOneCallPanel)
- {
- OneToOneCallPanel oneToOneCallPanel = (OneToOneCallPanel) callPanel;
- if(desktopSharingIsStreamed)
- oneToOneCallPanel.addDesktopSharingComponents();
- else
- oneToOneCallPanel.removeDesktopSharingComponents();
- }
- }
-
- /**
- * Updates this view i.e. <tt>CallPanel</tt> so that it depicts the current
- * state of its model i.e. <tt>callConference</tt>. The update is performed
- * in the AWT event dispatching thread.
- */
- private void doUpdateViewFromModelInEventDispatchThread()
- {
- /*
- * If the telephony conference depicted by this instance has ended, do
- * not update the user interface because it will be left blank. It is
- * CallManager's responsibility to dispose of this CallPanel after its
- * telephony conference has ended. Additionally, the various types of
- * callPanel will usually require at least one CallPeer in order to not
- * be blank. The absence of CallPeers usually indicates that a Call and,
- * respectively, a telephony conference has ended. So it makes some
- * sense to skip the update in such cases in order to try to not have
- * the user interface blank.
- */
- if (callConference.isEnded()
- || (callConference.getCallPeerCount() == 0))
- {
- /*
- * However, the settingsPanel contains buttons which may still need
- * to be disabled and/or hidden.
- */
- updateSettingsPanelInEventDispatchThread(true);
- return;
- }
-
- boolean isConference = isConference();
- boolean isVideo = CallManager.isVideoStreaming(callConference);
- CallPeer callPeer = null;
- boolean validateAndRepaint = false;
-
- if (callPanel != null)
- {
- boolean removeCallPanel;
-
- if (isConference)
- {
- if (callPanel instanceof BasicConferenceCallPanel)
- {
- if (isVideo)
- {
- removeCallPanel
- = !(callPanel instanceof VideoConferenceCallPanel);
- }
- else
- {
- removeCallPanel
- = (callPanel instanceof VideoConferenceCallPanel);
- }
- }
- else
- {
- removeCallPanel = true;
- }
- }
- else
- {
- if (callPanel instanceof OneToOneCallPanel)
- {
- if (callPeer == null)
- {
- List<CallPeer> callPeers
- = callConference.getCallPeers();
-
- if (!callPeers.isEmpty())
- callPeer = callPeers.get(0);
- }
- removeCallPanel
- = !((OneToOneCallPanel) callPanel).getCallPeer().equals(
- callPeer);
- }
- else
- {
- if( (callPanel instanceof BasicConferenceCallPanel) &&
- ((BasicConferenceCallPanel) callPanel)
- .hasDelayedCallPeers())
- {
- removeCallPanel = false;
- }
- else
- {
- removeCallPanel = true;
- }
-
- }
- }
- if (removeCallPanel)
- {
- remove(callPanel);
- validateAndRepaint = true;
- try
- {
- ((CallRenderer) callPanel).dispose();
- }
- finally
- {
- callPanel = null;
- }
- }
- }
- if (callPanel == null)
- {
- if (isConference)
- {
- if (isVideo)
- {
- callPanel
- = new VideoConferenceCallPanel(
- this,
- callConference,
- uiVideoHandler);
- }
- else
- {
- callPanel
- = new AudioConferenceCallPanel(this, callConference);
- }
-
- ((BasicConferenceCallPanel) callPanel)
- .addPeerViewlListener(this);
- }
- else
- {
- if (callPeer == null)
- {
- List<CallPeer> callPeers = callConference.getCallPeers();
-
- if (!callPeers.isEmpty())
- callPeer = callPeers.get(0);
- }
- if (callPeer != null)
- {
- callPanel
- = new OneToOneCallPanel(this, callPeer, uiVideoHandler);
- }
- }
- if (callPanel != null)
- {
- add(callPanel, BorderLayout.CENTER);
- validateAndRepaint = true;
- }
- }
-
- try
- {
- /*
- * The center of this view is occupied by callPanel and we have just
- * updated it. The bottom of this view is dedicated to settingsPanel
- * so we have to update it as well.
- */
- updateSettingsPanelInEventDispatchThread(false);
- }
- finally
- {
- /*
- * It seems that AWT/Swing does not validate and/or repaint this
- * Container (enough) and, consequently, its display may not update
- * itself with an up-to-date drawing of the current callPanel.
- */
- if (validateAndRepaint)
- {
- if (isDisplayable())
- {
- validate();
- repaint();
- }
- else
- doLayout();
- }
- }
- }
-
- /**
- * Attempts to give a specific <tt>Component</tt> a visible rectangle with a
- * specific width and a specific height if possible and sane by resizing
- * the <tt>Window</tt> which contains this instance.
- *
- * @param component the <tt>Component</tt> which requests a visible
- * rectangle with the specified <tt>width</tt> and <tt>height</tt>
- * @param width the width of the visible rectangle requested by the
- * specified <tt>component</tt>
- * @param height the height of the visible rectangle requested by the
- * specified <tt>component</tt>
- */
- private void ensureSize(Component component, int width, int height)
- {
- CallContainer callContainer = getCallWindow();
-
- if (callContainer != null)
- callContainer.ensureSize(component, width, height);
- }
-
- /**
- * Notifies interested listeners of a call title change.
- */
- private void fireTitleChangeEvent()
- {
- Iterator<CallTitleListener> listeners;
-
- synchronized (titleListeners)
- {
- listeners = new Vector<CallTitleListener>(titleListeners).iterator();
- }
-
- while (listeners.hasNext())
- {
- listeners.next().callTitleChanged(this);
- }
- }
-
- /**
- * Returns the <tt>CallConference</tt> depicted by this <tt>CallPanel</tt>
- *
- * @return the <tt>CallConference</tt> depicted by this
- * <tt>CallConference</tt>
- */
- public CallConference getCallConference()
- {
- return callConference;
- }
-
- /**
- * Returns the initial call title. The call title could be then changed by
- * call setCallTitle.
- *
- * @return the call title
- */
- public String getCallTitle()
- {
- return title;
- }
-
- /**
- * Returns the parent call window.
- *
- * @return the parent call window
- */
- public CallContainer getCallWindow()
- {
- return callWindow;
- }
-
- /**
- * Returns the currently used <tt>CallRenderer</tt>.
- * @return the currently used <tt>CallRenderer</tt>
- */
- public CallRenderer getCurrentCallRenderer()
- {
- return (CallRenderer) callPanel;
- }
-
- /**
- * Returns the <tt>DialpadDialog</tt> corresponding to this CallDialog.
- *
- * @return the <tt>DialpadDialog</tt> corresponding to this CallDialog.
- */
- private DialpadDialog getDialpadDialog()
- {
- return new DialpadDialog(dtmfHandler);
- }
-
- /**
- * Finds the <tt>Contact</tt>s which are participating in the telephony
- * conference depicted by this instance and which are capable of instant
- * messaging i.e. support {@link OperationSetBasicTelephony}.
- *
- * @param limit the maximum number of <tt>Contact</tt>s to be found. Since
- * it is expensive in terms of execution time (at very least) to find a
- * <tt>Contact</tt> which stands for a <tt>CallPeer</tt> (and to query it
- * whether it supports instant messaging), it is advised to limit the search
- * as much as possible. For example, the <tt>chatButton</tt> is enabled
- * and/or shown only when there is exactly one such <tt>Contact</tt> so it
- * makes perfect sense to specify <tt>1</tt> as the <tt>limit</tt> in the
- * case.
- * @return a <tt>List</tt> of the <tt>Contact</tt>s which are participating
- * in the telephony conference depicted by this instance and which are
- * capable of instant messaging i.e. support
- * <tt>OperationSetBasicTelephony</tt>
- */
- private List<Contact> getIMCapableCallPeers(int limit)
- {
- List<CallPeer> callPeers = callConference.getCallPeers();
- List<Contact> contacts = new ArrayList<Contact>(callPeers.size());
-
- /*
- * Choose the CallPeers (or rather their associated Contacts) which are
- * capable of basic instant messaging.
- */
- for (CallPeer callPeer : callPeers)
- {
- if (callPeer.getProtocolProvider().getOperationSet(
- OperationSetBasicInstantMessaging.class)
- != null)
- {
- /*
- * CallPeer#getContact) is more expensive in terms of execution
- * than ProtocolProviderService#getOperationSet(Class).
- */
- Contact contact = callPeer.getContact();
-
- if (contact != null)
- contacts.add(contact);
- }
- else
- {
- Contact contact = CallManager.getIMCapableCusaxContact(callPeer);
- if (contact != null)
- {
- contacts.add(contact);
- }
- }
-
- if (contacts.size() >= limit)
- break;
- }
- return contacts;
- }
-
- /**
- * Returns the minimum width needed to show buttons.
- * Used to calculate the minimum size of the call dialog.
- * @return the minimum width for the buttons.
- */
- public int getMinimumButtonWidth()
- {
- int numberOfButtons = countButtons(settingsPanel.getComponents());
-
- if (numberOfButtons > 0)
- {
- // +1 cause we had and a hangup button
- // *32, a button is 28 pixels width and give some border
- return (numberOfButtons + 1) * 32;
- }
- else
- return -1;
- }
-
- /**
- * Initializes buttons order in the call tool bar.
- */
- private void initButtonIndexes()
- {
- if (dialButton != null)
- dialButton.setIndex(0);
- if (conferenceButton != null)
- conferenceButton.setIndex(1);
- if (holdButton != null)
- holdButton.setIndex(2);
- if (recordButton != null)
- recordButton.setIndex(3);
- if (mergeButton != null)
- mergeButton.setIndex(4);
- if (transferCallButton != null)
- transferCallButton.setIndex(5);
-
- localLevel.setIndex(6);
- if (remoteLevel instanceof OrderedComponent)
- ((OrderedComponent) remoteLevel).setIndex(7);
-
- if (desktopSharingButton != null)
- desktopSharingButton.setIndex(8);
-
- if (fullScreenButton != null)
- fullScreenButton.setIndex(10);
-
- if (videoButton != null)
- videoButton.setIndex(11);
- if (showHideVideoButton != null)
- showHideVideoButton.setIndex(12);
- chatButton.setIndex(19);
-
- if (infoButton != null)
- infoButton.setIndex(20);
-
- hangupButton.setIndex(100);
- }
-
- /**
- * Initialize plug-in components already registered for this container.
- */
- private void initPluginComponents()
- {
- // Search for plug-in components registered through the OSGI
- // BundleContext.
-
- String osgiFilter
- = "("
- + net.java.sip.communicator.service.gui.Container.CONTAINER_ID
- + "="
- + net.java.sip.communicator.service.gui.Container
- .CONTAINER_CALL_DIALOG.getID()
- + ")";
- ServiceReference[] serRefs = null;
-
- try
- {
- serRefs
- = GuiActivator.bundleContext.getServiceReferences(
- PluginComponentFactory.class.getName(),
- osgiFilter);
- }
- catch (InvalidSyntaxException ise)
- {
- logger.error("Could not obtain plugin reference.", ise);
- }
-
- if (serRefs != null)
- {
- for (ServiceReference serRef : serRefs)
- {
- PluginComponentFactory factory
- = (PluginComponentFactory)
- GuiActivator.bundleContext.getService(serRef);
-
- PluginComponent component =
- factory.getPluginComponentInstance(CallPanel.this);
-
- component.setCurrentContact(
- CallManager.getPeerMetaContact(
- callConference.getCallPeers().get(0)));
-
- settingsPanel.add((Component) component.getComponent());
- }
- }
-
- GuiActivator.getUIService().addPluginComponentListener(this);
- }
-
- /**
- * Initializes the user interface hierarchy of this <tt>CallPanel</tt> i.e.
- * the AWT <tt>Component</tt>s which constitute the user interface to be
- * displayed by this <tt>Component</tt>. Their state does not have to depict
- * the current state of the model of this view because
- * {@link #updateViewFromModel()} will be invoked before this view becomes
- * visible. At the center of the user interface of this view is
- * {@link #callPanel} but it is dynamically added and removed multiple times
- * as part of the execution of the <tt>updateViewFromModel</tt> method so
- * it is not dealt with here.
- */
- private void initializeUserInterfaceHierarchy()
- {
- /*
- * The settingsPanel will contain the buttons. It is initialized before
- * the buttons in case any of the buttons need it (which is hard to
- * determine at the time of this writing).
- */
- settingsPanel = new CallToolBar(isFullScreen(), false);
-
- /*
- * TODO CallPanel depicts a whole CallConference which may have multiple
- * Calls, new Calls may be added to the CallConference and existing
- * Calls may be removed from the CallConference. For example, the
- * buttons which accept a Call as an argument should be changed to take
- * into account the whole CallConference.
- */
- Call aCall = callConference.getCalls().get(0);
-
- chatButton
- = new CallToolBarButton(
- ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_WHITE),
- CHAT_BUTTON,
- GuiActivator.getResources().getI18NString(
- "service.gui.CHAT"));
-
- if(isButtonEnabled(HIDE_CONFERENCE_BUTON_PROP))
- {
- conferenceButton
- = new CallToolBarButton(
- ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON),
- CONFERENCE_BUTTON,
- GuiActivator.getResources().getI18NString(
- "service.gui.CREATE_CONFERENCE_CALL"));
- }
-
- if(isButtonEnabled(HIDE_DESKTOP_SHARING_BUTON_PROP))
- {
- desktopSharingButton = new DesktopSharingButton(aCall);
- }
-
- if(isButtonEnabled(HIDE_DIAL_BUTON_PROP))
- {
- dialButton
- = new CallToolBarButton(
- ImageLoader.getImage(ImageLoader.DIAL_BUTTON),
- DIAL_BUTTON,
- GuiActivator.getResources().getI18NString(
- "service.gui.DIALPAD"));
- }
-
- if(isButtonEnabled(HIDE_FULL_SCREEN_BUTON_PROP))
- {
- fullScreenButton = new FullScreenButton(this);
- }
-
- hangupButton = new HangupButton(this);
-
- if(isButtonEnabled(HIDE_CALL_HOLD_BUTON_PROP))
- {
- holdButton = new HoldButton(aCall);
- }
-
- if(isButtonEnabled(HIDE_CALL_INFO_BUTON_PROP))
- {
- infoButton
- = new CallToolBarButton(
- ImageLoader.getImage(ImageLoader.CALL_INFO),
- INFO_BUTTON,
- GuiActivator.getResources().getI18NString(
- "service.gui.PRESS_FOR_CALL_INFO"));
- }
-
- if(isButtonEnabled(HIDE_CALL_MERGE_BUTON_PROP))
- {
- mergeButton
- = new CallToolBarButton(
- ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON),
- MERGE_BUTTON,
- GuiActivator.getResources().getI18NString(
- "service.gui.MERGE_TO_CALL"));
-
- }
-
- if(isButtonEnabled(HIDE_CALL_RECORD_BUTON_PROP))
- {
- recordButton = new RecordButton(aCall);
- }
-
- if(isButtonEnabled(HIDE_TOGGLE_VIDEO_BUTON_PROP))
- {
- showHideVideoButton = new ShowHideVideoButton(uiVideoHandler);
- }
-
- if(isButtonEnabled(HIDE_CALL_TRANSFER_BUTON_PROP))
- {
- transferCallButton = new TransferCallButton(aCall);
- }
-
- if(isButtonEnabled(HIDE_VIDEO_BUTON_PROP))
- {
- videoButton = new LocalVideoButton(aCall);
- }
-
- localLevel
- = new InputVolumeControlButton(
- aCall,
- ImageLoader.MICROPHONE,
- ImageLoader.MUTE_BUTTON,
- true,
- false);
- remoteLevel
- = new OutputVolumeControlButton(
- callConference,
- ImageLoader.VOLUME_CONTROL_BUTTON,
- false,
- true)
- .getComponent();
-
- /*
- * Now that the buttons have been initialized, set their order indexes
- * so that they get added in the correct order later on.
- */
- initButtonIndexes();
-
- chatButton.addActionListener(this);
- if (conferenceButton != null)
- conferenceButton.addActionListener(this);
- if (dialButton != null)
- dialButton.addActionListener(this);
- if (infoButton != null)
- infoButton.addActionListener(this);
- if (mergeButton != null)
- mergeButton.addActionListener(this);
-
- settingsPanel.add(chatButton);
- if (conferenceButton != null)
- settingsPanel.add(conferenceButton);
- if (desktopSharingButton != null)
- settingsPanel.add(desktopSharingButton);
- if (dialButton != null)
- settingsPanel.add(dialButton);
- if (fullScreenButton != null)
- settingsPanel.add(fullScreenButton);
-
- settingsPanel.add(hangupButton);
-
- if (holdButton != null)
- settingsPanel.add(holdButton);
- if (infoButton != null)
- settingsPanel.add(infoButton);
- if (mergeButton != null)
- settingsPanel.add(mergeButton);
- if (recordButton != null)
- settingsPanel.add(recordButton);
- if (showHideVideoButton != null)
- settingsPanel.add(showHideVideoButton);
- if (mergeButton != null)
- settingsPanel.add(transferCallButton);
- if (videoButton != null)
- settingsPanel.add(videoButton);
-
- // The bottom bar will contain the settingsPanel.
- add(createBottomBar(), BorderLayout.SOUTH);
- }
-
- /**
- * Tests a provided boolean property name, returning false if it should be
- * hidden.
- *
- * Used in {@link #initializeUserInterfaceHierarchy()}
- * @param buttonHidePropertyName the name of the boolean property to check.
- * @return false if the button should be hidden, true otherwise.
- *
- */
- private boolean isButtonEnabled(String buttonHidePropertyName)
- {
- return !GuiActivator.getConfigurationService().getBoolean(
- buttonHidePropertyName,
- false);
- }
-
- /**
- * Returns <code>true</code> if the call timer has been started, otherwise
- * returns <code>false</code>.
- * @return <code>true</code> if the call timer has been started, otherwise
- * returns <code>false</code>
- */
- public boolean isCallTimerStarted()
- {
- return isCallTimerStarted;
- }
-
- /**
- * Checks if the contained call is a conference call.
- *
- * @return <code>true</code> if the contained <tt>Call</tt> is a conference
- * call, otherwise - returns <code>false</code>.
- */
- boolean isConference()
- {
- // If we're the focus of the conference.
- if (callConference.isConferenceFocus())
- return true;
-
- // If one of our peers is a conference focus, we're in a
- // conference call.
- List<CallPeer> callPeers = callConference.getCallPeers();
-
- for (CallPeer callPeer : callPeers)
- {
- if (callPeer.isConferenceFocus())
- return true;
- }
-
- // the call can have two peers at the same time and there is no one
- // is conference focus. This is situation when someone has made an
- // attended transfer and has transfered us. We have one call with two
- // peers the one we are talking to and the one we have been transfered
- // to. And the first one is been hanged up and so the call passes through
- // conference call focus a moment and than go again to one to one call.
- return callPeers.size() > 1;
- }
-
- /**
- * Determines whether this view is displayed in full-screen or windowed
- * mode.
- *
- * @return <tt>true</tt> if this view is displayed in full-screen mode or
- * <tt>false</tt> for windowed mode
- */
- boolean isFullScreen()
- {
- return callWindow.isFullScreen();
- }
-
- /**
- * Checks whether recording is currently enabled or not, state retrieved
- * from call record button state.
- *
- * @return <tt>true</tt> if the recording is already started, <tt>false</tt>
- * otherwise
- */
- public boolean isRecordingStarted()
- {
- if (recordButton == null)
- return false;
-
- return recordButton.isSelected();
- }
-
- /**
- * Returns <tt>true</tt> if the show/hide video button is currently selected,
- * <tt>false</tt> - otherwise.
- *
- * @return <tt>true</tt> if the show/hide video button is currently selected,
- * <tt>false</tt> - otherwise
- */
- public boolean isShowHideVideoButtonSelected()
- {
- return showHideVideoButton.isSelected();
- }
-
- /**
- * Reloads icons.
- */
- public void loadSkin()
- {
- if (dialButton != null)
- {
- dialButton.setBackgroundImage(
- ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG));
- dialButton.setIconImage(
- ImageLoader.getImage(ImageLoader.DIAL_BUTTON));
- }
-
- if (conferenceButton != null)
- {
- conferenceButton.setBackgroundImage(
- ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG));
- conferenceButton.setIconImage(
- ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON));
- }
-
- if (hangupButton != null)
- hangupButton.setBackgroundImage(
- ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG));
- }
-
- /**
- * Notifies this instance about a specific <tt>VideoEvent</tt> which may
- * warrant {@link #ensureSize(Component, int, int)} to be invoked in order
- * to try to have the associated visual <tt>Component</tt> displaying video
- * shown without scaling. The method will execute on the AWT event
- * dispatching thread because it will be making its judgments based on the
- * properties of AWT <tt>Component</tt>s.
- *
- * @param ev a <tt>VideoEvent</tt> which represents the cause of the
- * notification and specifies the visual <tt>Component</tt> displaying video
- * which may need an adjustment of a Frame's size in order to be displayed
- * without scaling
- */
- private void maybeEnsureSize(final VideoEvent ev)
- {
- if (!SwingUtilities.isEventDispatchThread())
- {
- SwingUtilities.invokeLater(
- new Runnable()
- {
- public void run()
- {
- maybeEnsureSize(ev);
- }
- });
- return;
- }
-
- if (ev instanceof SizeChangeVideoEvent)
- {
- /*
- * If a visual Component depicting video (streaming between the
- * local peer/user and the remote peers) changes its size, try to
- * adjust the size of the Frame which displays it so that it appears
- * without scaling.
- */
- SizeChangeVideoEvent scev = (SizeChangeVideoEvent) ev;
-
- ensureSize(
- scev.getVisualComponent(),
- scev.getWidth(), scev.getHeight());
- }
- else if (ev.getType() == VideoEvent.VIDEO_ADDED)
- {
- Component video = ev.getVisualComponent();
-
- if ((video != null)
- && UIVideoHandler2.isAncestor(this, video)
- && video.isPreferredSizeSet())
- {
- Dimension prefSize = video.getPreferredSize();
-
- if ((prefSize.height > 0) && (prefSize.width > 0))
- {
- Dimension size = video.getSize();
-
- if ((prefSize.height > size.height)
- || (prefSize.width > size.width))
- {
- ensureSize(
- video,
- prefSize.width, prefSize.height);
- }
- }
- }
- }
- }
-
- /**
- * Invoked by {@link #callConferenceListener} to notify this instance about
- * an <tt>EventObject</tt> related to the <tt>CallConference</tt> depicted
- * by this <tt>CallPanel</tt>, the <tt>Call</tt>s participating in it,
- * the <tt>CallPeer</tt>s associated with them, the
- * <tt>ConferenceMember</tt>s participating in any telephony conferences
- * organized by them, etc. In other words, notifies this instance about
- * any change which may cause an update to be required so that this view
- * i.e. <tt>CallPanel</tt> depicts the current state of its model i.e.
- * {@link #callConference}.
- *
- * @param ev the <tt>EventObject</tt> this instance is being notified
- * about.
- */
- private void onCallConferenceEventObject(EventObject ev)
- {
- /*
- * The main task is to invoke updateViewFromModel() in order to make
- * sure that this view depicts the current state of its model.
- */
-
- try
- {
- /*
- * However, we seem to be keeping track of the duration of the call
- * (i.e. the telephony conference) in the user interface. Stop the
- * Timer which ticks the duration of the call as soon as the
- * telephony conference depicted by this instance appears to have
- * ended. The situation will very likely occur when a Call is
- * removed from the telephony conference or a CallPeer is removed
- * from a Call.
- */
- boolean tryStopCallTimer = false;
-
- if (ev instanceof CallPeerEvent)
- {
- tryStopCallTimer
- = (CallPeerEvent.CALL_PEER_REMOVED
- == ((CallPeerEvent) ev).getEventID());
- }
- else if (ev instanceof PropertyChangeEvent)
- {
- PropertyChangeEvent pcev = (PropertyChangeEvent) ev;
-
- tryStopCallTimer
- = (CallConference.CALLS.equals(pcev.getPropertyName())
- && (pcev.getOldValue() instanceof Call)
- && (pcev.getNewValue() == null));
- }
- if (tryStopCallTimer
- && (callConference.isEnded()
- || callConference.getCallPeerCount() == 0))
- {
- stopCallTimer();
- }
- }
- finally
- {
- updateViewFromModel();
- }
- }
-
- /**
- * Notifies this <tt>CallPanel</tt> about a specific <tt>CallEvent</tt>
- * (received by <tt>CallManager</tt>). The source <tt>Call</tt> may or may
- * not be participating in the telephony conference depicted by this
- * instance but allows it to update any state which may depend on the
- * <tt>Call</tt>s which are established application-wide.
- *
- * @param ev a <tt>CallEvent</tt> which specifies the <tt>Call</tt> which
- * caused this instance to be notified and the exact type of the
- * notification event
- */
- void onCallEvent(CallEvent ev)
- {
- updateMergeButtonState();
- }
-
- /**
- * Adds/removes the <tt>Component</tt> of the <tt>PluginComponent</tt>
- * specified by a <tt>PluginComponentEvent</tt> to/from
- * {@link #settingsPanel} (if it is appropriate for this
- * <tt>Container</tt>).
- *
- * @param ev a <tt>PluginComponentEvent</tt> which specifies the
- * <tt>PluginComponent</tt> whose <tt>Component</tt> is to be added/removed
- * to/from {@link #settingsPanel}
- */
- protected void onPluginComponentEvent(PluginComponentEvent ev)
- {
- PluginComponentFactory pc = ev.getPluginComponentFactory();
-
- if (pc.getContainer().equals(
- net.java.sip.communicator.service.gui.Container
- .CONTAINER_CALL_DIALOG))
- {
- PluginComponent plugin =
- pc.getPluginComponentInstance(CallPanel.this);
- Component c = (Component)plugin.getComponent();
- plugin.setCurrentContact(
- CallManager.getPeerMetaContact(
- callConference.getCallPeers().get(0)));
-
- switch (ev.getEventID())
- {
- case PluginComponentEvent.PLUGIN_COMPONENT_ADDED:
- settingsPanel.add(c);
- break;
- case PluginComponentEvent.PLUGIN_COMPONENT_REMOVED:
- settingsPanel.remove(c);
- break;
- }
-
- settingsPanel.revalidate();
- settingsPanel.repaint();
- }
- }
-
- /**
- * Indicates that the peer panel was added.
- *
- * @param ev the event.
- */
- public void peerViewAdded(ConferencePeerViewEvent ev) {}
-
- /**
- * Indicates that the peer panel was removed.
- *
- * @param ev the event.
- */
- public void peerViewRemoved(ConferencePeerViewEvent ev)
- {
- updateViewFromModel();
- }
-
- /**
- * {@inheritDoc}
- *
- * Adds the <tt>Component</tt> of the <tt>PluginComponent</tt> specified by
- * the <tt>PluginComponentEvent</tt> to {@link #settingsPanel} (if it is
- * appropriate for this <tt>Container</tt>).
- */
- public void pluginComponentAdded(PluginComponentEvent ev)
- {
- onPluginComponentEvent(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Removes the <tt>Component</tt> of the <tt>PluginComponent</tt> specified
- * by the <tt>PluginComponentEvent</tt> from {@link #settingsPanel} (if it
- * is appropriate for this <tt>Container</tt>).
- */
- public void pluginComponentRemoved(PluginComponentEvent ev)
- {
- onPluginComponentEvent(ev);
- }
-
- /**
- * Removes the given <tt>CallTitleListener</tt> to the list of listeners,
- * notified for call title changes.
- *
- * @param l the <tt>CallTitleListener</tt> to remove
- */
- public void removeCallTitleListener(CallTitleListener l)
- {
- synchronized (titleListeners)
- {
- titleListeners.remove(l);
- }
- }
-
- /**
- * Remove remote video specific components.
- */
- public void removeRemoteVideoSpecificComponents()
- {
- if(resizeVideoButton != null)
- settingsPanel.remove(resizeVideoButton);
-
- settingsPanel.revalidate();
- settingsPanel.repaint();
- }
-
- /**
- * Sets the title of this dialog in accord with a specific time of start of
- * the telephony call/conference depicted by this <tt>CallPanel</tt>.
- *
- * @param startTime the time in milliseconds at which the telephony
- * call/conference depicted by this <tt>CallPanel</tt> is considered to have
- * started
- */
- private void setCallTitle(long startTime)
- {
- StringBuilder title = new StringBuilder();
-
- if (startTime != 0)
- {
- title.append(
- GuiUtils.formatTime(
- startTime,
- System.currentTimeMillis()));
- title.append(" | ");
- }
- else
- title.append("00:00:00 | ");
-
- List<CallPeer> callPeers = callConference.getCallPeers();
-
- if ((callPeers.size() > 0)
- && (GuiActivator.getUIService().getSingleWindowContainer()
- != null))
- {
- title.append(callPeers.get(0).getDisplayName());
- }
- else
- {
- title.append(
- GuiActivator.getResources().getI18NString(
- "service.gui.CALL"));
- }
-
- this.title = title.toString();
-
- fireTitleChangeEvent();
- }
-
- /**
- * Sets the display of this view to full-screen or windowed mode.
- *
- * @param fullScreen <tt>true</tt> to display this view in full-screen mode
- * or <tt>false</tt> for windowed mode
- */
- void setFullScreen(boolean fullScreen)
- {
- callWindow.setFullScreen(fullScreen);
- }
-
- /**
- * Selects or unselects the video button in this call dialog.
- *
- * @param isSelected indicates if the video button should be selected or not
- */
- public void setVideoButtonSelected(boolean isSelected)
- {
- if (isSelected && !videoButton.isSelected())
- videoButton.setSelected(true);
- else if (!isSelected && videoButton.isSelected())
- videoButton.setSelected(false);
- }
-
- /**
- * Starts the timer that counts call duration.
- */
- public void startCallTimer()
- {
- callConferenceStartTime = System.currentTimeMillis();
- callDurationTimer.start();
- isCallTimerStarted = true;
- }
-
- /**
- * Stops the timer that counts call duration.
- */
- public void stopCallTimer()
- {
- this.callDurationTimer.stop();
- }
-
- /**
- * Notifies this instance that {@link #uiVideoHandler} has reported a change
- * in the video-related information which may warrant an update of this view
- * from its model.
- *
- * @param arg an <tt>Object</tt>, if any, which represents the cause that
- * triggered the notification
- */
- private void uiVideoHandlerUpdate(Object arg)
- {
- /* The most important task is to update this view from its model. */
-
- /*
- * If a visual Component displaying video is reported to have been
- * added/prepared/received, we may have to adjust the size of the Frame
- * displaying this user interface so that the video appears without
- * scaling.
- */
- /*
- * XXX The following may be making judgments about the user interface
- * out of the AWT event dispatching thread which is a prerequisite for
- * unexpected behavior. Anyway, that's the only idea at the time of this
- * writing.
- */
- VideoEvent maybeEnsureSize = null;
-
- if (arg instanceof VideoEvent)
- {
- try
- {
- VideoEvent vev = (VideoEvent) arg;
- int vevType = vev.getType();
-
- if (vevType == VideoEvent.VIDEO_ADDED)
- {
- Component video = vev.getVisualComponent();
-
- if ((video != null)
- && !UIVideoHandler2.isAncestor(this, video))
- {
- maybeEnsureSize = vev;
- }
- }
- else if (vevType == SizeChangeVideoEvent.VIDEO_SIZE_CHANGE)
- {
- /*
- * If a visual Component depicting video (streaming between
- * the local peer/user and the remote peers) changes its
- * size, try to adjust the size of the Frame which displays
- * it so that it appears without scaling.
- */
- maybeEnsureSize = vev;
- }
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- else if (logger.isDebugEnabled())
- {
- logger.debug(
- "Failed to determine whether it is necessary to"
- + " adjust a Frame's size in response to a"
- + " VideoEvent.",
- t);
- }
- }
- }
-
- updateViewFromModel();
-
- if (maybeEnsureSize != null)
- {
- /*
- * The method maybeEnsureSize will execute on the AWT event
- * dispatching thread.
- */
- try
- {
- maybeEnsureSize(maybeEnsureSize);
- }
- catch (Throwable t)
- {
- if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- else
- {
- logger.error(
- "Failed to adjust a Frame's size"
- + " in response to a VideoEvent.",
- t);
- }
- }
- }
- }
-
- /**
- * Updates the state of the general hold button. The hold button is selected
- * only if all call peers are locally or mutually on hold at the same time.
- * In all other cases the hold button is unselected.
- */
- public void updateHoldButtonState()
- {
- // If the hold button has been disabled by its configuration property we
- // have nothing more to do here.
- if (holdButton == null)
- return;
-
- if(!SwingUtilities.isEventDispatchThread())
- {
- SwingUtilities.invokeLater(new Runnable()
- {
- public void run()
- {
- updateHoldButtonState();
- }
- });
-
- return;
- }
-
- List<CallPeer> peers = callConference.getCallPeers();
- boolean areAllPeersLocallyOnHold;
-
- if (peers.isEmpty())
- {
- /*
- * It feels natural to not have the holdButton selected when there
- * are no peers.
- */
- areAllPeersLocallyOnHold = false;
- }
- else
- {
- areAllPeersLocallyOnHold = true;
- for (CallPeer peer : callConference.getCallPeers())
- {
- CallPeerState state = peer.getState();
-
- // If we have clicked the hold button in a full screen mode
- // we need to update the state of the call dialog hold button.
- if (!state.equals(CallPeerState.ON_HOLD_LOCALLY)
- && !state.equals(CallPeerState.ON_HOLD_MUTUALLY))
- {
- areAllPeersLocallyOnHold = false;
- break;
- }
- }
- }
-
- // If we have clicked the hold button in a full screen mode or selected
- // hold of the peer menu in a conference call we need to update the
- // state of the call dialog hold button.
- holdButton.setSelected(areAllPeersLocallyOnHold);
- }
-
- /**
- * Updates the <tt>visible</tt> state/property of {@link #mergeButton} if
- * the merge button is present.
- */
- private void updateMergeButtonState()
- {
- // If the merge button isn't present, for example if it's hidden by
- // its configuration property we have nothing more to do here.
- if (mergeButton == null)
- return;
-
- List<CallConference> conferences = new ArrayList<CallConference>();
- int cpt = 0;
-
- for (Call call : CallManager.getInProgressCalls())
- {
- CallConference conference = call.getConference();
-
- if (conference == null)
- cpt++;
- else if (!conferences.contains(conference))
- {
- conferences.add(conference);
- cpt++;
- }
- else
- continue;
-
- if (cpt > 1)
- break;
- }
-
- mergeButton.setVisible(cpt > 1);
- }
-
- /**
- * Updates {@link #settingsPanel} from the model of this view. The update is
- * performed in the AWT event dispatching thread.
- * <p>
- * The center of this view is occupied by {@link #callPanel}, the bottom of
- * this view is dedicated to <tt>settingsPanel</tt>. The method
- * {@link #updateViewFromModelInEventDispatchThread()} updates
- * <tt>callPanel</tt> from the model of this view and then invokes the
- * method <tt>updateSettingsPanelInEventDispatchThread()</tt>. Thus this
- * whole view is updated so that it depicts the current state of its model.
- * </p>
- *
- * @param callConferenceIsEnded <tt>true</tt> if the method
- * <tt>updateViewFromModelInEventDispatchThread()</tt> considers the
- * {@link #callConference} ended; otherwise, <tt>false</tt>. When the
- * <tt>callConference</tt> is considered ended, the <tt>callPanel</tt>
- * instance will not be switched to a specific type (one-to-one, audio-only,
- * or audio/video) because, otherwise, the switch will leave it
- * <tt>null</tt> and this view will remain blank. In such a case,
- * <tt>settingsPanel</tt> may wish to do pretty much the same but disable
- * and/or hide the buttons it contains.
- */
- private void updateSettingsPanelInEventDispatchThread(
- boolean callConferenceIsEnded)
- {
- /*
- * XXX The method directly delegates to the method
- * doUpdateSettingsPanelInEventDispatchThread at the time of this
- * writing which may be considered a waste. But in the fashion of the
- * method updateViewFromModelInEventDispatchThread we have made it easy
- * to add code before and/or after the invocation of the delegate.
- */
- doUpdateSettingsPanelInEventDispatchThread(callConferenceIsEnded);
- }
-
- /**
- * Updates this view i.e. <tt>CallPanel</tt> so that it depicts the current
- * state of its model i.e. <tt>callConference</tt>.
- */
- private void updateViewFromModel()
- {
- /*
- * We receive events/notifications from various threads and we respond
- * to them in the AWT event dispatching thread. It is possible to first
- * schedule an event to be brought to the AWT event dispatching thread,
- * then to have #dispose() invoked on this instance and, finally, to
- * receive the scheduled event in the AWT event dispatching thread. In
- * such a case, this disposed instance should not respond to the event.
- */
- if (!disposed)
- {
- if (SwingUtilities.isEventDispatchThread())
- updateViewFromModelInEventDispatchThread();
- else
- {
- SwingUtilities.invokeLater(
- updateViewFromModelInEventDispatchThread);
- }
- }
- }
-
- /**
- * Updates this view i.e. <tt>CallPanel</tt> so that it depicts the current
- * state of its model i.e. <tt>callConference</tt>. The update is performed
- * in the AWT event dispatching thread.
- */
- private void updateViewFromModelInEventDispatchThread()
- {
- /*
- * We receive events/notifications from various threads and we respond
- * to them in the AWT event dispatching thread. It is possible to first
- * schedule an event to be brought to the AWT event dispatching thread,
- * then to have #dispose() invoked on this instance and, finally, to
- * receive the scheduled event in the AWT event dispatching thread. In
- * such a case, this disposed instance should not respond to the event.
- */
- if (disposed)
- return;
-
- /*
- * We may add, remove, show, and hide various Components of the user
- * interface hierarchy of this instance bellow. Consequently, this view
- * may become larger in width and/or height than its current Frame has
- * dedicated to it. Try to detect such cases and attempt to adjust the
- * Frame's size accordingly.
- */
- Dimension oldPrefSize = getPreferredSize();
-
- doUpdateViewFromModelInEventDispatchThread();
-
- /*
- * We may have added, removed, shown, and hidden various Components of
- * the user interface hierarchy of this instance above. Consequently,
- * this view may have become larger in width and/or height than its
- * current Frame has dedicated to it. Try to detect such cases and
- * attempt to adjust the Frame's size accordingly.
- */
- Dimension newPrefSize = getPreferredSize();
-
- if ((newPrefSize != null)
- && ((newPrefSize.height > getHeight())
- || (newPrefSize.width > getWidth())))
- {
- int oldPrefHeight, oldPrefWidth;
-
- if (oldPrefSize == null)
- {
- oldPrefHeight = 0;
- oldPrefWidth = 0;
- }
- else
- {
- oldPrefHeight = oldPrefSize.height;
- oldPrefWidth = oldPrefSize.width;
- }
- if ((newPrefSize.height != oldPrefHeight)
- || (newPrefSize.width != oldPrefWidth))
- {
- ensureSize(
- this,
- newPrefSize.width, newPrefSize.height);
- }
- }
- }
-
- /**
- * Listens for contact status changes and updates the image of the
- * chat message button.
- * @param evt the ContactPresenceStatusChangeEvent describing the status
- */
- @Override
- public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt)
- {
- Contact contact = getIMCapableCallPeers(1).get(0);
-
- if(contact != null && contact.equals(evt.getSourceContact()))
- {
- chatButton.setIconImage(
- Constants.getMessageStatusIcon(contact.getPresenceStatus()));
- chatButton.repaint();
- }
- }
-
- /**
- * Implements the listener which listens to events fired by the
- * <tt>CallConference</tt> depicted by this instance, the <tt>Call</tt>s
- * participating in that telephony conference, the <tt>CallPeer</tt>s
- * associated with those <tt>Call</tt>s and the <tt>ConferenceMember</tt>s
- * participating in the telephony conferences organized by those
- * <tt>CallPeer</tt>s. Updates this view i.e. CallPanel so that it depicts
- * the current state of its model i.e. {@link #callConference}.
- */
- private class CallConferenceListener
- extends CallPeerConferenceAdapter
- implements CallChangeListener,
- PropertyChangeListener
- {
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the
- * <tt>CallPeerEvent</tt> allows distinguishing whether a
- * <tt>CallPeer</tt> was added or removed by examining its
- * <tt>eventID</tt>.
- */
- public void callPeerAdded(CallPeerEvent ev)
- {
- onCallPeerEvent(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the
- * <tt>CallPeerEvent</tt> allows distinguishing whether a
- * <tt>CallPeer</tt> was added or removed by examining its
- * <tt>eventID</tt>.
- */
- public void callPeerRemoved(CallPeerEvent ev)
- {
- onCallPeerEvent(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #onEventObject(EventObject)}.
- */
- public void callStateChanged(CallChangeEvent ev)
- {
- onEventObject(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #onEventObject(EventObject)}.
- */
- @Override
- protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
- {
- onEventObject(ev);
- }
-
- /**
- * Notifies this <tt>CallChangeListener</tt> about a specific
- * <tt>CallPeerEvent</tt> i.e. that a <tt>CallPeer</tt> was added to or
- * removed from a <tt>Call</tt>. Invokes
- * {@link #onEventObject(EventObject)}.
- *
- * @param ev the <tt>CallPeerEvent</tt> to notify this
- * <tt>CallChangeListener</tt> about i.e. which specifies the
- * <tt>CallPeer</tt> which was added/removed and the <tt>Call</tt>
- * to/from which it was added/removed
- */
- private void onCallPeerEvent(CallPeerEvent ev)
- {
- onEventObject(ev);
- }
-
- /**
- * Invoked by the various listener method implementations provided by
- * this <tt>CallConferenceListener</tt> to notify this instance about an
- * <tt>EventObject</tt> related to the <tt>CallConference</tt> depicted
- * by this <tt>CallPanel</tt>, the <tt>Call</tt>s participating in it,
- * the <tt>CallPeer</tt>s associated with them, the
- * <tt>ConferenceMember</tt>s participating in any telephony conferences
- * organized by them, etc. In other words, notifies this instance about
- * any change which may cause an update to be required so that this view
- * i.e. <tt>CallPanel</tt> depicts the current state of its model i.e.
- * {@link CallPanel#callConference}.
- *
- * @param ev the <tt>EventObject</tt> this instance is being notified
- * about.
- */
- private void onEventObject(EventObject ev)
- {
- onCallConferenceEventObject(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #onEventObject(EventObject)}.
- */
- public void propertyChange(PropertyChangeEvent ev)
- {
- String propertyName = ev.getPropertyName();
-
- /*
- * If a Call is added to or removed from the CallConference depicted
- * by this CallPanel, an update of the view from its model will most
- * likely be required.
- */
- if (propertyName.equals(CallConference.CALLS))
- {
- onEventObject(ev);
- }
- else if (propertyName.equals(CallContainer.PROP_FULL_SCREEN))
- {
- if (ev.getSource().equals(callWindow.getFrame()))
- {
- try
- {
- /*
- * We'll turn the switching between full-screen and
- * windowed mode into a model state because a
- * significant part of this view changes upon such a
- * switch.
- */
- onEventObject(ev);
- }
- finally
- {
- callWindowPropertyChange(ev);
- }
- }
- }
- }
- }
-}
+/*
+ * 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.gui.main.call;
+
+import java.awt.*;
+import java.awt.Container;
+import java.awt.event.*;
+import java.beans.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.Timer;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.impl.gui.event.*;
+import net.java.sip.communicator.impl.gui.main.call.conference.*;
+import net.java.sip.communicator.impl.gui.utils.*;
+import net.java.sip.communicator.impl.gui.utils.Constants;
+import net.java.sip.communicator.plugin.desktoputil.*;
+import net.java.sip.communicator.service.contactlist.*;
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.gui.call.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.Logger;
+import net.java.sip.communicator.util.skin.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jitsi.util.*;
+import org.jitsi.util.event.*;
+import org.osgi.framework.*;
+
+/**
+ * The dialog created for a given call.
+ *
+ * Ordered buttons we are adding/removing, numbers are the index we have set.
+ * And the order that will be kept.
+ * 0 dialButton
+ * 1 conferenceButton
+ * 2 holdButton
+ * 3 recordButton
+ * 4 mergeButton
+ * 5 transferCallButton
+ * 6 localLevel
+ * 7 remoteLevel
+ * 8 desktopSharingButton
+ * 9 resizeVideoButton
+ * 10 fullScreenButton
+ * 11 videoButton
+ * 12 showHideVideoButton
+ * 19 chatButton
+ * 20 infoButton
+ *
+ * @author Yana Stamcheva
+ * @author Adam Netocny
+ * @author Lyubomir Marinov
+ * @author Hristo Terezov
+ */
+public class CallPanel
+ extends TransparentPanel
+ implements ActionListener,
+ PluginComponentListener,
+ Skinnable,
+ ConferencePeerViewListener,
+ ContactPresenceStatusListener
+{
+ /**
+ * The chat button name.
+ */
+ private static final String CHAT_BUTTON = "CHAT_BUTTON";
+
+ /**
+ * The conference button name.
+ */
+ private static final String CONFERENCE_BUTTON = "CONFERENCE_BUTTON";
+
+ /**
+ * The dial button name.
+ */
+ private static final String DIAL_BUTTON = "DIAL_BUTTON";
+
+ /**
+ * The info button name.
+ */
+ private static final String INFO_BUTTON = "INFO_BUTTON";
+
+ /**
+ * The logger for this class.
+ */
+ private static final Logger logger = Logger.getLogger(CallDialog.class);
+
+ /**
+ * The hang up button name.
+ */
+ private static final String MERGE_BUTTON = "MERGE_BUTTON";
+
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Property to disable the info button.
+ */
+ private static final String HIDE_CALL_INFO_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_INFO_BUTTON";
+
+ /**
+ * Property to disable the conference "add to call" button.
+ */
+ private static final String HIDE_CONFERENCE_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_CONFERENCE_BUTTON";
+
+ /**
+ * Property to disable the record button.
+ */
+ private static final String HIDE_CALL_RECORD_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_RECORD_BUTTON";
+
+ /**
+ * Property to disable the "call merge" button.
+ */
+ private static final String HIDE_CALL_MERGE_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_MERGE_BUTTON";
+
+ /**
+ * Property to disable the "call merge" button.
+ */
+ private static final String HIDE_CALL_TRANSFER_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_TRANSFER_BUTTON";
+
+ /**
+ * Property to disable the "hold" button.
+ */
+ private static final String HIDE_CALL_HOLD_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_HOLD_BUTTON";
+
+ /**
+ * Property to disable the dial button.
+ */
+ private static final String HIDE_DIAL_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_DIAL_BUTTON";
+
+ /**
+ * Property to disable the video button.
+ */
+ private static final String HIDE_VIDEO_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_VIDEO_BUTTON";
+
+ /**
+ * Property to disable the desktop sharing button.
+ */
+ private static final String HIDE_DESKTOP_SHARING_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_DESKTOP_SHARING_BUTTON";
+
+ /**
+ * Property to disable the full screen button.
+ */
+ private static final String HIDE_FULL_SCREEN_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_FULL_SCREEN_BUTTON";
+
+ /**
+ * Property to disable the "show/hide local video" button.
+ */
+ private static final String HIDE_TOGGLE_VIDEO_BUTON_PROP
+ = "net.java.sip.communicator.impl.gui.main.call.HIDE_TOGGLE_VIDEO_BUTTON";
+
+ /**
+ * The <tt>Component</tt> which is at the bottom of this view and contains
+ * {@link #settingsPanel}. It overrides the Swing-defined background on OS
+ * X so it needs explicit updating upon switching between full-screen and
+ * windowed mode in order to respect any background-related settings of the
+ * ancestors such as black background in full-screen mode.
+ */
+ private JComponent bottomBar;
+
+ /**
+ * The {@link CallConference} instance depicted by this <tt>CallPanel</tt>.
+ */
+ private final CallConference callConference;
+
+ /**
+ * The listener which listens to events fired by the <tt>CallConference</tt>
+ * depicted by this instance, the <tt>Call</tt>s participating in that
+ * telephony conference, the <tt>CallPeer</tt>s associated with those
+ * <tt>Call</tt>s and the <tt>ConferenceMember</tt>s participating in the
+ * telephony conferences organized by those <tt>CallPeer</tt>s. Updates this
+ * view i.e. CallPanel so that it depicts the current state of its model
+ * i.e. {@link #callConference}.
+ */
+ private final CallConferenceListener callConferenceListener
+ = new CallConferenceListener();
+
+ /**
+ * The time in milliseconds at which the telephony call/conference depicted
+ * by this <tt>CallPanel</tt> (i.e. {@link #callConference}) has started.
+ */
+ private long callConferenceStartTime;
+
+ /**
+ * A timer to count call duration.
+ */
+ private Timer callDurationTimer;
+
+ /**
+ * The Frame used to display this call information statistics.
+ */
+ private CallInfoFrame callInfoFrame;
+
+ /**
+ * The panel representing the call. For conference calls this would be an
+ * instance of <tt>ConferenceCallPanel</tt> and for one-to-one calls this
+ * would be an instance of <tt>OneToOneCallPanel</tt>.
+ */
+ private JComponent callPanel;
+
+ /**
+ * Parent window.
+ */
+ private final CallContainer callWindow;
+
+ /**
+ * Chat button.
+ */
+ private SIPCommButton chatButton;
+
+ /**
+ * The operation set that will be used to update chatButton icon and
+ * the corresponding contact.
+ */
+ private OperationSetPresence operationSetPresence;
+
+ /**
+ * The conference button.
+ */
+ private CallToolBarButton conferenceButton;
+
+ /**
+ * The desktop sharing button.
+ */
+ private DesktopSharingButton desktopSharingButton;
+
+ /**
+ * The dial button, which opens a keypad dialog.
+ */
+ private CallToolBarButton dialButton;
+
+ /**
+ * The dial pad dialog opened when the dial button is clicked.
+ */
+ private DialpadDialog dialpadDialog;
+
+ /**
+ * The indicator which determines whether {@link #dispose()} has already
+ * been invoked on this instance. If <tt>true</tt>, this instance is
+ * considered non-functional and is to be left to the garbage collector.
+ */
+ private boolean disposed = false;
+
+ /**
+ * The handler for DTMF tones.
+ */
+ private DTMFHandler dtmfHandler;
+
+ /**
+ * The full screen button.
+ */
+ private FullScreenButton fullScreenButton;
+
+ /**
+ * HangUp button.
+ */
+ private SIPCommButton hangupButton;
+
+ /**
+ * The hold button.
+ */
+ private HoldButton holdButton;
+
+ /**
+ * Info button.
+ */
+ private SIPCommButton infoButton;
+
+ /**
+ * Indicates if the call timer has been started.
+ */
+ private boolean isCallTimerStarted = false;
+
+ /**
+ * Sound local level label.
+ */
+ private InputVolumeControlButton localLevel;
+
+ /**
+ * Merge button.
+ */
+ private CallToolBarButton mergeButton;
+
+ /**
+ * The button which allows starting and stopping the recording of
+ * {@link #callConference}.
+ */
+ private RecordButton recordButton;
+
+ /**
+ * Sound remote level label.
+ */
+ private Component remoteLevel;
+
+ /**
+ * The video resize button.
+ */
+ private ResizeVideoButton resizeVideoButton;
+
+ /**
+ * The panel containing call settings.
+ */
+ private CallToolBar settingsPanel;
+
+ /**
+ * The button responsible for hiding/showing the local video.
+ */
+ private ShowHideVideoButton showHideVideoButton;
+
+ /**
+ * The title of this call container.
+ */
+ private String title;
+
+ /**
+ * A collection of listeners, registered for call title change events.
+ */
+ private Collection<CallTitleListener> titleListeners
+ = new Vector<CallTitleListener>();
+
+ /**
+ * The transfer call button.
+ */
+ private TransferCallButton transferCallButton;
+
+ /**
+ * The facility which aids this instance in the dealing with the
+ * video-related information.
+ */
+ private final UIVideoHandler2 uiVideoHandler;
+
+ /**
+ * Indicates if this call panel should be closed immediately after hang up
+ * or should wait some time so that the user can be notified of the last
+ * state. By default we wait, so that the user can be notified of the
+ * current state of the call.
+ */
+ private boolean isCloseWaitAfterHangup = true;
+
+ /**
+ * The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about
+ * changes in the video-related information.
+ */
+ private final Observer uiVideoHandlerObserver
+ = new Observer()
+ {
+ public void update(Observable o, Object arg)
+ {
+ uiVideoHandlerUpdate(arg);
+ }
+ };
+
+ /**
+ * The <tt>Runnable</tt> which is scheduled by
+ * {@link #updateViewFromModel()} for execution in the AWT event dispatching
+ * thread in order to invoke
+ * {@link #updateViewFromModelInEventDispatchThread()}.
+ */
+ private final Runnable updateViewFromModelInEventDispatchThread
+ = new Runnable()
+ {
+ public void run()
+ {
+ /*
+ * We receive events/notifications from various threads and we
+ * respond to them in the AWT event dispatching thread. It is
+ * possible to first schedule an event to be brought to the AWT
+ * event dispatching thread, then to have #dispose() invoked on
+ * this instance and, finally, to receive the scheduled event in
+ * the AWT event dispatching thread. In such a case, this
+ * disposed instance should not respond to the event.
+ */
+ if (!disposed)
+ updateViewFromModelInEventDispatchThread();
+ }
+ };
+
+ /**
+ * The video button.
+ */
+ private LocalVideoButton videoButton;
+
+ /**
+ * Initializes a new <tt>CallPanel</tt> which is to depict a specific
+ * <tt>CallConference</tt>.
+ *
+ * @param callConference the <tt>CallConference</tt> to be depicted by the
+ * new instance
+ * @param callWindow the parent window in which the new instance will be
+ * added
+ */
+ public CallPanel( CallConference callConference,
+ CallContainer callWindow)
+ {
+ super(new BorderLayout());
+
+ this.callConference = callConference;
+ this.callWindow = callWindow;
+
+ uiVideoHandler = new UIVideoHandler2(this.callConference);
+
+ callDurationTimer
+ = new Timer(
+ 1000,
+ new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setCallTitle(callConferenceStartTime);
+ }
+ });
+ callDurationTimer.setRepeats(true);
+
+ // The call duration parameter is not known yet.
+ setCallTitle(0);
+
+ initializeUserInterfaceHierarchy();
+
+ dtmfHandler = new DTMFHandler(this);
+
+ /*
+ * Adds the listeners which will observe the model and will trigger the
+ * updates of this view from it.
+ */
+ this.callConference.addCallChangeListener(callConferenceListener);
+ this.callConference.addCallPeerConferenceListener(
+ callConferenceListener);
+ this.callConference.addPropertyChangeListener(callConferenceListener);
+ uiVideoHandler.addObserver(uiVideoHandlerObserver);
+
+ callWindow.getFrame().addPropertyChangeListener(
+ CallContainer.PROP_FULL_SCREEN,
+ callConferenceListener);
+
+ updateViewFromModel();
+
+ initPluginComponents();
+ }
+
+ /**
+ * Handles action events.
+ * @param evt the <tt>ActionEvent</tt> that was triggered
+ */
+ public void actionPerformed(ActionEvent evt)
+ {
+ JButton button = (JButton) evt.getSource();
+ String buttonName = button.getName();
+
+ if (buttonName.equals(MERGE_BUTTON))
+ {
+ CallManager.mergeExistingCalls(
+ callConference,
+ CallManager.getInProgressCalls());
+ }
+ else if (buttonName.equals(DIAL_BUTTON))
+ {
+ if (dialpadDialog == null)
+ dialpadDialog = this.getDialpadDialog();
+
+ if(!dialpadDialog.isVisible())
+ {
+ dialpadDialog.pack();
+
+ Point location = new Point( button.getX(),
+ button.getY() + button.getHeight());
+ SwingUtilities.convertPointToScreen(
+ location, button.getParent());
+
+ dialpadDialog.setLocation(
+ (int) location.getX() + 2,
+ (int) location.getY() + 2);
+
+ dialpadDialog.addWindowFocusListener(dialpadDialog);
+ dialpadDialog.setVisible(true);
+ }
+ else
+ {
+ dialpadDialog.removeWindowFocusListener(dialpadDialog);
+ dialpadDialog.setVisible(false);
+ }
+ }
+ else if (buttonName.equals(CONFERENCE_BUTTON))
+ {
+ ConferenceInviteDialog inviteDialog;
+
+ if (callConference.isJitsiVideobridge())
+ {
+ inviteDialog
+ = new ConferenceInviteDialog(
+ callConference,
+ callConference.getCalls().get(0)
+ .getProtocolProvider(),
+ true);
+ }
+ else
+ inviteDialog = new ConferenceInviteDialog(callConference);
+
+ inviteDialog.setVisible(true);
+ }
+ else if (buttonName.equals(CHAT_BUTTON))
+ {
+ /*
+ * If there is exactly 1 CallPeer capable of instant messaging, then
+ * we'll try to start a chat with her.
+ */
+ /*
+ * TODO The following is very likely to block the user interface in
+ * a noticeable way sooner or later.
+ */
+ List<Contact> imCapableCallPeers = getIMCapableCallPeers(1);
+
+ if (imCapableCallPeers.size() == 1)
+ {
+ Contact contact = imCapableCallPeers.get(0);
+ MetaContact metaContact
+ = GuiActivator.getContactListService()
+ .findMetaContactByContact(contact);
+ GuiActivator.getUIService().getChatWindowManager().startChat(
+ metaContact);
+ }
+ }
+ else if (buttonName.equals(INFO_BUTTON))
+ {
+ if (callInfoFrame == null)
+ {
+ callInfoFrame = new CallInfoFrame(callConference);
+ addCallTitleListener(callInfoFrame);
+ }
+ callInfoFrame.setVisible(
+ callInfoFrame.hasCallInfo() && !callInfoFrame.isVisible());
+ }
+ }
+
+ /**
+ * Executes the action associated with the "Hang up" button which may be
+ * invoked by clicking the button in question or by closing this dialog.
+ *
+ * @param closeWait <tt>true</tt> to close this instance with a few seconds
+ * of delay or <tt>false</tt> to close it immediately
+ */
+ public void actionPerformedOnHangupButton(boolean closeWait)
+ {
+ isCloseWaitAfterHangup = closeWait;
+
+ this.disposeCallInfoFrame();
+
+ /*
+ * It is the responsibility of CallManager to close this CallPanel
+ * when a Call is ended.
+ */
+ if (callConference.getCallCount() > 0)
+ CallManager.hangupCalls(callConference);
+ /*
+ * If however there are no more calls related to this panel we will
+ * close the window directly. This could happen in the case, where
+ * the other side has already hanged up the call, the call window shows
+ * the state disconnected and we press the hang up button. In this
+ * case the contained call is already null and we should only close the
+ * call window.
+ */
+ else
+ callWindow.close(this, false);
+ }
+
+ /**
+ * Indicates if this call panel should be closed immediately after hang up
+ * or should wait some time so that the user can be notified of the last
+ * state.
+ *
+ * @return <tt>true</tt> to indicate that when hanged up this call panel
+ * should not be closed immediately, <tt>false</tt> - otherwise
+ */
+ public boolean isCloseWaitAfterHangup()
+ {
+ return isCloseWaitAfterHangup;
+ }
+
+ /**
+ * Adds the given <tt>CallTitleListener</tt> to the list of listeners,
+ * notified for call title changes.
+ *
+ * @param l the <tt>CallTitleListener</tt> to add
+ */
+ public void addCallTitleListener(CallTitleListener l)
+ {
+ synchronized (titleListeners)
+ {
+ titleListeners.add(l);
+ }
+ }
+
+ /**
+ * Adds remote video specific components.
+ *
+ * @param callPeer the <tt>CallPeer</tt>
+ */
+ public void addRemoteVideoSpecificComponents(CallPeer callPeer)
+ {
+ if(CallManager.isVideoQualityPresetSupported(callPeer))
+ {
+ if(resizeVideoButton == null)
+ {
+ resizeVideoButton = new ResizeVideoButton(callPeer.getCall());
+ resizeVideoButton.setIndex(9);
+ }
+
+ if(resizeVideoButton.countAvailableOptions() > 1)
+ {
+ settingsPanel.add(resizeVideoButton);
+ settingsPanel.revalidate();
+ settingsPanel.repaint();
+ }
+ }
+ }
+
+ /**
+ * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
+ * {@link #callWindow}.
+ *
+ * @param ev the <tt>PropertyChangeEvent</tt> fired by <tt>callWindow</tt>
+ * to notify this instance about
+ */
+ private void callWindowPropertyChange(PropertyChangeEvent ev)
+ {
+ /*
+ * We are registered for CallContainer#PROP_FULL_SCREEN only. This
+ * instance will fire the notification as its own to allow listeners to
+ * register with a source which is more similar to them with respect to
+ * life span.
+ */
+ try
+ {
+ if (OSUtils.IS_MAC && (bottomBar != null))
+ bottomBar.setOpaque(!isFullScreen());
+ }
+ finally
+ {
+ firePropertyChange(
+ ev.getPropertyName(),
+ ev.getOldValue(), ev.getNewValue());
+ }
+ }
+
+ /**
+ * Count the number of the buttons in the supplied components.
+ * @param cs the components to search for buttons.
+ * @return number of buttons.
+ */
+ private int countButtons(Component[] cs)
+ {
+ int count = 0;
+
+ for(Component c : cs)
+ {
+ if(c instanceof SIPCommButton || c instanceof SIPCommToggleButton)
+ count++;
+ if(c instanceof Container)
+ count += countButtons(((Container)c).getComponents());
+ }
+
+ return count;
+ }
+
+ /**
+ * Creates the bottom bar panel for this <tt>CallPanel</tt>.
+ *
+ * @return a new bottom bar panel for this <tt>CallPanel</tt>
+ */
+ private JComponent createBottomBar()
+ {
+ bottomBar
+ = new TransparentPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
+ bottomBar.setBorder(BorderFactory.createEmptyBorder(0, 30, 2, 30));
+
+ /*
+ * The bottomBar on OS X overrides the Swing-defined background.
+ * However, full-screen display usually uses a black background. The
+ * black background will be set elsewhere on an ancestor but we have to
+ * make sure that bottomBar's background does not interfere with the
+ * setting.
+ */
+ if (OSUtils.IS_MAC)
+ {
+ bottomBar.setOpaque(!isFullScreen());
+ bottomBar.setBackground(
+ new Color(GuiActivator.getResources().getColor(
+ "service.gui.MAC_PANEL_BACKGROUND")));
+ }
+
+ bottomBar.add(settingsPanel);
+
+ return bottomBar;
+ }
+
+ /**
+ * Releases the resources acquired by this instance which require explicit
+ * disposal (e.g. any listeners added to the depicted
+ * <tt>CallConference</tt>, the participating <tt>Call</tt>s, and their
+ * associated <tt>CallPeer</tt>s). Invoked by <tt>CallManager</tt> when it
+ * determines that this <tt>CallPanel</tt> is no longer necessary.
+ */
+ void dispose()
+ {
+ disposed = true;
+
+ callConference.removeCallChangeListener(callConferenceListener);
+ callConference.removeCallPeerConferenceListener(callConferenceListener);
+ callConference.removePropertyChangeListener(callConferenceListener);
+
+ uiVideoHandler.deleteObserver(uiVideoHandlerObserver);
+ uiVideoHandler.dispose();
+
+ callWindow.getFrame().removePropertyChangeListener(
+ CallContainer.PROP_FULL_SCREEN,
+ callConferenceListener);
+
+ if (callPanel != null)
+ {
+ if(callPanel instanceof BasicConferenceCallPanel)
+ {
+ ((BasicConferenceCallPanel) callPanel)
+ .removePeerViewListener(this);
+ }
+ ((CallRenderer) callPanel).dispose();
+ }
+
+ // clears the contact status listener
+ if(operationSetPresence != null)
+ {
+ operationSetPresence.removeContactPresenceStatusListener(this);
+ }
+ }
+
+ /**
+ * Disposes the call info frame if it exists.
+ */
+ public void disposeCallInfoFrame()
+ {
+ if (callInfoFrame != null)
+ callInfoFrame.dispose();
+ }
+
+ /**
+ * Updates {@link #settingsPanel} from the model of this view. The update is
+ * performed in the AWT event dispatching thread.
+ * <p>
+ * The center of this view is occupied by {@link #callPanel}, the bottom of
+ * this view is dedicated to <tt>settingsPanel</tt>. The method
+ * {@link #updateViewFromModelInEventDispatchThread()} updates
+ * <tt>callPanel</tt> from the model of this view and then invokes the
+ * method <tt>updateSettingsPanelInEventDispatchThread()</tt>. Thus this
+ * whole view is updated so that it depicts the current state of its model.
+ * </p>
+ *
+ * @param callConferenceIsEnded <tt>true</tt> if the method
+ * <tt>updateViewFromModelInEventDispatchThread()</tt> considers the
+ * {@link #callConference} ended; otherwise, <tt>false</tt>. When the
+ * <tt>callConference</tt> is considered ended, the <tt>callPanel</tt>
+ * instance will not be switched to a specific type (one-to-one, audio-only,
+ * or audio/video) because, otherwise, the switch will leave it
+ * <tt>null</tt> and this view will remain blank. In such a case,
+ * <tt>settingsPanel</tt> may wish to do pretty much the same but disable
+ * and/or hide the buttons it contains.
+ */
+ private void doUpdateSettingsPanelInEventDispatchThread(
+ boolean callConferenceIsEnded)
+ {
+ settingsPanel.setFullScreen(isFullScreen());
+
+ boolean isConference = (callPanel instanceof BasicConferenceCallPanel);
+
+ /*
+ * For whatever reason, we're treating the localLevel and the
+ * remoteLevel buttons differently and we're adding and removing them in
+ * accord with the conference state of the user interface.
+ */
+ if (isConference)
+ {
+ settingsPanel.add(localLevel);
+ settingsPanel.add(remoteLevel);
+ }
+ else
+ {
+ settingsPanel.remove(localLevel);
+ settingsPanel.remove(remoteLevel);
+ }
+
+ /*
+ * We do not support chat conferencing with the participants in a
+ * telephony conference at this time so we do not want the chatButton
+ * visible in such a scenario.
+ */
+ List<Contact> imContacts = getIMCapableCallPeers(1);
+ chatButton.setVisible(
+ !isConference && (imContacts.size() == 1));
+ if(chatButton.isVisible() && operationSetPresence == null)
+ {
+ Contact contact = imContacts.get(0);
+ operationSetPresence =
+ contact.getProtocolProvider()
+ .getOperationSet(OperationSetPresence.class);
+ if(operationSetPresence != null)
+ operationSetPresence.addContactPresenceStatusListener(this);
+
+ chatButton.setIconImage(
+ Constants.getMessageStatusIcon(contact.getPresenceStatus()));
+ chatButton.repaint();
+ }
+
+ updateHoldButtonState();
+ updateMergeButtonState();
+
+ List<Call> calls = callConference.getCalls();
+ /*
+ * OperationSetAdvancedTelephony implements call transfer. The feature
+ * is not supported if the local user/peer is a conference focus.
+ * Instead of disabling the transferCallButton in this case though, we
+ * want it hidden.
+ */
+ boolean advancedTelephony = !calls.isEmpty();
+ boolean telephonyConferencing = false;
+ boolean videoTelephony = false;
+ boolean videoTelephonyIsLocalVideoAllowed = false;
+ boolean videoTelephonyIsLocalVideoStreaming = false;
+ boolean desktopSharing = false;
+ boolean desktopSharingIsStreamed = false;
+ boolean allCallsConnected = true;
+
+ for (Call call : calls)
+ {
+ ProtocolProviderService pps = call.getProtocolProvider();
+
+ /*
+ * The transferCallButton requires OperationSetAdvancedTelephony
+ * for all Calls.
+ */
+ if (advancedTelephony)
+ {
+ OperationSetAdvancedTelephony<?> osat
+ = pps.getOperationSet(OperationSetAdvancedTelephony.class);
+
+ if (osat == null)
+ advancedTelephony = false;
+ }
+
+ /*
+ * The conferenceButton needs at least one Call with
+ * OperationSetTelephonyConferencing,
+ */
+ if (!telephonyConferencing)
+ {
+ OperationSetTelephonyConferencing ostc
+ = pps.getOperationSet(
+ OperationSetTelephonyConferencing.class);
+
+ if (ostc != null)
+ telephonyConferencing = true;
+ }
+
+ if (!videoTelephony
+ || !videoTelephonyIsLocalVideoAllowed
+ || !videoTelephonyIsLocalVideoStreaming)
+ {
+ OperationSetVideoTelephony osvt
+ = pps.getOperationSet(OperationSetVideoTelephony.class);
+
+ if (osvt != null)
+ {
+ if (!videoTelephony)
+ videoTelephony = true;
+ if (!videoTelephonyIsLocalVideoAllowed
+ && osvt.isLocalVideoAllowed(call))
+ videoTelephonyIsLocalVideoAllowed = true;
+ if (!videoTelephonyIsLocalVideoStreaming
+ && osvt.isLocalVideoStreaming(call))
+ videoTelephonyIsLocalVideoStreaming = true;
+ }
+ }
+
+ if(!desktopSharing)
+ {
+ OperationSetDesktopStreaming osds
+ = pps.getOperationSet(
+ OperationSetDesktopStreaming.class);
+ if(osds != null)
+ {
+ desktopSharing = true;
+
+ if(videoTelephonyIsLocalVideoStreaming
+ && call instanceof MediaAwareCall
+ && ((MediaAwareCall<?,?,?>) call).getMediaUseCase()
+ == MediaUseCase.DESKTOP)
+ {
+ desktopSharingIsStreamed = true;
+ }
+ }
+ }
+
+ if (CallState.CALL_IN_PROGRESS != call.getCallState())
+ {
+ allCallsConnected = false;
+ }
+ }
+
+ if(conferenceButton != null)
+ conferenceButton.setEnabled(telephonyConferencing);
+
+ if(transferCallButton != null)
+ {
+ transferCallButton.setEnabled(advancedTelephony);
+ transferCallButton.setVisible(!callConference.isConferenceFocus());
+ }
+
+ /*
+ * The videoButton is a beast of its own kind because it depends not
+ * only on the state of the depicted telephony conference but also on
+ * the global application state.
+ */
+ if(videoButton != null)
+ {
+ videoButton.setEnabled(allCallsConnected && videoTelephony);
+ videoButton.setSelected(videoTelephonyIsLocalVideoAllowed);
+
+ /*
+ * Consequently, the showHideVideoButton which depends on videoButton
+ * has to be updated depending on the state of the videoButton as well.
+ */
+ if(showHideVideoButton != null)
+ {
+ showHideVideoButton.setEnabled(
+ videoButton.isEnabled()
+ && videoTelephonyIsLocalVideoAllowed);
+ showHideVideoButton.setSelected(
+ showHideVideoButton.isEnabled()
+ && uiVideoHandler.isLocalVideoVisible());
+ showHideVideoButton.setVisible(showHideVideoButton.isEnabled());
+ }
+ }
+
+ // The desktop sharing button depends on the operation set desktop
+ // sharing server.
+ if(desktopSharingButton != null)
+ {
+ desktopSharingButton.setEnabled(desktopSharing);
+ desktopSharingButton.setSelected(desktopSharingIsStreamed);
+ }
+
+ if (callPanel instanceof OneToOneCallPanel)
+ {
+ OneToOneCallPanel oneToOneCallPanel = (OneToOneCallPanel) callPanel;
+ if(desktopSharingIsStreamed)
+ oneToOneCallPanel.addDesktopSharingComponents();
+ else
+ oneToOneCallPanel.removeDesktopSharingComponents();
+ }
+ }
+
+ /**
+ * Updates this view i.e. <tt>CallPanel</tt> so that it depicts the current
+ * state of its model i.e. <tt>callConference</tt>. The update is performed
+ * in the AWT event dispatching thread.
+ */
+ private void doUpdateViewFromModelInEventDispatchThread()
+ {
+ /*
+ * If the telephony conference depicted by this instance has ended, do
+ * not update the user interface because it will be left blank. It is
+ * CallManager's responsibility to dispose of this CallPanel after its
+ * telephony conference has ended. Additionally, the various types of
+ * callPanel will usually require at least one CallPeer in order to not
+ * be blank. The absence of CallPeers usually indicates that a Call and,
+ * respectively, a telephony conference has ended. So it makes some
+ * sense to skip the update in such cases in order to try to not have
+ * the user interface blank.
+ */
+ if (callConference.isEnded()
+ || (callConference.getCallPeerCount() == 0))
+ {
+ /*
+ * However, the settingsPanel contains buttons which may still need
+ * to be disabled and/or hidden.
+ */
+ updateSettingsPanelInEventDispatchThread(true);
+ return;
+ }
+
+ boolean isConference = isConference();
+ boolean isVideo = CallManager.isVideoStreaming(callConference);
+ CallPeer callPeer = null;
+ boolean validateAndRepaint = false;
+
+ if (callPanel != null)
+ {
+ boolean removeCallPanel;
+
+ if (isConference)
+ {
+ if (callPanel instanceof BasicConferenceCallPanel)
+ {
+ if (isVideo)
+ {
+ removeCallPanel
+ = !(callPanel instanceof VideoConferenceCallPanel);
+ }
+ else
+ {
+ removeCallPanel
+ = (callPanel instanceof VideoConferenceCallPanel);
+ }
+ }
+ else
+ {
+ removeCallPanel = true;
+ }
+ }
+ else
+ {
+ if (callPanel instanceof OneToOneCallPanel)
+ {
+ if (callPeer == null)
+ {
+ List<CallPeer> callPeers
+ = callConference.getCallPeers();
+
+ if (!callPeers.isEmpty())
+ callPeer = callPeers.get(0);
+ }
+ removeCallPanel
+ = !((OneToOneCallPanel) callPanel).getCallPeer().equals(
+ callPeer);
+ }
+ else
+ {
+ if( (callPanel instanceof BasicConferenceCallPanel) &&
+ ((BasicConferenceCallPanel) callPanel)
+ .hasDelayedCallPeers())
+ {
+ removeCallPanel = false;
+ }
+ else
+ {
+ removeCallPanel = true;
+ }
+
+ }
+ }
+ if (removeCallPanel)
+ {
+ remove(callPanel);
+ validateAndRepaint = true;
+ try
+ {
+ ((CallRenderer) callPanel).dispose();
+ }
+ finally
+ {
+ callPanel = null;
+ }
+ }
+ }
+ if (callPanel == null)
+ {
+ if (isConference)
+ {
+ if (isVideo)
+ {
+ callPanel
+ = new VideoConferenceCallPanel(
+ this,
+ callConference,
+ uiVideoHandler);
+ }
+ else
+ {
+ callPanel
+ = new AudioConferenceCallPanel(this, callConference);
+ }
+
+ ((BasicConferenceCallPanel) callPanel)
+ .addPeerViewlListener(this);
+ }
+ else
+ {
+ if (callPeer == null)
+ {
+ List<CallPeer> callPeers = callConference.getCallPeers();
+
+ if (!callPeers.isEmpty())
+ callPeer = callPeers.get(0);
+ }
+ if (callPeer != null)
+ {
+ callPanel
+ = new OneToOneCallPanel(this, callPeer, uiVideoHandler);
+ }
+ }
+ if (callPanel != null)
+ {
+ add(callPanel, BorderLayout.CENTER);
+ validateAndRepaint = true;
+ }
+ }
+
+ try
+ {
+ /*
+ * The center of this view is occupied by callPanel and we have just
+ * updated it. The bottom of this view is dedicated to settingsPanel
+ * so we have to update it as well.
+ */
+ updateSettingsPanelInEventDispatchThread(false);
+ }
+ finally
+ {
+ /*
+ * It seems that AWT/Swing does not validate and/or repaint this
+ * Container (enough) and, consequently, its display may not update
+ * itself with an up-to-date drawing of the current callPanel.
+ */
+ if (validateAndRepaint)
+ {
+ if (isDisplayable())
+ {
+ validate();
+ repaint();
+ }
+ else
+ doLayout();
+ }
+ }
+ }
+
+ /**
+ * Attempts to give a specific <tt>Component</tt> a visible rectangle with a
+ * specific width and a specific height if possible and sane by resizing
+ * the <tt>Window</tt> which contains this instance.
+ *
+ * @param component the <tt>Component</tt> which requests a visible
+ * rectangle with the specified <tt>width</tt> and <tt>height</tt>
+ * @param width the width of the visible rectangle requested by the
+ * specified <tt>component</tt>
+ * @param height the height of the visible rectangle requested by the
+ * specified <tt>component</tt>
+ */
+ private void ensureSize(Component component, int width, int height)
+ {
+ CallContainer callContainer = getCallWindow();
+
+ if (callContainer != null)
+ callContainer.ensureSize(component, width, height);
+ }
+
+ /**
+ * Notifies interested listeners of a call title change.
+ */
+ private void fireTitleChangeEvent()
+ {
+ Iterator<CallTitleListener> listeners;
+
+ synchronized (titleListeners)
+ {
+ listeners = new Vector<CallTitleListener>(titleListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ listeners.next().callTitleChanged(this);
+ }
+ }
+
+ /**
+ * Returns the <tt>CallConference</tt> depicted by this <tt>CallPanel</tt>
+ *
+ * @return the <tt>CallConference</tt> depicted by this
+ * <tt>CallConference</tt>
+ */
+ public CallConference getCallConference()
+ {
+ return callConference;
+ }
+
+ /**
+ * Returns the initial call title. The call title could be then changed by
+ * call setCallTitle.
+ *
+ * @return the call title
+ */
+ public String getCallTitle()
+ {
+ return title;
+ }
+
+ /**
+ * Returns the parent call window.
+ *
+ * @return the parent call window
+ */
+ public CallContainer getCallWindow()
+ {
+ return callWindow;
+ }
+
+ /**
+ * Returns the currently used <tt>CallRenderer</tt>.
+ * @return the currently used <tt>CallRenderer</tt>
+ */
+ public CallRenderer getCurrentCallRenderer()
+ {
+ return (CallRenderer) callPanel;
+ }
+
+ /**
+ * Returns the <tt>DialpadDialog</tt> corresponding to this CallDialog.
+ *
+ * @return the <tt>DialpadDialog</tt> corresponding to this CallDialog.
+ */
+ private DialpadDialog getDialpadDialog()
+ {
+ return new DialpadDialog(dtmfHandler);
+ }
+
+ /**
+ * Finds the <tt>Contact</tt>s which are participating in the telephony
+ * conference depicted by this instance and which are capable of instant
+ * messaging i.e. support {@link OperationSetBasicTelephony}.
+ *
+ * @param limit the maximum number of <tt>Contact</tt>s to be found. Since
+ * it is expensive in terms of execution time (at very least) to find a
+ * <tt>Contact</tt> which stands for a <tt>CallPeer</tt> (and to query it
+ * whether it supports instant messaging), it is advised to limit the search
+ * as much as possible. For example, the <tt>chatButton</tt> is enabled
+ * and/or shown only when there is exactly one such <tt>Contact</tt> so it
+ * makes perfect sense to specify <tt>1</tt> as the <tt>limit</tt> in the
+ * case.
+ * @return a <tt>List</tt> of the <tt>Contact</tt>s which are participating
+ * in the telephony conference depicted by this instance and which are
+ * capable of instant messaging i.e. support
+ * <tt>OperationSetBasicTelephony</tt>
+ */
+ private List<Contact> getIMCapableCallPeers(int limit)
+ {
+ List<CallPeer> callPeers = callConference.getCallPeers();
+ List<Contact> contacts = new ArrayList<Contact>(callPeers.size());
+
+ /*
+ * Choose the CallPeers (or rather their associated Contacts) which are
+ * capable of basic instant messaging.
+ */
+ for (CallPeer callPeer : callPeers)
+ {
+ if (callPeer.getProtocolProvider().getOperationSet(
+ OperationSetBasicInstantMessaging.class)
+ != null)
+ {
+ /*
+ * CallPeer#getContact) is more expensive in terms of execution
+ * than ProtocolProviderService#getOperationSet(Class).
+ */
+ Contact contact = callPeer.getContact();
+
+ if (contact != null)
+ contacts.add(contact);
+ }
+ else
+ {
+ Contact contact = CallManager.getIMCapableCusaxContact(callPeer);
+ if (contact != null)
+ {
+ contacts.add(contact);
+ }
+ }
+
+ if (contacts.size() >= limit)
+ break;
+ }
+ return contacts;
+ }
+
+ /**
+ * Returns the minimum width needed to show buttons.
+ * Used to calculate the minimum size of the call dialog.
+ * @return the minimum width for the buttons.
+ */
+ public int getMinimumButtonWidth()
+ {
+ int numberOfButtons = countButtons(settingsPanel.getComponents());
+
+ if (numberOfButtons > 0)
+ {
+ // +1 cause we had and a hangup button
+ // *32, a button is 28 pixels width and give some border
+ return (numberOfButtons + 1) * 32;
+ }
+ else
+ return -1;
+ }
+
+ /**
+ * Initializes buttons order in the call tool bar.
+ */
+ private void initButtonIndexes()
+ {
+ if (dialButton != null)
+ dialButton.setIndex(0);
+ if (conferenceButton != null)
+ conferenceButton.setIndex(1);
+ if (holdButton != null)
+ holdButton.setIndex(2);
+ if (recordButton != null)
+ recordButton.setIndex(3);
+ if (mergeButton != null)
+ mergeButton.setIndex(4);
+ if (transferCallButton != null)
+ transferCallButton.setIndex(5);
+
+ localLevel.setIndex(6);
+ if (remoteLevel instanceof OrderedComponent)
+ ((OrderedComponent) remoteLevel).setIndex(7);
+
+ if (desktopSharingButton != null)
+ desktopSharingButton.setIndex(8);
+
+ if (fullScreenButton != null)
+ fullScreenButton.setIndex(10);
+
+ if (videoButton != null)
+ videoButton.setIndex(11);
+ if (showHideVideoButton != null)
+ showHideVideoButton.setIndex(12);
+ chatButton.setIndex(19);
+
+ if (infoButton != null)
+ infoButton.setIndex(20);
+
+ hangupButton.setIndex(100);
+ }
+
+ /**
+ * Initialize plug-in components already registered for this container.
+ */
+ private void initPluginComponents()
+ {
+ // Search for plug-in components registered through the OSGI
+ // BundleContext.
+
+ String osgiFilter
+ = "("
+ + net.java.sip.communicator.service.gui.Container.CONTAINER_ID
+ + "="
+ + net.java.sip.communicator.service.gui.Container
+ .CONTAINER_CALL_DIALOG.getID()
+ + ")";
+ ServiceReference[] serRefs = null;
+
+ try
+ {
+ serRefs
+ = GuiActivator.bundleContext.getServiceReferences(
+ PluginComponentFactory.class.getName(),
+ osgiFilter);
+ }
+ catch (InvalidSyntaxException ise)
+ {
+ logger.error("Could not obtain plugin reference.", ise);
+ }
+
+ if (serRefs != null)
+ {
+ for (ServiceReference serRef : serRefs)
+ {
+ PluginComponentFactory factory
+ = (PluginComponentFactory)
+ GuiActivator.bundleContext.getService(serRef);
+
+ PluginComponent component =
+ factory.getPluginComponentInstance(CallPanel.this);
+
+ component.setCurrentContact(
+ CallManager.getPeerMetaContact(
+ callConference.getCallPeers().get(0)));
+
+ settingsPanel.add((Component) component.getComponent());
+ }
+ }
+
+ GuiActivator.getUIService().addPluginComponentListener(this);
+ }
+
+ /**
+ * Initializes the user interface hierarchy of this <tt>CallPanel</tt> i.e.
+ * the AWT <tt>Component</tt>s which constitute the user interface to be
+ * displayed by this <tt>Component</tt>. Their state does not have to depict
+ * the current state of the model of this view because
+ * {@link #updateViewFromModel()} will be invoked before this view becomes
+ * visible. At the center of the user interface of this view is
+ * {@link #callPanel} but it is dynamically added and removed multiple times
+ * as part of the execution of the <tt>updateViewFromModel</tt> method so
+ * it is not dealt with here.
+ */
+ private void initializeUserInterfaceHierarchy()
+ {
+ /*
+ * The settingsPanel will contain the buttons. It is initialized before
+ * the buttons in case any of the buttons need it (which is hard to
+ * determine at the time of this writing).
+ */
+ settingsPanel = new CallToolBar(isFullScreen(), false);
+
+ /*
+ * TODO CallPanel depicts a whole CallConference which may have multiple
+ * Calls, new Calls may be added to the CallConference and existing
+ * Calls may be removed from the CallConference. For example, the
+ * buttons which accept a Call as an argument should be changed to take
+ * into account the whole CallConference.
+ */
+ Call aCall = callConference.getCalls().get(0);
+
+ chatButton
+ = new CallToolBarButton(
+ ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_WHITE),
+ CHAT_BUTTON,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CHAT"));
+
+ if(isButtonEnabled(HIDE_CONFERENCE_BUTON_PROP))
+ {
+ conferenceButton
+ = new CallToolBarButton(
+ ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON),
+ CONFERENCE_BUTTON,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CREATE_CONFERENCE_CALL"));
+ }
+
+ if(isButtonEnabled(HIDE_DESKTOP_SHARING_BUTON_PROP))
+ {
+ desktopSharingButton = new DesktopSharingButton(aCall);
+ }
+
+ if(isButtonEnabled(HIDE_DIAL_BUTON_PROP))
+ {
+ dialButton
+ = new CallToolBarButton(
+ ImageLoader.getImage(ImageLoader.DIAL_BUTTON),
+ DIAL_BUTTON,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.DIALPAD"));
+ }
+
+ if(isButtonEnabled(HIDE_FULL_SCREEN_BUTON_PROP))
+ {
+ fullScreenButton = new FullScreenButton(this);
+ }
+
+ hangupButton = new HangupButton(this);
+
+ if(isButtonEnabled(HIDE_CALL_HOLD_BUTON_PROP))
+ {
+ holdButton = new HoldButton(aCall);
+ }
+
+ if(isButtonEnabled(HIDE_CALL_INFO_BUTON_PROP))
+ {
+ infoButton
+ = new CallToolBarButton(
+ ImageLoader.getImage(ImageLoader.CALL_INFO),
+ INFO_BUTTON,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.PRESS_FOR_CALL_INFO"));
+ }
+
+ if(isButtonEnabled(HIDE_CALL_MERGE_BUTON_PROP))
+ {
+ mergeButton
+ = new CallToolBarButton(
+ ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON),
+ MERGE_BUTTON,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.MERGE_TO_CALL"));
+
+ }
+
+ if(isButtonEnabled(HIDE_CALL_RECORD_BUTON_PROP))
+ {
+ recordButton = new RecordButton(aCall);
+ }
+
+ if(isButtonEnabled(HIDE_TOGGLE_VIDEO_BUTON_PROP))
+ {
+ showHideVideoButton = new ShowHideVideoButton(uiVideoHandler);
+ }
+
+ if(isButtonEnabled(HIDE_CALL_TRANSFER_BUTON_PROP))
+ {
+ transferCallButton = new TransferCallButton(aCall);
+ }
+
+ if(isButtonEnabled(HIDE_VIDEO_BUTON_PROP))
+ {
+ videoButton = new LocalVideoButton(aCall);
+ }
+
+ localLevel
+ = new InputVolumeControlButton(
+ aCall,
+ ImageLoader.MICROPHONE,
+ ImageLoader.MUTE_BUTTON,
+ true,
+ false);
+ remoteLevel
+ = new OutputVolumeControlButton(
+ callConference,
+ ImageLoader.VOLUME_CONTROL_BUTTON,
+ false,
+ true)
+ .getComponent();
+
+ /*
+ * Now that the buttons have been initialized, set their order indexes
+ * so that they get added in the correct order later on.
+ */
+ initButtonIndexes();
+
+ chatButton.addActionListener(this);
+ if (conferenceButton != null)
+ conferenceButton.addActionListener(this);
+ if (dialButton != null)
+ dialButton.addActionListener(this);
+ if (infoButton != null)
+ infoButton.addActionListener(this);
+ if (mergeButton != null)
+ mergeButton.addActionListener(this);
+
+ settingsPanel.add(chatButton);
+ if (conferenceButton != null)
+ settingsPanel.add(conferenceButton);
+ if (desktopSharingButton != null)
+ settingsPanel.add(desktopSharingButton);
+ if (dialButton != null)
+ settingsPanel.add(dialButton);
+ if (fullScreenButton != null)
+ settingsPanel.add(fullScreenButton);
+
+ settingsPanel.add(hangupButton);
+
+ if (holdButton != null)
+ settingsPanel.add(holdButton);
+ if (infoButton != null)
+ settingsPanel.add(infoButton);
+ if (mergeButton != null)
+ settingsPanel.add(mergeButton);
+ if (recordButton != null)
+ settingsPanel.add(recordButton);
+ if (showHideVideoButton != null)
+ settingsPanel.add(showHideVideoButton);
+ if (mergeButton != null)
+ settingsPanel.add(transferCallButton);
+ if (videoButton != null)
+ settingsPanel.add(videoButton);
+
+ // The bottom bar will contain the settingsPanel.
+ add(createBottomBar(), BorderLayout.SOUTH);
+ }
+
+ /**
+ * Tests a provided boolean property name, returning false if it should be
+ * hidden.
+ *
+ * Used in {@link #initializeUserInterfaceHierarchy()}
+ * @param buttonHidePropertyName the name of the boolean property to check.
+ * @return false if the button should be hidden, true otherwise.
+ *
+ */
+ private boolean isButtonEnabled(String buttonHidePropertyName)
+ {
+ return !GuiActivator.getConfigurationService().getBoolean(
+ buttonHidePropertyName,
+ false);
+ }
+
+ /**
+ * Returns <code>true</code> if the call timer has been started, otherwise
+ * returns <code>false</code>.
+ * @return <code>true</code> if the call timer has been started, otherwise
+ * returns <code>false</code>
+ */
+ public boolean isCallTimerStarted()
+ {
+ return isCallTimerStarted;
+ }
+
+ /**
+ * Checks if the contained call is a conference call.
+ *
+ * @return <code>true</code> if the contained <tt>Call</tt> is a conference
+ * call, otherwise - returns <code>false</code>.
+ */
+ boolean isConference()
+ {
+ // If we're the focus of the conference.
+ if (callConference.isConferenceFocus())
+ return true;
+
+ // If one of our peers is a conference focus, we're in a
+ // conference call.
+ List<CallPeer> callPeers = callConference.getCallPeers();
+
+ for (CallPeer callPeer : callPeers)
+ {
+ if (callPeer.isConferenceFocus())
+ return true;
+ }
+
+ // the call can have two peers at the same time and there is no one
+ // is conference focus. This is situation when someone has made an
+ // attended transfer and has transfered us. We have one call with two
+ // peers the one we are talking to and the one we have been transfered
+ // to. And the first one is been hanged up and so the call passes through
+ // conference call focus a moment and than go again to one to one call.
+ return callPeers.size() > 1;
+ }
+
+ /**
+ * Determines whether this view is displayed in full-screen or windowed
+ * mode.
+ *
+ * @return <tt>true</tt> if this view is displayed in full-screen mode or
+ * <tt>false</tt> for windowed mode
+ */
+ boolean isFullScreen()
+ {
+ return callWindow.isFullScreen();
+ }
+
+ /**
+ * Checks whether recording is currently enabled or not, state retrieved
+ * from call record button state.
+ *
+ * @return <tt>true</tt> if the recording is already started, <tt>false</tt>
+ * otherwise
+ */
+ public boolean isRecordingStarted()
+ {
+ if (recordButton == null)
+ return false;
+
+ return recordButton.isSelected();
+ }
+
+ /**
+ * Returns <tt>true</tt> if the show/hide video button is currently selected,
+ * <tt>false</tt> - otherwise.
+ *
+ * @return <tt>true</tt> if the show/hide video button is currently selected,
+ * <tt>false</tt> - otherwise
+ */
+ public boolean isShowHideVideoButtonSelected()
+ {
+ return showHideVideoButton.isSelected();
+ }
+
+ /**
+ * Reloads icons.
+ */
+ public void loadSkin()
+ {
+ if (dialButton != null)
+ {
+ dialButton.setBackgroundImage(
+ ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG));
+ dialButton.setIconImage(
+ ImageLoader.getImage(ImageLoader.DIAL_BUTTON));
+ }
+
+ if (conferenceButton != null)
+ {
+ conferenceButton.setBackgroundImage(
+ ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG));
+ conferenceButton.setIconImage(
+ ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON));
+ }
+
+ if (hangupButton != null)
+ hangupButton.setBackgroundImage(
+ ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG));
+ }
+
+ /**
+ * Notifies this instance about a specific <tt>VideoEvent</tt> which may
+ * warrant {@link #ensureSize(Component, int, int)} to be invoked in order
+ * to try to have the associated visual <tt>Component</tt> displaying video
+ * shown without scaling. The method will execute on the AWT event
+ * dispatching thread because it will be making its judgments based on the
+ * properties of AWT <tt>Component</tt>s.
+ *
+ * @param ev a <tt>VideoEvent</tt> which represents the cause of the
+ * notification and specifies the visual <tt>Component</tt> displaying video
+ * which may need an adjustment of a Frame's size in order to be displayed
+ * without scaling
+ */
+ private void maybeEnsureSize(final VideoEvent ev)
+ {
+ if (!SwingUtilities.isEventDispatchThread())
+ {
+ SwingUtilities.invokeLater(
+ new Runnable()
+ {
+ public void run()
+ {
+ maybeEnsureSize(ev);
+ }
+ });
+ return;
+ }
+
+ if (ev instanceof SizeChangeVideoEvent)
+ {
+ /*
+ * If a visual Component depicting video (streaming between the
+ * local peer/user and the remote peers) changes its size, try to
+ * adjust the size of the Frame which displays it so that it appears
+ * without scaling.
+ */
+ SizeChangeVideoEvent scev = (SizeChangeVideoEvent) ev;
+
+ ensureSize(
+ scev.getVisualComponent(),
+ scev.getWidth(), scev.getHeight());
+ }
+ else if (ev.getType() == VideoEvent.VIDEO_ADDED)
+ {
+ Component video = ev.getVisualComponent();
+
+ if ((video != null)
+ && UIVideoHandler2.isAncestor(this, video)
+ && video.isPreferredSizeSet())
+ {
+ Dimension prefSize = video.getPreferredSize();
+
+ if ((prefSize.height > 0) && (prefSize.width > 0))
+ {
+ Dimension size = video.getSize();
+
+ if ((prefSize.height > size.height)
+ || (prefSize.width > size.width))
+ {
+ ensureSize(
+ video,
+ prefSize.width, prefSize.height);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked by {@link #callConferenceListener} to notify this instance about
+ * an <tt>EventObject</tt> related to the <tt>CallConference</tt> depicted
+ * by this <tt>CallPanel</tt>, the <tt>Call</tt>s participating in it,
+ * the <tt>CallPeer</tt>s associated with them, the
+ * <tt>ConferenceMember</tt>s participating in any telephony conferences
+ * organized by them, etc. In other words, notifies this instance about
+ * any change which may cause an update to be required so that this view
+ * i.e. <tt>CallPanel</tt> depicts the current state of its model i.e.
+ * {@link #callConference}.
+ *
+ * @param ev the <tt>EventObject</tt> this instance is being notified
+ * about.
+ */
+ private void onCallConferenceEventObject(EventObject ev)
+ {
+ /*
+ * The main task is to invoke updateViewFromModel() in order to make
+ * sure that this view depicts the current state of its model.
+ */
+
+ try
+ {
+ /*
+ * However, we seem to be keeping track of the duration of the call
+ * (i.e. the telephony conference) in the user interface. Stop the
+ * Timer which ticks the duration of the call as soon as the
+ * telephony conference depicted by this instance appears to have
+ * ended. The situation will very likely occur when a Call is
+ * removed from the telephony conference or a CallPeer is removed
+ * from a Call.
+ */
+ boolean tryStopCallTimer = false;
+
+ if (ev instanceof CallPeerEvent)
+ {
+ tryStopCallTimer
+ = (CallPeerEvent.CALL_PEER_REMOVED
+ == ((CallPeerEvent) ev).getEventID());
+ }
+ else if (ev instanceof PropertyChangeEvent)
+ {
+ PropertyChangeEvent pcev = (PropertyChangeEvent) ev;
+
+ tryStopCallTimer
+ = (CallConference.CALLS.equals(pcev.getPropertyName())
+ && (pcev.getOldValue() instanceof Call)
+ && (pcev.getNewValue() == null));
+ }
+ if (tryStopCallTimer
+ && (callConference.isEnded()
+ || callConference.getCallPeerCount() == 0))
+ {
+ stopCallTimer();
+ }
+ }
+ finally
+ {
+ updateViewFromModel();
+ }
+ }
+
+ /**
+ * Notifies this <tt>CallPanel</tt> about a specific <tt>CallEvent</tt>
+ * (received by <tt>CallManager</tt>). The source <tt>Call</tt> may or may
+ * not be participating in the telephony conference depicted by this
+ * instance but allows it to update any state which may depend on the
+ * <tt>Call</tt>s which are established application-wide.
+ *
+ * @param ev a <tt>CallEvent</tt> which specifies the <tt>Call</tt> which
+ * caused this instance to be notified and the exact type of the
+ * notification event
+ */
+ void onCallEvent(CallEvent ev)
+ {
+ updateMergeButtonState();
+ }
+
+ /**
+ * Adds/removes the <tt>Component</tt> of the <tt>PluginComponent</tt>
+ * specified by a <tt>PluginComponentEvent</tt> to/from
+ * {@link #settingsPanel} (if it is appropriate for this
+ * <tt>Container</tt>).
+ *
+ * @param ev a <tt>PluginComponentEvent</tt> which specifies the
+ * <tt>PluginComponent</tt> whose <tt>Component</tt> is to be added/removed
+ * to/from {@link #settingsPanel}
+ */
+ protected void onPluginComponentEvent(PluginComponentEvent ev)
+ {
+ PluginComponentFactory pc = ev.getPluginComponentFactory();
+
+ if (pc.getContainer().equals(
+ net.java.sip.communicator.service.gui.Container
+ .CONTAINER_CALL_DIALOG))
+ {
+ PluginComponent plugin =
+ pc.getPluginComponentInstance(CallPanel.this);
+ Component c = (Component)plugin.getComponent();
+ plugin.setCurrentContact(
+ CallManager.getPeerMetaContact(
+ callConference.getCallPeers().get(0)));
+
+ switch (ev.getEventID())
+ {
+ case PluginComponentEvent.PLUGIN_COMPONENT_ADDED:
+ settingsPanel.add(c);
+ break;
+ case PluginComponentEvent.PLUGIN_COMPONENT_REMOVED:
+ settingsPanel.remove(c);
+ break;
+ }
+
+ settingsPanel.revalidate();
+ settingsPanel.repaint();
+ }
+ }
+
+ /**
+ * Indicates that the peer panel was added.
+ *
+ * @param ev the event.
+ */
+ public void peerViewAdded(ConferencePeerViewEvent ev) {}
+
+ /**
+ * Indicates that the peer panel was removed.
+ *
+ * @param ev the event.
+ */
+ public void peerViewRemoved(ConferencePeerViewEvent ev)
+ {
+ updateViewFromModel();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Adds the <tt>Component</tt> of the <tt>PluginComponent</tt> specified by
+ * the <tt>PluginComponentEvent</tt> to {@link #settingsPanel} (if it is
+ * appropriate for this <tt>Container</tt>).
+ */
+ public void pluginComponentAdded(PluginComponentEvent ev)
+ {
+ onPluginComponentEvent(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Removes the <tt>Component</tt> of the <tt>PluginComponent</tt> specified
+ * by the <tt>PluginComponentEvent</tt> from {@link #settingsPanel} (if it
+ * is appropriate for this <tt>Container</tt>).
+ */
+ public void pluginComponentRemoved(PluginComponentEvent ev)
+ {
+ onPluginComponentEvent(ev);
+ }
+
+ /**
+ * Removes the given <tt>CallTitleListener</tt> to the list of listeners,
+ * notified for call title changes.
+ *
+ * @param l the <tt>CallTitleListener</tt> to remove
+ */
+ public void removeCallTitleListener(CallTitleListener l)
+ {
+ synchronized (titleListeners)
+ {
+ titleListeners.remove(l);
+ }
+ }
+
+ /**
+ * Remove remote video specific components.
+ */
+ public void removeRemoteVideoSpecificComponents()
+ {
+ if(resizeVideoButton != null)
+ settingsPanel.remove(resizeVideoButton);
+
+ settingsPanel.revalidate();
+ settingsPanel.repaint();
+ }
+
+ /**
+ * Sets the title of this dialog in accord with a specific time of start of
+ * the telephony call/conference depicted by this <tt>CallPanel</tt>.
+ *
+ * @param startTime the time in milliseconds at which the telephony
+ * call/conference depicted by this <tt>CallPanel</tt> is considered to have
+ * started
+ */
+ private void setCallTitle(long startTime)
+ {
+ StringBuilder title = new StringBuilder();
+
+ if (startTime != 0)
+ {
+ title.append(
+ GuiUtils.formatTime(
+ startTime,
+ System.currentTimeMillis()));
+ title.append(" | ");
+ }
+ else
+ title.append("00:00:00 | ");
+
+ List<CallPeer> callPeers = callConference.getCallPeers();
+
+ if ((callPeers.size() > 0)
+ && (GuiActivator.getUIService().getSingleWindowContainer()
+ != null))
+ {
+ title.append(callPeers.get(0).getDisplayName());
+ }
+ else
+ {
+ title.append(
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CALL"));
+ }
+
+ this.title = title.toString();
+
+ fireTitleChangeEvent();
+ }
+
+ /**
+ * Sets the display of this view to full-screen or windowed mode.
+ *
+ * @param fullScreen <tt>true</tt> to display this view in full-screen mode
+ * or <tt>false</tt> for windowed mode
+ */
+ void setFullScreen(boolean fullScreen)
+ {
+ callWindow.setFullScreen(fullScreen);
+ }
+
+ /**
+ * Selects or unselects the video button in this call dialog.
+ *
+ * @param isSelected indicates if the video button should be selected or not
+ */
+ public void setVideoButtonSelected(boolean isSelected)
+ {
+ if (isSelected && !videoButton.isSelected())
+ videoButton.setSelected(true);
+ else if (!isSelected && videoButton.isSelected())
+ videoButton.setSelected(false);
+ }
+
+ /**
+ * Starts the timer that counts call duration.
+ */
+ public void startCallTimer()
+ {
+ callConferenceStartTime = System.currentTimeMillis();
+ callDurationTimer.start();
+ isCallTimerStarted = true;
+ }
+
+ /**
+ * Stops the timer that counts call duration.
+ */
+ public void stopCallTimer()
+ {
+ this.callDurationTimer.stop();
+ }
+
+ /**
+ * Notifies this instance that {@link #uiVideoHandler} has reported a change
+ * in the video-related information which may warrant an update of this view
+ * from its model.
+ *
+ * @param arg an <tt>Object</tt>, if any, which represents the cause that
+ * triggered the notification
+ */
+ private void uiVideoHandlerUpdate(Object arg)
+ {
+ /* The most important task is to update this view from its model. */
+
+ /*
+ * If a visual Component displaying video is reported to have been
+ * added/prepared/received, we may have to adjust the size of the Frame
+ * displaying this user interface so that the video appears without
+ * scaling.
+ */
+ /*
+ * XXX The following may be making judgments about the user interface
+ * out of the AWT event dispatching thread which is a prerequisite for
+ * unexpected behavior. Anyway, that's the only idea at the time of this
+ * writing.
+ */
+ VideoEvent maybeEnsureSize = null;
+
+ if (arg instanceof VideoEvent)
+ {
+ try
+ {
+ VideoEvent vev = (VideoEvent) arg;
+ int vevType = vev.getType();
+
+ if (vevType == VideoEvent.VIDEO_ADDED)
+ {
+ Component video = vev.getVisualComponent();
+
+ if ((video != null)
+ && !UIVideoHandler2.isAncestor(this, video))
+ {
+ maybeEnsureSize = vev;
+ }
+ }
+ else if (vevType == SizeChangeVideoEvent.VIDEO_SIZE_CHANGE)
+ {
+ /*
+ * If a visual Component depicting video (streaming between
+ * the local peer/user and the remote peers) changes its
+ * size, try to adjust the size of the Frame which displays
+ * it so that it appears without scaling.
+ */
+ maybeEnsureSize = vev;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof ThreadDeath)
+ throw (ThreadDeath) t;
+ else if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "Failed to determine whether it is necessary to"
+ + " adjust a Frame's size in response to a"
+ + " VideoEvent.",
+ t);
+ }
+ }
+ }
+
+ updateViewFromModel();
+
+ if (maybeEnsureSize != null)
+ {
+ /*
+ * The method maybeEnsureSize will execute on the AWT event
+ * dispatching thread.
+ */
+ try
+ {
+ maybeEnsureSize(maybeEnsureSize);
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof ThreadDeath)
+ throw (ThreadDeath) t;
+ else
+ {
+ logger.error(
+ "Failed to adjust a Frame's size"
+ + " in response to a VideoEvent.",
+ t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the state of the general hold button. The hold button is selected
+ * only if all call peers are locally or mutually on hold at the same time.
+ * In all other cases the hold button is unselected.
+ */
+ public void updateHoldButtonState()
+ {
+ // If the hold button has been disabled by its configuration property we
+ // have nothing more to do here.
+ if (holdButton == null)
+ return;
+
+ if(!SwingUtilities.isEventDispatchThread())
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ updateHoldButtonState();
+ }
+ });
+
+ return;
+ }
+
+ List<CallPeer> peers = callConference.getCallPeers();
+ boolean areAllPeersLocallyOnHold;
+
+ if (peers.isEmpty())
+ {
+ /*
+ * It feels natural to not have the holdButton selected when there
+ * are no peers.
+ */
+ areAllPeersLocallyOnHold = false;
+ }
+ else
+ {
+ areAllPeersLocallyOnHold = true;
+ for (CallPeer peer : callConference.getCallPeers())
+ {
+ CallPeerState state = peer.getState();
+
+ // If we have clicked the hold button in a full screen mode
+ // we need to update the state of the call dialog hold button.
+ if (!state.equals(CallPeerState.ON_HOLD_LOCALLY)
+ && !state.equals(CallPeerState.ON_HOLD_MUTUALLY))
+ {
+ areAllPeersLocallyOnHold = false;
+ break;
+ }
+ }
+ }
+
+ // If we have clicked the hold button in a full screen mode or selected
+ // hold of the peer menu in a conference call we need to update the
+ // state of the call dialog hold button.
+ holdButton.setSelected(areAllPeersLocallyOnHold);
+ }
+
+ /**
+ * Updates the <tt>visible</tt> state/property of {@link #mergeButton} if
+ * the merge button is present.
+ */
+ private void updateMergeButtonState()
+ {
+ // If the merge button isn't present, for example if it's hidden by
+ // its configuration property we have nothing more to do here.
+ if (mergeButton == null)
+ return;
+
+ List<CallConference> conferences = new ArrayList<CallConference>();
+ int cpt = 0;
+
+ for (Call call : CallManager.getInProgressCalls())
+ {
+ CallConference conference = call.getConference();
+
+ if (conference == null)
+ cpt++;
+ else if (!conferences.contains(conference))
+ {
+ conferences.add(conference);
+ cpt++;
+ }
+ else
+ continue;
+
+ if (cpt > 1)
+ break;
+ }
+
+ mergeButton.setVisible(cpt > 1);
+ }
+
+ /**
+ * Updates {@link #settingsPanel} from the model of this view. The update is
+ * performed in the AWT event dispatching thread.
+ * <p>
+ * The center of this view is occupied by {@link #callPanel}, the bottom of
+ * this view is dedicated to <tt>settingsPanel</tt>. The method
+ * {@link #updateViewFromModelInEventDispatchThread()} updates
+ * <tt>callPanel</tt> from the model of this view and then invokes the
+ * method <tt>updateSettingsPanelInEventDispatchThread()</tt>. Thus this
+ * whole view is updated so that it depicts the current state of its model.
+ * </p>
+ *
+ * @param callConferenceIsEnded <tt>true</tt> if the method
+ * <tt>updateViewFromModelInEventDispatchThread()</tt> considers the
+ * {@link #callConference} ended; otherwise, <tt>false</tt>. When the
+ * <tt>callConference</tt> is considered ended, the <tt>callPanel</tt>
+ * instance will not be switched to a specific type (one-to-one, audio-only,
+ * or audio/video) because, otherwise, the switch will leave it
+ * <tt>null</tt> and this view will remain blank. In such a case,
+ * <tt>settingsPanel</tt> may wish to do pretty much the same but disable
+ * and/or hide the buttons it contains.
+ */
+ private void updateSettingsPanelInEventDispatchThread(
+ boolean callConferenceIsEnded)
+ {
+ /*
+ * XXX The method directly delegates to the method
+ * doUpdateSettingsPanelInEventDispatchThread at the time of this
+ * writing which may be considered a waste. But in the fashion of the
+ * method updateViewFromModelInEventDispatchThread we have made it easy
+ * to add code before and/or after the invocation of the delegate.
+ */
+ doUpdateSettingsPanelInEventDispatchThread(callConferenceIsEnded);
+ }
+
+ /**
+ * Updates this view i.e. <tt>CallPanel</tt> so that it depicts the current
+ * state of its model i.e. <tt>callConference</tt>.
+ */
+ private void updateViewFromModel()
+ {
+ /*
+ * We receive events/notifications from various threads and we respond
+ * to them in the AWT event dispatching thread. It is possible to first
+ * schedule an event to be brought to the AWT event dispatching thread,
+ * then to have #dispose() invoked on this instance and, finally, to
+ * receive the scheduled event in the AWT event dispatching thread. In
+ * such a case, this disposed instance should not respond to the event.
+ */
+ if (!disposed)
+ {
+ if (SwingUtilities.isEventDispatchThread())
+ updateViewFromModelInEventDispatchThread();
+ else
+ {
+ SwingUtilities.invokeLater(
+ updateViewFromModelInEventDispatchThread);
+ }
+ }
+ }
+
+ /**
+ * Updates this view i.e. <tt>CallPanel</tt> so that it depicts the current
+ * state of its model i.e. <tt>callConference</tt>. The update is performed
+ * in the AWT event dispatching thread.
+ */
+ private void updateViewFromModelInEventDispatchThread()
+ {
+ /*
+ * We receive events/notifications from various threads and we respond
+ * to them in the AWT event dispatching thread. It is possible to first
+ * schedule an event to be brought to the AWT event dispatching thread,
+ * then to have #dispose() invoked on this instance and, finally, to
+ * receive the scheduled event in the AWT event dispatching thread. In
+ * such a case, this disposed instance should not respond to the event.
+ */
+ if (disposed)
+ return;
+
+ /*
+ * We may add, remove, show, and hide various Components of the user
+ * interface hierarchy of this instance bellow. Consequently, this view
+ * may become larger in width and/or height than its current Frame has
+ * dedicated to it. Try to detect such cases and attempt to adjust the
+ * Frame's size accordingly.
+ */
+ Dimension oldPrefSize = getPreferredSize();
+
+ doUpdateViewFromModelInEventDispatchThread();
+
+ /*
+ * We may have added, removed, shown, and hidden various Components of
+ * the user interface hierarchy of this instance above. Consequently,
+ * this view may have become larger in width and/or height than its
+ * current Frame has dedicated to it. Try to detect such cases and
+ * attempt to adjust the Frame's size accordingly.
+ */
+ Dimension newPrefSize = getPreferredSize();
+
+ if ((newPrefSize != null)
+ && ((newPrefSize.height > getHeight())
+ || (newPrefSize.width > getWidth())))
+ {
+ int oldPrefHeight, oldPrefWidth;
+
+ if (oldPrefSize == null)
+ {
+ oldPrefHeight = 0;
+ oldPrefWidth = 0;
+ }
+ else
+ {
+ oldPrefHeight = oldPrefSize.height;
+ oldPrefWidth = oldPrefSize.width;
+ }
+ if ((newPrefSize.height != oldPrefHeight)
+ || (newPrefSize.width != oldPrefWidth))
+ {
+ ensureSize(
+ this,
+ newPrefSize.width, newPrefSize.height);
+ }
+ }
+ }
+
+ /**
+ * Listens for contact status changes and updates the image of the
+ * chat message button.
+ * @param evt the ContactPresenceStatusChangeEvent describing the status
+ */
+ @Override
+ public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt)
+ {
+ Contact contact = getIMCapableCallPeers(1).get(0);
+
+ if(contact != null && contact.equals(evt.getSourceContact()))
+ {
+ chatButton.setIconImage(
+ Constants.getMessageStatusIcon(contact.getPresenceStatus()));
+ chatButton.repaint();
+ }
+ }
+
+ /**
+ * Implements the listener which listens to events fired by the
+ * <tt>CallConference</tt> depicted by this instance, the <tt>Call</tt>s
+ * participating in that telephony conference, the <tt>CallPeer</tt>s
+ * associated with those <tt>Call</tt>s and the <tt>ConferenceMember</tt>s
+ * participating in the telephony conferences organized by those
+ * <tt>CallPeer</tt>s. Updates this view i.e. CallPanel so that it depicts
+ * the current state of its model i.e. {@link #callConference}.
+ */
+ private class CallConferenceListener
+ extends CallPeerConferenceAdapter
+ implements CallChangeListener,
+ PropertyChangeListener
+ {
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the
+ * <tt>CallPeerEvent</tt> allows distinguishing whether a
+ * <tt>CallPeer</tt> was added or removed by examining its
+ * <tt>eventID</tt>.
+ */
+ public void callPeerAdded(CallPeerEvent ev)
+ {
+ onCallPeerEvent(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the
+ * <tt>CallPeerEvent</tt> allows distinguishing whether a
+ * <tt>CallPeer</tt> was added or removed by examining its
+ * <tt>eventID</tt>.
+ */
+ public void callPeerRemoved(CallPeerEvent ev)
+ {
+ onCallPeerEvent(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #onEventObject(EventObject)}.
+ */
+ public void callStateChanged(CallChangeEvent ev)
+ {
+ onEventObject(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #onEventObject(EventObject)}.
+ */
+ @Override
+ protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
+ {
+ onEventObject(ev);
+ }
+
+ /**
+ * Notifies this <tt>CallChangeListener</tt> about a specific
+ * <tt>CallPeerEvent</tt> i.e. that a <tt>CallPeer</tt> was added to or
+ * removed from a <tt>Call</tt>. Invokes
+ * {@link #onEventObject(EventObject)}.
+ *
+ * @param ev the <tt>CallPeerEvent</tt> to notify this
+ * <tt>CallChangeListener</tt> about i.e. which specifies the
+ * <tt>CallPeer</tt> which was added/removed and the <tt>Call</tt>
+ * to/from which it was added/removed
+ */
+ private void onCallPeerEvent(CallPeerEvent ev)
+ {
+ onEventObject(ev);
+ }
+
+ /**
+ * Invoked by the various listener method implementations provided by
+ * this <tt>CallConferenceListener</tt> to notify this instance about an
+ * <tt>EventObject</tt> related to the <tt>CallConference</tt> depicted
+ * by this <tt>CallPanel</tt>, the <tt>Call</tt>s participating in it,
+ * the <tt>CallPeer</tt>s associated with them, the
+ * <tt>ConferenceMember</tt>s participating in any telephony conferences
+ * organized by them, etc. In other words, notifies this instance about
+ * any change which may cause an update to be required so that this view
+ * i.e. <tt>CallPanel</tt> depicts the current state of its model i.e.
+ * {@link CallPanel#callConference}.
+ *
+ * @param ev the <tt>EventObject</tt> this instance is being notified
+ * about.
+ */
+ private void onEventObject(EventObject ev)
+ {
+ onCallConferenceEventObject(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #onEventObject(EventObject)}.
+ */
+ public void propertyChange(PropertyChangeEvent ev)
+ {
+ String propertyName = ev.getPropertyName();
+
+ /*
+ * If a Call is added to or removed from the CallConference depicted
+ * by this CallPanel, an update of the view from its model will most
+ * likely be required.
+ */
+ if (propertyName.equals(CallConference.CALLS))
+ {
+ onEventObject(ev);
+ }
+ else if (propertyName.equals(CallContainer.PROP_FULL_SCREEN))
+ {
+ if (ev.getSource().equals(callWindow.getFrame()))
+ {
+ try
+ {
+ /*
+ * We'll turn the switching between full-screen and
+ * windowed mode into a model state because a
+ * significant part of this view changes upon such a
+ * switch.
+ */
+ onEventObject(ev);
+ }
+ finally
+ {
+ callWindowPropertyChange(ev);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java
index 01f316c..68e55b8 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java
@@ -1,279 +1,279 @@
-/*
- * 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.gui.main.call;
-
-import java.awt.datatransfer.*;
-import java.awt.im.*;
-import java.io.*;
-import java.util.*;
-
-import javax.swing.*;
-
-import org.jitsi.service.resources.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * A <tt>TransferHandler</tt> that handles dropping of <tt>UIContact</tt>s or
- * <tt>String</tt> addresses on a <tt>CallConference</tt>. Dropping such data on
- * the <tt>CallDialog</tt> will turn a one-to-one <tt>Call</tt> into a telephony
- * conference.
- *
- * @author Yana Stamcheva
- */
-public class CallTransferHandler
- extends ExtendedTransferHandler
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * The data flavor used when transferring <tt>UIContact</tt>s.
- */
- protected static final DataFlavor uiContactDataFlavor
- = new DataFlavor(UIContact.class, "UIContact");
-
- /**
- * The logger.
- */
- private static final Logger logger
- = Logger.getLogger(CallTransferHandler.class);
-
- /**
- * The <tt>CallConference</tt> into which the dropped callees are to be
- * invited.
- */
- private final CallConference callConference;
-
- /**
- * Initializes a new <tt>CallTransferHandler</tt> instance which is to
- * invite dropped callees to a telephony conference specified by a specific
- * <tt>Call</tt> which participates in it.
- *
- * @param call the <tt>Call</tt> which specifies the telephony conference to
- * which dropped callees are to be invited
- */
- public CallTransferHandler(Call call)
- {
- this(call.getConference());
- }
-
- /**
- * Initializes a new <tt>CallTransferHandler</tt> instance which is to
- * invite dropped callees to a specific <tt>CallConference</tt>.
- *
- * @param callConference the <tt>CallConference</tt> to which dropped
- * callees are to be invited
- */
- public CallTransferHandler(CallConference callConference)
- {
- this.callConference = callConference;
- }
-
- /**
- * Indicates whether a component will accept an import of the given
- * set of data flavors prior to actually attempting to import it. We return
- * <tt>true</tt> to indicate that the transfer with at least one of the
- * given flavors would work and <tt>false</tt> to reject the transfer.
- * <p>
- * @param comp component
- * @param flavor the data formats available
- * @return true if the data can be inserted into the component, false
- * otherwise
- * @throws NullPointerException if <code>support</code> is {@code null}
- */
- @Override
- public boolean canImport(JComponent comp, DataFlavor[] flavor)
- {
- for (DataFlavor f : flavor)
- {
- if (f.equals(DataFlavor.stringFlavor)
- || f.equals(uiContactDataFlavor))
- {
- return (comp instanceof JPanel);
- }
- }
- return false;
- }
-
- /**
- * Handles transfers to the chat panel from the clip board or a
- * DND drop operation. The <tt>Transferable</tt> parameter contains the
- * data that needs to be imported.
- * <p>
- * @param comp the component to receive the transfer;
- * @param t the data to import
- * @return true if the data was inserted into the component and false
- * otherwise
- */
- @Override
- public boolean importData(JComponent comp, Transferable t)
- {
- String callee = null;
- ProtocolProviderService provider = null;
-
- if (t.isDataFlavorSupported(uiContactDataFlavor))
- {
- Object o = null;
-
- try
- {
- o = t.getTransferData(uiContactDataFlavor);
- }
- catch (UnsupportedFlavorException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop meta contact.", e);
- }
- catch (IOException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop meta contact.", e);
- }
-
- if (o instanceof ContactNode)
- {
- UIContact uiContact = ((ContactNode) o).getContactDescriptor();
- Iterator<UIContactDetail> contactDetails
- = uiContact
- .getContactDetailsForOperationSet(
- OperationSetBasicTelephony.class)
- .iterator();
-
- while (contactDetails.hasNext())
- {
- UIContactDetail detail = contactDetails.next();
- ProtocolProviderService detailProvider
- = detail.getPreferredProtocolProvider(
- OperationSetBasicTelephony.class);
-
- if (detailProvider != null)
- {
- /*
- * Currently for videobridge conferences we only support
- * adding contacts via the account with the videobridge
- */
- if (callConference.isJitsiVideoBridge())
- {
- for (Call call : callConference.getCalls())
- {
- if (detailProvider == call.getProtocolProvider())
- {
- callee = detail.getAddress();
- provider = detailProvider;
- break;
- }
- }
- }
- else
- {
- callee = detail.getAddress();
- provider = detailProvider;
- break;
- }
- }
- }
-
- if (callee == null)
- {
- /*
- * It turns out that the error message to be reported would
- * like to display information about the account which could
- * not add the dropped callee to the telephony conference.
- * Unfortunately, a telephony conference may have multiple
- * accounts involved. Anyway, choose the first account
- * involved in the telephony conference.
- */
- ProtocolProviderService callProvider
- = callConference.getCalls().get(0)
- .getProtocolProvider();
-
- ResourceManagementService resources
- = GuiActivator.getResources();
- AccountID accountID = callProvider.getAccountID();
-
- new ErrorDialog(null,
- resources.getI18NString("service.gui.ERROR"),
- resources.getI18NString(
- "service.gui.CALL_NOT_SUPPORTING_PARTICIPANT",
- new String[]
- {
- accountID.getService(),
- accountID.getUserID(),
- uiContact.getDisplayName()
- }))
- .showDialog();
- }
- }
- }
- else if (t.isDataFlavorSupported(DataFlavor.stringFlavor))
- {
- InputContext inputContext = comp.getInputContext();
-
- if (inputContext != null)
- inputContext.endComposition();
-
- try
- {
- BufferedReader reader
- = new BufferedReader(
- DataFlavor.stringFlavor.getReaderForText(t));
-
- try
- {
- String line;
- StringBuilder calleeBuilder = new StringBuilder();
-
- while ((line = reader.readLine()) != null)
- calleeBuilder.append(line);
-
- callee = calleeBuilder.toString();
- /*
- * The value of the local variable provider will be null
- * because we have a String only and hence we have no
- * associated ProtocolProviderService.
- * CallManager.inviteToConferenceCall will accept it.
- */
- }
- finally
- {
- reader.close();
- }
- }
- catch (UnsupportedFlavorException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop string.", e);
- }
- catch (IOException e)
- {
- if (logger.isDebugEnabled())
- logger.debug("Failed to drop string.", e);
- }
- }
-
- if (callee == null)
- return false;
- else
- {
- Map<ProtocolProviderService, List<String>> callees
- = new HashMap<ProtocolProviderService, List<String>>();
-
- callees.put(provider, Arrays.asList(callee));
- CallManager.inviteToConferenceCall(callees, callConference);
-
- return true;
- }
- }
-}
+/*
+ * 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.gui.main.call;
+
+import java.awt.datatransfer.*;
+import java.awt.im.*;
+import java.io.*;
+import java.util.*;
+
+import javax.swing.*;
+
+import org.jitsi.service.resources.*;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.impl.gui.main.contactlist.*;
+import net.java.sip.communicator.plugin.desktoputil.*;
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * A <tt>TransferHandler</tt> that handles dropping of <tt>UIContact</tt>s or
+ * <tt>String</tt> addresses on a <tt>CallConference</tt>. Dropping such data on
+ * the <tt>CallDialog</tt> will turn a one-to-one <tt>Call</tt> into a telephony
+ * conference.
+ *
+ * @author Yana Stamcheva
+ */
+public class CallTransferHandler
+ extends ExtendedTransferHandler
+{
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * The data flavor used when transferring <tt>UIContact</tt>s.
+ */
+ protected static final DataFlavor uiContactDataFlavor
+ = new DataFlavor(UIContact.class, "UIContact");
+
+ /**
+ * The logger.
+ */
+ private static final Logger logger
+ = Logger.getLogger(CallTransferHandler.class);
+
+ /**
+ * The <tt>CallConference</tt> into which the dropped callees are to be
+ * invited.
+ */
+ private final CallConference callConference;
+
+ /**
+ * Initializes a new <tt>CallTransferHandler</tt> instance which is to
+ * invite dropped callees to a telephony conference specified by a specific
+ * <tt>Call</tt> which participates in it.
+ *
+ * @param call the <tt>Call</tt> which specifies the telephony conference to
+ * which dropped callees are to be invited
+ */
+ public CallTransferHandler(Call call)
+ {
+ this(call.getConference());
+ }
+
+ /**
+ * Initializes a new <tt>CallTransferHandler</tt> instance which is to
+ * invite dropped callees to a specific <tt>CallConference</tt>.
+ *
+ * @param callConference the <tt>CallConference</tt> to which dropped
+ * callees are to be invited
+ */
+ public CallTransferHandler(CallConference callConference)
+ {
+ this.callConference = callConference;
+ }
+
+ /**
+ * Indicates whether a component will accept an import of the given
+ * set of data flavors prior to actually attempting to import it. We return
+ * <tt>true</tt> to indicate that the transfer with at least one of the
+ * given flavors would work and <tt>false</tt> to reject the transfer.
+ * <p>
+ * @param comp component
+ * @param flavor the data formats available
+ * @return true if the data can be inserted into the component, false
+ * otherwise
+ * @throws NullPointerException if <code>support</code> is {@code null}
+ */
+ @Override
+ public boolean canImport(JComponent comp, DataFlavor[] flavor)
+ {
+ for (DataFlavor f : flavor)
+ {
+ if (f.equals(DataFlavor.stringFlavor)
+ || f.equals(uiContactDataFlavor))
+ {
+ return (comp instanceof JPanel);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Handles transfers to the chat panel from the clip board or a
+ * DND drop operation. The <tt>Transferable</tt> parameter contains the
+ * data that needs to be imported.
+ * <p>
+ * @param comp the component to receive the transfer;
+ * @param t the data to import
+ * @return true if the data was inserted into the component and false
+ * otherwise
+ */
+ @Override
+ public boolean importData(JComponent comp, Transferable t)
+ {
+ String callee = null;
+ ProtocolProviderService provider = null;
+
+ if (t.isDataFlavorSupported(uiContactDataFlavor))
+ {
+ Object o = null;
+
+ try
+ {
+ o = t.getTransferData(uiContactDataFlavor);
+ }
+ catch (UnsupportedFlavorException e)
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Failed to drop meta contact.", e);
+ }
+ catch (IOException e)
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Failed to drop meta contact.", e);
+ }
+
+ if (o instanceof ContactNode)
+ {
+ UIContact uiContact = ((ContactNode) o).getContactDescriptor();
+ Iterator<UIContactDetail> contactDetails
+ = uiContact
+ .getContactDetailsForOperationSet(
+ OperationSetBasicTelephony.class)
+ .iterator();
+
+ while (contactDetails.hasNext())
+ {
+ UIContactDetail detail = contactDetails.next();
+ ProtocolProviderService detailProvider
+ = detail.getPreferredProtocolProvider(
+ OperationSetBasicTelephony.class);
+
+ if (detailProvider != null)
+ {
+ /*
+ * Currently for videobridge conferences we only support
+ * adding contacts via the account with the videobridge
+ */
+ if (callConference.isJitsiVideobridge())
+ {
+ for (Call call : callConference.getCalls())
+ {
+ if (detailProvider == call.getProtocolProvider())
+ {
+ callee = detail.getAddress();
+ provider = detailProvider;
+ break;
+ }
+ }
+ }
+ else
+ {
+ callee = detail.getAddress();
+ provider = detailProvider;
+ break;
+ }
+ }
+ }
+
+ if (callee == null)
+ {
+ /*
+ * It turns out that the error message to be reported would
+ * like to display information about the account which could
+ * not add the dropped callee to the telephony conference.
+ * Unfortunately, a telephony conference may have multiple
+ * accounts involved. Anyway, choose the first account
+ * involved in the telephony conference.
+ */
+ ProtocolProviderService callProvider
+ = callConference.getCalls().get(0)
+ .getProtocolProvider();
+
+ ResourceManagementService resources
+ = GuiActivator.getResources();
+ AccountID accountID = callProvider.getAccountID();
+
+ new ErrorDialog(null,
+ resources.getI18NString("service.gui.ERROR"),
+ resources.getI18NString(
+ "service.gui.CALL_NOT_SUPPORTING_PARTICIPANT",
+ new String[]
+ {
+ accountID.getService(),
+ accountID.getUserID(),
+ uiContact.getDisplayName()
+ }))
+ .showDialog();
+ }
+ }
+ }
+ else if (t.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ InputContext inputContext = comp.getInputContext();
+
+ if (inputContext != null)
+ inputContext.endComposition();
+
+ try
+ {
+ BufferedReader reader
+ = new BufferedReader(
+ DataFlavor.stringFlavor.getReaderForText(t));
+
+ try
+ {
+ String line;
+ StringBuilder calleeBuilder = new StringBuilder();
+
+ while ((line = reader.readLine()) != null)
+ calleeBuilder.append(line);
+
+ callee = calleeBuilder.toString();
+ /*
+ * The value of the local variable provider will be null
+ * because we have a String only and hence we have no
+ * associated ProtocolProviderService.
+ * CallManager.inviteToConferenceCall will accept it.
+ */
+ }
+ finally
+ {
+ reader.close();
+ }
+ }
+ catch (UnsupportedFlavorException e)
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Failed to drop string.", e);
+ }
+ catch (IOException e)
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Failed to drop string.", e);
+ }
+ }
+
+ if (callee == null)
+ return false;
+ else
+ {
+ Map<ProtocolProviderService, List<String>> callees
+ = new HashMap<ProtocolProviderService, List<String>>();
+
+ callees.put(provider, Arrays.asList(callee));
+ CallManager.inviteToConferenceCall(callees, callConference);
+
+ return true;
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java
index a79ae91..82cfdde 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceInviteDialog.java
@@ -1,573 +1,573 @@
-/*
- * 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.gui.main.call.conference;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-import java.util.List;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.*;
-import net.java.sip.communicator.impl.gui.main.call.*;
-import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.service.contactsource.*;
-import net.java.sip.communicator.service.gui.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-/**
- * The invite dialog is the one shown when the user clicks on the conference
- * button in the chat toolbar.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class ConferenceInviteDialog
- extends InviteDialog
-{
- /**
- * Serial version UID.
- */
- private static final long serialVersionUID = 0L;
-
- /**
- * The account selector box.
- */
- private final JComboBox accountSelectorBox = new JComboBox();
-
- /**
- * The last selected account.
- */
- private Object lastSelectedAccount;
-
- /**
- * The telephony conference into which this instance is to invite
- * participants.
- */
- private final CallConference conference;
-
- /**
- * The current provider contact source.
- */
- private ContactSourceService currentProviderContactSource;
-
- /**
- * The current string contact source.
- */
- private ContactSourceService currentStringContactSource;
-
- /**
- * The previously selected protocol provider, with which this dialog has
- * been instantiated.
- */
- private ProtocolProviderService preselectedProtocolProvider;
-
- /**
- * Indicates if this conference invite dialog is associated with a video
- * bridge invite.
- */
- private final boolean isVideoBridge;
-
- /**
- * Initializes a new <tt>ConferenceInviteDialog</tt> instance which is to
- * invite contacts/participants in a specific telephony conference.
- *
- * @param conference the telephony conference in which the new instance is
- * to invite contacts/participants
- */
- public ConferenceInviteDialog(
- CallConference conference,
- ProtocolProviderService preselectedProvider,
- List<ProtocolProviderService> protocolProviders,
- final boolean isVideoBridge)
- {
- // Set the correct dialog title depending if we're going to create a
- // video bridge conference call
- super((isVideoBridge
- ? GuiActivator.getResources()
- .getI18NString("service.gui.INVITE_CONTACT_TO_VIDEO_BRIDGE")
- : GuiActivator.getResources()
- .getI18NString("service.gui.INVITE_CONTACT_TO_CALL")),
- false);
-
- this.conference = conference;
- this.preselectedProtocolProvider = preselectedProvider;
- this.isVideoBridge = isVideoBridge;
-
- if (preselectedProtocolProvider == null)
- initAccountSelectorPanel(protocolProviders);
-
- // init the list, as we check whether features are supported
- // it may take some time if we have too much contacts
- SwingUtilities.invokeLater(new Runnable()
- {
- public void run()
- {
- initContactSources();
-
- // Initialize the list of contacts to select from.
- if (preselectedProtocolProvider != null)
- initContactListData(preselectedProtocolProvider);
- else
- initContactListData(
- (ProtocolProviderService) accountSelectorBox
- .getSelectedItem());
- }
- });
-
- this.addInviteButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- Collection<UIContact> selectedContacts
- = destContactList.getContacts(null);
-
- if (selectedContacts != null && selectedContacts.size() > 0)
- {
- if (preselectedProtocolProvider == null)
- preselectedProtocolProvider
- = (ProtocolProviderService) accountSelectorBox
- .getSelectedItem();
-
- if (isVideoBridge)
- inviteVideoBridgeContacts( preselectedProtocolProvider,
- selectedContacts);
- else
- inviteContacts(selectedContacts);
-
- // Store the last used account in order to pre-select it
- // next time.
- ConfigurationUtils.setLastCallConferenceProvider(
- preselectedProtocolProvider);
-
- dispose();
- }
- else
- {
- // TODO: The underlying invite dialog should show a message
- // to the user that she should select at least two contacts
- // in order to create a conference.
- }
- }
- });
-
- this.addCancelButtonListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- dispose();
- }
- });
- }
-
- /**
- * Constructs the <tt>ConferenceInviteDialog</tt>.
- */
- public ConferenceInviteDialog()
- {
- this(null, null, null, false);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an
- * already created conference. To use when inviting contacts to an existing
- * conference is needed.
- *
- * @param conference the existing <tt>CallConference</tt>
- */
- public ConferenceInviteDialog(CallConference conference)
- {
- this(conference, null, null, false);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an
- * already created conference. To use when inviting contacts to an existing
- * conference is needed.
- *
- * @param conference the existing <tt>CallConference</tt>
- */
- public ConferenceInviteDialog(
- CallConference conference,
- ProtocolProviderService preselectedProtocolProvider,
- boolean isVideoBridge)
- {
- this(conference, preselectedProtocolProvider, null, isVideoBridge);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a
- * preselected protocol provider to be used and if this is an invite for
- * a video bridge conference.
- *
- * @param selectedConfProvider the preselected protocol provider
- * @param isVideoBridge indicates if this dialog should create a conference
- * through a video bridge
- */
- public ConferenceInviteDialog(
- List<ProtocolProviderService> protocolProviders,
- boolean isVideoBridge)
- {
- this(null, null, protocolProviders, isVideoBridge);
- }
-
- /**
- * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a
- * preselected protocol provider to be used and if this is an invite for
- * a video bridge conference.
- *
- * @param selectedConfProvider the preselected protocol provider
- * @param isVideoBridge indicates if this dialog should create a conference
- * through a video bridge
- */
- public ConferenceInviteDialog(
- ProtocolProviderService selectedConfProvider,
- boolean isVideoBridge)
- {
- this(null, selectedConfProvider, null, isVideoBridge);
- }
-
- /**
- * Initializes the account selector panel.
- *
- * @param protocolProviders the list of protocol providers we'd like to
- * show in the account selector box
- */
- private void initAccountSelectorPanel(
- List<ProtocolProviderService> protocolProviders)
- {
- JLabel accountSelectorLabel = new JLabel(
- GuiActivator.getResources().getI18NString("service.gui.CALL_VIA"));
-
- TransparentPanel accountSelectorPanel
- = new TransparentPanel(new BorderLayout());
-
- accountSelectorPanel.setBorder(
- BorderFactory.createEmptyBorder(5, 5, 5, 5));
- accountSelectorPanel.add(accountSelectorLabel, BorderLayout.WEST);
- accountSelectorPanel.add(accountSelectorBox, BorderLayout.CENTER);
-
- // Initialize the account selector box.
- if (protocolProviders != null && protocolProviders.size() > 0)
- this.initAccountListData(protocolProviders);
- else
- this.initAccountListData();
-
- this.accountSelectorBox.setRenderer(new DefaultListCellRenderer()
- {
- private static final long serialVersionUID = 0L;
-
- @Override
- public Component getListCellRendererComponent(JList list,
- Object value, int index, boolean isSelected,
- boolean cellHasFocus)
- {
- ProtocolProviderService protocolProvider
- = (ProtocolProviderService) value;
-
- if (protocolProvider != null)
- {
- this.setText(
- protocolProvider.getAccountID().getDisplayName());
- this.setIcon(
- ImageLoader.getAccountStatusImage(protocolProvider));
- }
-
- return this;
- }
- });
-
- this.accountSelectorBox.addActionListener(new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- Object accountSelectorBoxSelectedItem
- = accountSelectorBox.getSelectedItem();
-
- if (lastSelectedAccount == null
- || !lastSelectedAccount
- .equals(accountSelectorBoxSelectedItem))
- {
- lastSelectedAccount = accountSelectorBoxSelectedItem;
-
- initContactListData(
- (ProtocolProviderService) accountSelectorBox
- .getSelectedItem());
-
- if (isVideoBridge)
- destContactList.removeAll();
- }
- }
- });
-
- this.getContentPane().add(accountSelectorPanel, BorderLayout.NORTH);
- }
-
- /**
- * Initializes the account selector box with the given list of
- * <tt>ProtocolProviderService</tt>-s.
- *
- * @param protocolProviders the list of <tt>ProtocolProviderService</tt>-s
- * we'd like to show in the account selector box
- */
- private void initAccountListData(
- List<ProtocolProviderService> protocolProviders)
- {
- Iterator<ProtocolProviderService> providersIter
- = protocolProviders.iterator();
-
- while (providersIter.hasNext())
- {
- ProtocolProviderService protocolProvider
- = providersIter.next();
-
- accountSelectorBox.addItem(protocolProvider);
- }
-
- if (accountSelectorBox.getItemCount() > 0)
- accountSelectorBox.setSelectedIndex(0);
- }
-
- /**
- * Initializes the account list.
- */
- private void initAccountListData()
- {
- Iterator<ProtocolProviderService> protocolProviders
- = GuiActivator.getUIService().getMainFrame().getProtocolProviders();
-
- while(protocolProviders.hasNext())
- {
- ProtocolProviderService protocolProvider
- = protocolProviders.next();
- OperationSet opSet
- = protocolProvider.getOperationSet(
- OperationSetTelephonyConferencing.class);
-
- if ((opSet != null) && protocolProvider.isRegistered())
- accountSelectorBox.addItem(protocolProvider);
- }
-
- // Try to select the last used account if available.
- ProtocolProviderService pps
- = ConfigurationUtils.getLastCallConferenceProvider();
-
- if (pps == null && conference != null)
- {
- /*
- * Pick up the first account from the ones participating in the
- * associated telephony conference which supports
- * OperationSetTelephonyConferencing.
- */
- for (Call call : conference.getCalls())
- {
- ProtocolProviderService callPps = call.getProtocolProvider();
-
- if (callPps.getOperationSet(
- OperationSetTelephonyConferencing.class)
- != null)
- {
- pps = callPps;
- break;
- }
- }
- }
-
- if (pps != null)
- accountSelectorBox.setSelectedItem(pps);
- else if (accountSelectorBox.getItemCount() > 0)
- accountSelectorBox.setSelectedIndex(0);
- }
-
- /**
- * Initializes contact list sources.
- */
- private void initContactSources()
- {
- DemuxContactSourceService demuxCSService
- = GuiActivator.getDemuxContactSourceService();
-
- // If the DemuxContactSourceService isn't registered we use the default
- // contact source set.
- if (demuxCSService == null)
- return;
-
- Iterator<UIContactSource> sourcesIter
- = new ArrayList<UIContactSource>(
- srcContactList.getContactSources()).iterator();
-
- srcContactList.removeAllContactSources();
-
- while (sourcesIter.hasNext())
- {
- ContactSourceService contactSource
- = sourcesIter.next().getContactSourceService();
-
- srcContactList.addContactSource(
- demuxCSService.createDemuxContactSource(contactSource));
- }
- }
-
- /**
- * Initializes the left contact list with the contacts that could be added
- * to the current chat session.
- * @param protocolProvider the protocol provider from which to initialize
- * the contact list data
- */
- private void initContactListData(ProtocolProviderService protocolProvider)
- {
- this.setCurrentProvider(protocolProvider);
-
- Iterator<UIContactSource> sourcesIter
- = new ArrayList<UIContactSource>(
- srcContactList.getContactSources()).iterator();
-
- while (sourcesIter.hasNext())
- {
- ContactSourceService contactSource
- = sourcesIter.next().getContactSourceService();
-
- if (contactSource instanceof ProtocolAwareContactSourceService)
- {
- ((ProtocolAwareContactSourceService) contactSource)
- .setPreferredProtocolProvider(
- OperationSetBasicTelephony.class, protocolProvider);
- }
- }
-
- srcContactList.removeContactSource(currentProviderContactSource);
- srcContactList.removeContactSource(currentStringContactSource);
-
- currentProviderContactSource
- = new ProtocolContactSourceServiceImpl(
- protocolProvider,
- OperationSetBasicTelephony.class);
- currentStringContactSource
- = new StringContactSourceServiceImpl(
- protocolProvider,
- OperationSetBasicTelephony.class);
-
- srcContactList.addContactSource(currentProviderContactSource);
- srcContactList.addContactSource(currentStringContactSource);
-
- srcContactList.applyDefaultFilter();
- }
-
- /**
- * Invites the contacts to the chat conference.
- *
- * @param contacts the list of contacts to invite
- */
- private void inviteContacts(Collection<UIContact> contacts)
- {
- ProtocolProviderService selectedProvider = null;
- Map<ProtocolProviderService, List<String>> selectedProviderCallees
- = new HashMap<ProtocolProviderService, List<String>>();
- List<String> callees = null;
-
- Iterator<UIContact> contactsIter = contacts.iterator();
-
- while (contactsIter.hasNext())
- {
- UIContact uiContact = contactsIter.next();
-
- Iterator<UIContactDetail> contactDetailsIter = uiContact
- .getContactDetailsForOperationSet(
- OperationSetBasicTelephony.class).iterator();
-
- // We invite the first protocol contact that corresponds to the
- // invite provider.
- if (contactDetailsIter.hasNext())
- {
- UIContactDetail inviteDetail = contactDetailsIter.next();
- selectedProvider = inviteDetail
- .getPreferredProtocolProvider(
- OperationSetBasicTelephony.class);
-
- if (selectedProvider == null)
- {
- selectedProvider
- = (ProtocolProviderService)
- accountSelectorBox.getSelectedItem();
- }
-
- if(selectedProvider != null
- && selectedProviderCallees.get(selectedProvider) != null)
- {
- callees = selectedProviderCallees.get(selectedProvider);
- }
- else
- {
- callees = new ArrayList<String>();
- }
-
- callees.add(inviteDetail.getAddress());
- selectedProviderCallees.put(selectedProvider, callees);
- }
- }
-
- if(conference != null)
- {
- CallManager.inviteToConferenceCall(
- selectedProviderCallees,
- conference);
- }
- else
- {
- CallManager.createConferenceCall(selectedProviderCallees);
- }
- }
-
- /**
- * Invites the contacts to the chat conference.
- *
- * @param contacts the list of contacts to invite
- */
- private void inviteVideoBridgeContacts(
- ProtocolProviderService preselectedProvider,
- Collection<UIContact> contacts)
- {
- List<String> callees = new ArrayList<String>();
-
- Iterator<UIContact> contactsIter = contacts.iterator();
-
- while (contactsIter.hasNext())
- {
- UIContact uiContact = contactsIter.next();
-
- Iterator<UIContactDetail> contactDetailsIter = uiContact
- .getContactDetailsForOperationSet(
- OperationSetBasicTelephony.class).iterator();
-
- // We invite the first protocol contact that corresponds to the
- // invite provider.
- if (contactDetailsIter.hasNext())
- {
- UIContactDetail inviteDetail = contactDetailsIter.next();
-
- callees.add(inviteDetail.getAddress());
- }
- }
-
- if(conference != null)
- {
- CallManager.inviteToVideoBridgeConfCall(
- callees.toArray(new String[callees.size()]),
- conference.getCalls().get(0));
- }
- else
- {
- CallManager.createVideoBridgeConfCall(
- preselectedProvider,
- callees.toArray(new String[callees.size()]));
- }
- }
-}
+/*
+ * 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.gui.main.call.conference;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.impl.gui.main.call.*;
+import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*;
+import net.java.sip.communicator.impl.gui.utils.*;
+import net.java.sip.communicator.plugin.desktoputil.*;
+import net.java.sip.communicator.service.contactsource.*;
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * The invite dialog is the one shown when the user clicks on the conference
+ * button in the chat toolbar.
+ *
+ * @author Yana Stamcheva
+ * @author Lyubomir Marinov
+ */
+public class ConferenceInviteDialog
+ extends InviteDialog
+{
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * The account selector box.
+ */
+ private final JComboBox accountSelectorBox = new JComboBox();
+
+ /**
+ * The last selected account.
+ */
+ private Object lastSelectedAccount;
+
+ /**
+ * The telephony conference into which this instance is to invite
+ * participants.
+ */
+ private final CallConference conference;
+
+ /**
+ * The current provider contact source.
+ */
+ private ContactSourceService currentProviderContactSource;
+
+ /**
+ * The current string contact source.
+ */
+ private ContactSourceService currentStringContactSource;
+
+ /**
+ * The previously selected protocol provider, with which this dialog has
+ * been instantiated.
+ */
+ private ProtocolProviderService preselectedProtocolProvider;
+
+ /**
+ * Indicates whether this conference invite dialog is associated with a
+ * Jitsi Videobridge invite.
+ */
+ private final boolean isJitsiVideobridge;
+
+ /**
+ * Initializes a new <tt>ConferenceInviteDialog</tt> instance which is to
+ * invite contacts/participants in a specific telephony conference.
+ *
+ * @param conference the telephony conference in which the new instance is
+ * to invite contacts/participants
+ */
+ public ConferenceInviteDialog(
+ CallConference conference,
+ ProtocolProviderService preselectedProvider,
+ List<ProtocolProviderService> protocolProviders,
+ final boolean isJitsiVideobridge)
+ {
+ // Set the correct dialog title depending if we're going to create a
+ // video bridge conference call
+ super((isJitsiVideobridge
+ ? GuiActivator.getResources()
+ .getI18NString("service.gui.INVITE_CONTACT_TO_VIDEO_BRIDGE")
+ : GuiActivator.getResources()
+ .getI18NString("service.gui.INVITE_CONTACT_TO_CALL")),
+ false);
+
+ this.conference = conference;
+ this.preselectedProtocolProvider = preselectedProvider;
+ this.isJitsiVideobridge = isJitsiVideobridge;
+
+ if (preselectedProtocolProvider == null)
+ initAccountSelectorPanel(protocolProviders);
+
+ // init the list, as we check whether features are supported
+ // it may take some time if we have too much contacts
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ initContactSources();
+
+ // Initialize the list of contacts to select from.
+ if (preselectedProtocolProvider != null)
+ initContactListData(preselectedProtocolProvider);
+ else
+ initContactListData(
+ (ProtocolProviderService) accountSelectorBox
+ .getSelectedItem());
+ }
+ });
+
+ this.addInviteButtonListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Collection<UIContact> selectedContacts
+ = destContactList.getContacts(null);
+
+ if (selectedContacts != null && selectedContacts.size() > 0)
+ {
+ if (preselectedProtocolProvider == null)
+ preselectedProtocolProvider
+ = (ProtocolProviderService) accountSelectorBox
+ .getSelectedItem();
+
+ if (isJitsiVideobridge)
+ inviteJitsiVideobridgeContacts( preselectedProtocolProvider,
+ selectedContacts);
+ else
+ inviteContacts(selectedContacts);
+
+ // Store the last used account in order to pre-select it
+ // next time.
+ ConfigurationUtils.setLastCallConferenceProvider(
+ preselectedProtocolProvider);
+
+ dispose();
+ }
+ else
+ {
+ // TODO: The underlying invite dialog should show a message
+ // to the user that she should select at least two contacts
+ // in order to create a conference.
+ }
+ }
+ });
+
+ this.addCancelButtonListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ dispose();
+ }
+ });
+ }
+
+ /**
+ * Constructs the <tt>ConferenceInviteDialog</tt>.
+ */
+ public ConferenceInviteDialog()
+ {
+ this(null, null, null, false);
+ }
+
+ /**
+ * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an
+ * already created conference. To use when inviting contacts to an existing
+ * conference is needed.
+ *
+ * @param conference the existing <tt>CallConference</tt>
+ */
+ public ConferenceInviteDialog(CallConference conference)
+ {
+ this(conference, null, null, false);
+ }
+
+ /**
+ * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying an
+ * already created conference. To use when inviting contacts to an existing
+ * conference is needed.
+ *
+ * @param conference the existing <tt>CallConference</tt>
+ */
+ public ConferenceInviteDialog(
+ CallConference conference,
+ ProtocolProviderService preselectedProtocolProvider,
+ boolean isJitsiVideobridge)
+ {
+ this(conference, preselectedProtocolProvider, null, isJitsiVideobridge);
+ }
+
+ /**
+ * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a
+ * preselected protocol provider to be used and if this is an invite for
+ * a video bridge conference.
+ *
+ * @param selectedConfProvider the preselected protocol provider
+ * @param isJitsiVideobridge <tt>true</tt> if this dialog should create a
+ * conference through a Jitsi Videobridge; otherwise, <tt>false</tt>
+ */
+ public ConferenceInviteDialog(
+ List<ProtocolProviderService> protocolProviders,
+ boolean isJitsiVideobridge)
+ {
+ this(null, null, protocolProviders, isJitsiVideobridge);
+ }
+
+ /**
+ * Creates an instance of <tt>ConferenceInviteDialog</tt> by specifying a
+ * preselected protocol provider to be used and if this is an invite for
+ * a video bridge conference.
+ *
+ * @param selectedConfProvider the preselected protocol provider
+ * @param isJitsiVideobridge <tt>true</tt> if this dialog should create a
+ * conference through a Jitsi Videobridge; otherwise, <tt>false</tt>
+ */
+ public ConferenceInviteDialog(
+ ProtocolProviderService selectedConfProvider,
+ boolean isJitsiVideobridge)
+ {
+ this(null, selectedConfProvider, null, isJitsiVideobridge);
+ }
+
+ /**
+ * Initializes the account selector panel.
+ *
+ * @param protocolProviders the list of protocol providers we'd like to
+ * show in the account selector box
+ */
+ private void initAccountSelectorPanel(
+ List<ProtocolProviderService> protocolProviders)
+ {
+ JLabel accountSelectorLabel = new JLabel(
+ GuiActivator.getResources().getI18NString("service.gui.CALL_VIA"));
+
+ TransparentPanel accountSelectorPanel
+ = new TransparentPanel(new BorderLayout());
+
+ accountSelectorPanel.setBorder(
+ BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ accountSelectorPanel.add(accountSelectorLabel, BorderLayout.WEST);
+ accountSelectorPanel.add(accountSelectorBox, BorderLayout.CENTER);
+
+ // Initialize the account selector box.
+ if (protocolProviders != null && protocolProviders.size() > 0)
+ this.initAccountListData(protocolProviders);
+ else
+ this.initAccountListData();
+
+ this.accountSelectorBox.setRenderer(new DefaultListCellRenderer()
+ {
+ private static final long serialVersionUID = 0L;
+
+ @Override
+ public Component getListCellRendererComponent(JList list,
+ Object value, int index, boolean isSelected,
+ boolean cellHasFocus)
+ {
+ ProtocolProviderService protocolProvider
+ = (ProtocolProviderService) value;
+
+ if (protocolProvider != null)
+ {
+ this.setText(
+ protocolProvider.getAccountID().getDisplayName());
+ this.setIcon(
+ ImageLoader.getAccountStatusImage(protocolProvider));
+ }
+
+ return this;
+ }
+ });
+
+ this.accountSelectorBox.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Object accountSelectorBoxSelectedItem
+ = accountSelectorBox.getSelectedItem();
+
+ if (lastSelectedAccount == null
+ || !lastSelectedAccount
+ .equals(accountSelectorBoxSelectedItem))
+ {
+ lastSelectedAccount = accountSelectorBoxSelectedItem;
+
+ initContactListData(
+ (ProtocolProviderService) accountSelectorBox
+ .getSelectedItem());
+
+ if (isJitsiVideobridge)
+ destContactList.removeAll();
+ }
+ }
+ });
+
+ this.getContentPane().add(accountSelectorPanel, BorderLayout.NORTH);
+ }
+
+ /**
+ * Initializes the account selector box with the given list of
+ * <tt>ProtocolProviderService</tt>-s.
+ *
+ * @param protocolProviders the list of <tt>ProtocolProviderService</tt>-s
+ * we'd like to show in the account selector box
+ */
+ private void initAccountListData(
+ List<ProtocolProviderService> protocolProviders)
+ {
+ Iterator<ProtocolProviderService> providersIter
+ = protocolProviders.iterator();
+
+ while (providersIter.hasNext())
+ {
+ ProtocolProviderService protocolProvider
+ = providersIter.next();
+
+ accountSelectorBox.addItem(protocolProvider);
+ }
+
+ if (accountSelectorBox.getItemCount() > 0)
+ accountSelectorBox.setSelectedIndex(0);
+ }
+
+ /**
+ * Initializes the account list.
+ */
+ private void initAccountListData()
+ {
+ Iterator<ProtocolProviderService> protocolProviders
+ = GuiActivator.getUIService().getMainFrame().getProtocolProviders();
+
+ while(protocolProviders.hasNext())
+ {
+ ProtocolProviderService protocolProvider
+ = protocolProviders.next();
+ OperationSet opSet
+ = protocolProvider.getOperationSet(
+ OperationSetTelephonyConferencing.class);
+
+ if ((opSet != null) && protocolProvider.isRegistered())
+ accountSelectorBox.addItem(protocolProvider);
+ }
+
+ // Try to select the last used account if available.
+ ProtocolProviderService pps
+ = ConfigurationUtils.getLastCallConferenceProvider();
+
+ if (pps == null && conference != null)
+ {
+ /*
+ * Pick up the first account from the ones participating in the
+ * associated telephony conference which supports
+ * OperationSetTelephonyConferencing.
+ */
+ for (Call call : conference.getCalls())
+ {
+ ProtocolProviderService callPps = call.getProtocolProvider();
+
+ if (callPps.getOperationSet(
+ OperationSetTelephonyConferencing.class)
+ != null)
+ {
+ pps = callPps;
+ break;
+ }
+ }
+ }
+
+ if (pps != null)
+ accountSelectorBox.setSelectedItem(pps);
+ else if (accountSelectorBox.getItemCount() > 0)
+ accountSelectorBox.setSelectedIndex(0);
+ }
+
+ /**
+ * Initializes contact list sources.
+ */
+ private void initContactSources()
+ {
+ DemuxContactSourceService demuxCSService
+ = GuiActivator.getDemuxContactSourceService();
+
+ // If the DemuxContactSourceService isn't registered we use the default
+ // contact source set.
+ if (demuxCSService == null)
+ return;
+
+ Iterator<UIContactSource> sourcesIter
+ = new ArrayList<UIContactSource>(
+ srcContactList.getContactSources()).iterator();
+
+ srcContactList.removeAllContactSources();
+
+ while (sourcesIter.hasNext())
+ {
+ ContactSourceService contactSource
+ = sourcesIter.next().getContactSourceService();
+
+ srcContactList.addContactSource(
+ demuxCSService.createDemuxContactSource(contactSource));
+ }
+ }
+
+ /**
+ * Initializes the left contact list with the contacts that could be added
+ * to the current chat session.
+ * @param protocolProvider the protocol provider from which to initialize
+ * the contact list data
+ */
+ private void initContactListData(ProtocolProviderService protocolProvider)
+ {
+ this.setCurrentProvider(protocolProvider);
+
+ Iterator<UIContactSource> sourcesIter
+ = new ArrayList<UIContactSource>(
+ srcContactList.getContactSources()).iterator();
+
+ while (sourcesIter.hasNext())
+ {
+ ContactSourceService contactSource
+ = sourcesIter.next().getContactSourceService();
+
+ if (contactSource instanceof ProtocolAwareContactSourceService)
+ {
+ ((ProtocolAwareContactSourceService) contactSource)
+ .setPreferredProtocolProvider(
+ OperationSetBasicTelephony.class, protocolProvider);
+ }
+ }
+
+ srcContactList.removeContactSource(currentProviderContactSource);
+ srcContactList.removeContactSource(currentStringContactSource);
+
+ currentProviderContactSource
+ = new ProtocolContactSourceServiceImpl(
+ protocolProvider,
+ OperationSetBasicTelephony.class);
+ currentStringContactSource
+ = new StringContactSourceServiceImpl(
+ protocolProvider,
+ OperationSetBasicTelephony.class);
+
+ srcContactList.addContactSource(currentProviderContactSource);
+ srcContactList.addContactSource(currentStringContactSource);
+
+ srcContactList.applyDefaultFilter();
+ }
+
+ /**
+ * Invites the contacts to the chat conference.
+ *
+ * @param contacts the list of contacts to invite
+ */
+ private void inviteContacts(Collection<UIContact> contacts)
+ {
+ ProtocolProviderService selectedProvider = null;
+ Map<ProtocolProviderService, List<String>> selectedProviderCallees
+ = new HashMap<ProtocolProviderService, List<String>>();
+ List<String> callees = null;
+
+ Iterator<UIContact> contactsIter = contacts.iterator();
+
+ while (contactsIter.hasNext())
+ {
+ UIContact uiContact = contactsIter.next();
+
+ Iterator<UIContactDetail> contactDetailsIter = uiContact
+ .getContactDetailsForOperationSet(
+ OperationSetBasicTelephony.class).iterator();
+
+ // We invite the first protocol contact that corresponds to the
+ // invite provider.
+ if (contactDetailsIter.hasNext())
+ {
+ UIContactDetail inviteDetail = contactDetailsIter.next();
+ selectedProvider = inviteDetail
+ .getPreferredProtocolProvider(
+ OperationSetBasicTelephony.class);
+
+ if (selectedProvider == null)
+ {
+ selectedProvider
+ = (ProtocolProviderService)
+ accountSelectorBox.getSelectedItem();
+ }
+
+ if(selectedProvider != null
+ && selectedProviderCallees.get(selectedProvider) != null)
+ {
+ callees = selectedProviderCallees.get(selectedProvider);
+ }
+ else
+ {
+ callees = new ArrayList<String>();
+ }
+
+ callees.add(inviteDetail.getAddress());
+ selectedProviderCallees.put(selectedProvider, callees);
+ }
+ }
+
+ if(conference != null)
+ {
+ CallManager.inviteToConferenceCall(
+ selectedProviderCallees,
+ conference);
+ }
+ else
+ {
+ CallManager.createConferenceCall(selectedProviderCallees);
+ }
+ }
+
+ /**
+ * Invites the contacts to the chat conference.
+ *
+ * @param contacts the list of contacts to invite
+ */
+ private void inviteJitsiVideobridgeContacts(
+ ProtocolProviderService preselectedProvider,
+ Collection<UIContact> contacts)
+ {
+ List<String> callees = new ArrayList<String>();
+
+ Iterator<UIContact> contactsIter = contacts.iterator();
+
+ while (contactsIter.hasNext())
+ {
+ UIContact uiContact = contactsIter.next();
+
+ Iterator<UIContactDetail> contactDetailsIter = uiContact
+ .getContactDetailsForOperationSet(
+ OperationSetBasicTelephony.class).iterator();
+
+ // We invite the first protocol contact that corresponds to the
+ // invite provider.
+ if (contactDetailsIter.hasNext())
+ {
+ UIContactDetail inviteDetail = contactDetailsIter.next();
+
+ callees.add(inviteDetail.getAddress());
+ }
+ }
+
+ if(conference != null)
+ {
+ CallManager.inviteToJitsiVideobridgeConfCall(
+ callees.toArray(new String[callees.size()]),
+ conference.getCalls().get(0));
+ }
+ else
+ {
+ CallManager.createJitsiVideobridgeConfCall(
+ preselectedProvider,
+ callees.toArray(new String[callees.size()]));
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java
index 742bfd2..b56bada 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/VideoConferenceCallPanel.java
@@ -1,1385 +1,1385 @@
-/*
- * 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.gui.main.call.conference;
-
-import java.awt.*;
-import java.util.*;
-import java.util.List;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.gui.main.call.*;
-import net.java.sip.communicator.impl.gui.utils.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.plugin.desktoputil.*;
-import net.java.sip.communicator.plugin.desktoputil.TransparentPanel;
-
-import org.jitsi.util.swing.*;
-
-/**
- * Extends <tt>BasicConferenceCallPanel</tt> to implement a user interface
- * <tt>Component</tt> which depicts a <tt>CallConference</tt> with audio and
- * video and is contained in a <tt>CallPanel</tt>.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class VideoConferenceCallPanel
- extends BasicConferenceCallPanel
-{
- /**
- * The <tt>Logger</tt> used by the <tt>VideoConferenceCallPanel</tt> class
- * and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(VideoConferenceCallPanel.class);
-
- /**
- * The compile-time flag which indicates whether each video displayed by
- * <tt>VideoConferenceCallPanel</tt> is to be depicted with an associated
- * tool bar showing information and controls related to the (local or
- * remote) peer sending the respective video.
- */
- private static final boolean SHOW_TOOLBARS = true;
-
- /**
- * The facility which aids this instance with the video-related information.
- */
- private final UIVideoHandler2 uiVideoHandler;
-
- /**
- * The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about
- * changes in the video-related information.
- */
- private final Observer uiVideoHandlerObserver
- = new Observer()
- {
- public void update(Observable o, Object arg)
- {
- updateViewFromModel();
- }
- };
-
- /**
- * The <tt>VideoContainer</tt> which occupies this whole <tt>Component</tt>
- * and arranges the visual <tt>Component</tt>s displaying the video
- * streaming between the local peer/user and the remote peer(s).
- */
- private final VideoContainer videoContainer;
-
- /**
- * The set of visual <tt>Component</tt>s displaying video streaming between
- * the local peer/user and the remote peers which are depicted by this
- * instance.
- */
- private final Set<Component> videos = new HashSet<Component>();
-
- /**
- * The thumbnail container.
- */
- private final ThumbnailConferenceCallPanel thumbnailContainer;
-
- /**
- * Initializes a new <tt>VideoConferenceCallPanel</tt> instance which is to
- * be used by a specific <tt>CallPanel</tt> to depict a specific
- * <tt>CallConference</tt>. The new instance will depict both the
- * audio-related and the video-related information.
- *
- * @param callPanel the <tt>CallPanel</tt> which will use the new instance
- * to depict the specified <tt>CallConference</tt>.
- * @param callConference the <tt>CallConference</tt> to be depicted by the
- * new instance
- * @param uiVideoHandler the utility which is to aid the new instance in
- * dealing with the video-related information
- */
- public VideoConferenceCallPanel(
- CallPanel callPanel,
- CallConference callConference,
- UIVideoHandler2 uiVideoHandler)
- {
- super(callPanel, callConference);
-
- this.uiVideoHandler = uiVideoHandler;
-
- thumbnailContainer
- = new ThumbnailConferenceCallPanel( callPanel,
- callConference,
- uiVideoHandler);
-
- videoContainer = createVideoContainer();
-
- /*
- * Our user interface hierarchy has been initialized so we are ready to
- * begin receiving events warranting updates of this view from its
- * model.
- */
- uiVideoHandler.addObserver(uiVideoHandlerObserver);
-
- /*
- * Notify the super that this instance has completed its initialization
- * and the view that it implements is ready to be updated from the
- * model.
- */
- initializeComplete();
- }
-
- private void addConferenceMemberContainers(
- ConferenceParticipantContainer cpc)
- {
- List<ConferenceParticipantContainer> cmcs
- = cpc.conferenceMemberContainers;
-
- if ((cmcs != null) && !cmcs.isEmpty())
- {
- for (ConferenceParticipantContainer cmc : cmcs)
- {
- if (!cmc.toBeRemoved)
- {
- videoContainer.add(
- cmc.getComponent(),
- VideoLayout.CENTER_REMOTE);
- }
- }
- }
- }
-
- private Component createDefaultPhotoPanel(Call call)
- {
- OperationSetServerStoredAccountInfo accountInfo
- = call.getProtocolProvider().getOperationSet(
- OperationSetServerStoredAccountInfo.class);
- ImageIcon photoLabelIcon = null;
-
- if (accountInfo != null)
- {
- byte[] accountImage = AccountInfoUtils.getImage(accountInfo);
-
- // do not set empty images
- if ((accountImage != null) && (accountImage.length > 0))
- photoLabelIcon = new ImageIcon(accountImage);
- }
- if (photoLabelIcon == null)
- {
- photoLabelIcon
- = new ImageIcon(
- ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO));
- }
-
- return createDefaultPhotoPanel(photoLabelIcon);
- }
-
- private Component createDefaultPhotoPanel(CallPeer callPeer)
- {
- byte[] peerImage = CallManager.getPeerImage(callPeer);
- ImageIcon photoLabelIcon
- = (peerImage == null)
- ? new ImageIcon(
- ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
- : new ImageIcon(peerImage);
-
- return createDefaultPhotoPanel(photoLabelIcon);
- }
-
- private Component createDefaultPhotoPanel(ConferenceMember conferenceMember)
- {
- return
- createDefaultPhotoPanel(
- new ImageIcon(
- ImageLoader.getImage(
- ImageLoader.DEFAULT_USER_PHOTO)));
- }
-
- /**
- * Creates a new <tt>Component</tt> which is to display a specific
- * <tt>ImageIcon</tt> representing the photo of a participant in a call.
- *
- * @param photoLabelIcon the <tt>ImageIcon</tt> which represents the photo
- * of a participant in a call and which is to be displayed by the new
- * <tt>Component</tt>
- * @return a new <tt>Component</tt> which displays the specified
- * <tt>photoLabelIcon</tt>
- */
- private Component createDefaultPhotoPanel(ImageIcon photoLabelIcon)
- {
- JLabel photoLabel = new JLabel();
-
- photoLabel.setIcon(photoLabelIcon);
-
- @SuppressWarnings("serial")
- JPanel photoPanel
- = new TransparentPanel(new GridBagLayout())
- {
- /**
- * @{inheritDoc}
- */
- @Override
- public void paintComponent(Graphics g)
- {
- super.paintComponent(g);
-
- g = g.create();
- try
- {
- AntialiasingManager.activateAntialiasing(g);
-
- g.setColor(Color.GRAY);
- g.fillRoundRect(
- 0, 0, this.getWidth(), this.getHeight(),
- 6, 6);
- }
- finally
- {
- g.dispose();
- }
- }
- };
-
- photoPanel.setPreferredSize(new Dimension(320, 240));
-
- GridBagConstraints photoPanelConstraints = new GridBagConstraints();
-
- photoPanelConstraints.anchor = GridBagConstraints.CENTER;
- photoPanelConstraints.fill = GridBagConstraints.NONE;
- photoPanel.add(photoLabel, photoPanelConstraints);
-
- return photoPanel;
- }
-
- /**
- * Initializes a new <tt>VideoContainer</tt> instance which is to contain
- * the visual/video <tt>Component</tt>s of the telephony conference depicted
- * by this instance.
- */
- private VideoContainer createVideoContainer()
- {
- VideoContainer videoContainer = new VideoContainer(null, true);
-
- JPanel thumbnailPanel = new JPanel(new BorderLayout());
- thumbnailPanel.setBackground(Color.DARK_GRAY);
- thumbnailPanel.add(thumbnailContainer, BorderLayout.NORTH);
-
- add(thumbnailPanel, BorderLayout.EAST);
- add(videoContainer, BorderLayout.CENTER);
-
- return videoContainer;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void dispose()
- {
- try
- {
- uiVideoHandler.deleteObserver(uiVideoHandlerObserver);
- }
- finally
- {
- super.dispose();
- }
- }
-
- /**
- * Determines whether a specific <tt>ConferenceMember</tt> represents the
- * same conference participant as a specific <tt>CallPeer</tt>. If the
- * specified <tt>conferenceMember</tt> is <tt>null</tt>, returns
- * <tt>true</tt>. Otherwise, determines whether the addresses of the
- * specified <tt>conferenceMember</tt> and the specified <tt>callPeer</tt>
- * identify one and the same entity.
- *
- * @param conferenceMember the <tt>ConferenceMember</tt> to be checked
- * whether is represents the same conference participant as the specified
- * <tt>callPeer</tt>. If it is <tt>null</tt>, <tt>true</tt> is returned.
- * @param callPeer the <tt>CallPeer</tt> to be checked whether it represents
- * the same conference participant as the specified
- * <tt>conferenceMember</tt>
- * @return <tt>true</tt> if the specified <tt>conferenceMember</tt> and the
- * specified <tt>callPeer</tt> represent the same conference participant or
- * the specified <tt>conferenceMember</tt> is <tt>null</tt>; otherwise,
- * <tt>false</tt>
- */
- private boolean isConferenceMemberCallPeer(
- ConferenceMember conferenceMember,
- CallPeer callPeer)
- {
- return
- (conferenceMember == null)
- ? true
- : CallManager.addressesAreEqual(
- conferenceMember.getAddress(),
- callPeer.getAddress());
- }
-
- /**
- * Determines whether a specific <tt>ConferenceMember</tt> represents the
- * local peer/user. Since this instance depicts a whole telephony
- * conference, the local peer/user may be participating with multiple
- * <tt>Call</tt>s in it. The <tt>Call</tt>s may be through different
- * (local) accounts. That's why the implementation determines whether the
- * address of the specified <tt>conferenceMember</tt> identifies the address
- * of a (local) accounts involved in the telephony conference depicted by
- * this instance.
- *
- * @param conferenceMember the <tt>ConferenceMember</tt> to be checked
- * whether it represents the local peer/user
- * @return <tt>true</tt> if the specified <tt>conferenceMember</tt>
- * represents the local peer/user; otherwise, <tt>false</tt>
- */
- private boolean isConferenceMemberLocalUser(
- ConferenceMember conferenceMember)
- {
- String address = conferenceMember.getAddress();
-
- for (Call call : callConference.getCalls())
- {
- if (CallManager.addressesAreEqual(
- address,
- call.getProtocolProvider().getAccountID()
- .getAccountAddress()))
- {
- return true;
- }
- }
- return false;
- }
-
- private void removeConferenceMemberContainers(
- ConferenceParticipantContainer cpc,
- boolean all)
- {
- List<ConferenceParticipantContainer> cmcs
- = cpc.conferenceMemberContainers;
-
- if ((cmcs != null) && !cmcs.isEmpty())
- {
- Iterator<ConferenceParticipantContainer> i = cmcs.iterator();
-
- while (i.hasNext())
- {
- ConferenceParticipantContainer cmc = i.next();
-
- if (all || cmc.toBeRemoved)
- {
- i.remove();
-
- videoContainer.remove(cmc.getComponent());
- cmc.dispose();
- }
- }
- }
- }
-
- /**
- * Updates the <tt>ConferenceParticipantContainer</tt>s which depict the
- * <tt>ConferenceMember</tt>s of the <tt>CallPeer</tt> depicted by a
- * specific <tt>ConferenceParticipantContainer</tt>.
- *
- * @param cpc the <tt>ConferenceParticipantContainer</tt> which depicts the
- * <tt>CallPeer</tt> whose <tt>ConferenceMember</tt>s are to be depicted
- * @param videos the visual <tt>Component</tt>s displaying video streaming
- * from the remote peer (represented by <tt>cpc</tt>) to the local peer/user
- * @param videoTelephony the <tt>OperationSetVideoTelephony</tt> which
- * retrieved the specified <tt>videos</tt> from the <tt>CallPeer</tt>
- * depicted by <tt>cpc</tt>. While the <tt>CallPeer</tt> could be queried
- * for it, such a query would waste more resources at run time given that
- * the invoker has it already.
- */
- private void updateConferenceMemberContainers(
- ConferenceParticipantContainer cpc,
- List<Component> videos,
- OperationSetVideoTelephony videoTelephony)
- {
- CallPeer callPeer = (CallPeer) cpc.getParticipant();
- List<ConferenceParticipantContainer> cmcs
- = cpc.conferenceMemberContainers;
-
- /*
- * Invalidate all conferenceMemberContainers. Then validate which of
- * them are to remain and which of them are to really be removed
- * later on.
- */
- if (cmcs != null)
- {
- for (ConferenceParticipantContainer cmc : cmcs)
- cmc.toBeRemoved = true;
- }
-
- /*
- * Depict the remote videos. They may or may not be associated with
- * ConferenceMembers so the ConferenceMembers which have no
- * associated videos will be depicted afterwards.
- */
- if (videos != null)
- {
- Component video = cpc.getVideo();
-
- for (Component conferenceMemberVideo : videos)
- {
- /*
- * One of the remote videos is already used to depict the
- * callPeer.
- */
- if (conferenceMemberVideo == video)
- continue;
-
- boolean addNewConferenceParticipantContainer = true;
- ConferenceMember conferenceMember
- = videoTelephony.getConferenceMember(
- callPeer,
- conferenceMemberVideo);
-
- if (cmcs == null)
- {
- cmcs = new LinkedList<ConferenceParticipantContainer>();
- cpc.conferenceMemberContainers = cmcs;
- }
- else
- {
- for (ConferenceParticipantContainer cmc : cmcs)
- {
- Object cmcParticipant = cmc.getParticipant();
-
- if (conferenceMember == null)
- {
- if (cmcParticipant == callPeer)
- {
- Component cmcVideo = cmc.getVideo();
-
- if (cmcVideo == null)
- {
- cmc.setVideo(conferenceMemberVideo);
- cmc.toBeRemoved = false;
- addNewConferenceParticipantContainer
- = false;
- break;
- }
- else if (cmcVideo == conferenceMemberVideo)
- {
- cmc.toBeRemoved = false;
- addNewConferenceParticipantContainer
- = false;
- break;
- }
- }
- }
- else if (cmcParticipant == conferenceMember)
- {
- cmc.setVideo(conferenceMemberVideo);
- cmc.toBeRemoved = false;
- addNewConferenceParticipantContainer = false;
- break;
- }
- }
- }
-
- if (addNewConferenceParticipantContainer)
- {
- ConferenceParticipantContainer cmc
- = (conferenceMember == null)
- ? new ConferenceParticipantContainer(
- callPeer,
- conferenceMemberVideo)
- : new ConferenceParticipantContainer(
- conferenceMember,
- conferenceMemberVideo);
-
- cmcs.add(cmc);
- }
- }
- }
-
- /*
- * Depict the ConferenceMembers which have not been depicted yet.
- * They have no associated videos.
- */
- List<ConferenceMember> conferenceMembers
- = callPeer.getConferenceMembers();
-
- if (!conferenceMembers.isEmpty())
- {
- if (cmcs == null)
- {
- cmcs = new LinkedList<ConferenceParticipantContainer>();
- cpc.conferenceMemberContainers = cmcs;
- }
- for (ConferenceMember conferenceMember : conferenceMembers)
- {
- /*
- * If the callPeer reports itself as a ConferenceMember, then
- * we've already depicted it with cpc.
- */
- if (isConferenceMemberCallPeer(conferenceMember, callPeer))
- continue;
- /*
- * If the callPeer reports the local peer/user as a
- * ConferenceMember, then we've already depicted it.
- */
- if (isConferenceMemberLocalUser(conferenceMember))
- continue;
-
- boolean addNewConferenceParticipantContainer = true;
-
- for (ConferenceParticipantContainer cmc : cmcs)
- {
- if (cmc.getParticipant() == conferenceMember)
- {
- /*
- * It is possible to have a ConferenceMember who is
- * sending video but we just do not have the SSRC of
- * that video to associate the video with the
- * ConferenceMember. In such a case, we may be depicting
- * the ConferenceMember twice: once with video without a
- * ConferenceMember and once with a ConferenceMember
- * without video. This will surely be the case at the
- * time of this writing with non-focus participants in a
- * telephony conference hosted on a Jitsi VideoBridge.
- * Such a display is undesirable. If the
- * conferenceMember is known to send video, we will not
- * display it until we associated it with a video. This
- * way, if a ConferenceMember is not sending video, we
- * will depict it and we can be sure that no video
- * without a ConferenceMember association will be
- * depicting it a second time.
- */
- if (cmc.toBeRemoved
- && !conferenceMember
- .getVideoStatus()
- .allowsSending())
- {
- cmc.setVideo(null);
- cmc.toBeRemoved = false;
- }
- addNewConferenceParticipantContainer = false;
- break;
- }
- }
-
- if (addNewConferenceParticipantContainer)
- {
- ConferenceParticipantContainer cmc
- = new ConferenceParticipantContainer(
- conferenceMember,
- null);
-
- cmcs.add(cmc);
- }
- }
- }
-
- if ((cmcs != null) && !cmcs.isEmpty())
- {
- removeConferenceMemberContainers(cpc, false);
- /*
- * If cpc is already added to the user interface hierarchy of this
- * instance, then it was there before the update procedure and it
- * was determined to be appropriate to continue to depict its model.
- * Consequently, its Component will be neither added to (because it
- * was already added) nor removed from the user interface hierarchy
- * of this instance. That's why we have make sure that the
- * Components of its conferenceMemberContainers are also added to
- * the user interface.
- */
- if (UIVideoHandler2.isAncestor(this, cpc.getComponent()))
- addConferenceMemberContainers(cpc);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected ConferenceCallPeerRenderer updateViewFromModel(
- ConferenceCallPeerRenderer callPeerPanel,
- CallPeer callPeer)
- {
- if (callPeer == null)
- {
- /*
- * The local peer/user will be represented by a Call which has a
- * CallPeer who provides local video. However, if the user has
- * selected to hide the local video, the local peer/user will not be
- * represented at all.
- */
- Component video = null;
-
- if (uiVideoHandler.isLocalVideoVisible())
- {
- for (Call aCall : callConference.getCalls())
- {
- Iterator<? extends CallPeer> callPeerIter
- = aCall.getCallPeers();
- OperationSetVideoTelephony videoTelephony
- = aCall.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
-
- while (callPeerIter.hasNext())
- {
- callPeer = callPeerIter.next();
-
- if (videoTelephony != null)
- {
- try
- {
- video
- = videoTelephony.getLocalVisualComponent(
- callPeer);
- }
- catch (OperationFailedException ofe)
- {
- logger.error(
- "Failed to retrieve the local video"
- + " for display",
- ofe);
- }
- if (video != null)
- break;
- }
- }
- if (video != null)
- break;
- }
- }
-
- if (callPeer == null)
- callPeerPanel = null;
- else
- {
- Call call = callPeer.getCall();
-
- if (callPeerPanel instanceof ConferenceParticipantContainer)
- {
- ConferenceParticipantContainer cpc
- = (ConferenceParticipantContainer) callPeerPanel;
-
- if (cpc.getParticipant() == call)
- cpc.setVideo(video);
- else
- callPeerPanel = null;
- }
- else
- callPeerPanel = null;
- if (callPeerPanel == null)
- {
- callPeerPanel
- = new ConferenceParticipantContainer(call, video);
- }
- }
- }
- else
- {
- /*
- * The specified callPeer will be represented by one of its remote
- * videos which is not associated with a ConferenceMember or is
- * associated with a ConferenceMember representing the callPeer
- * itself.
- */
- OperationSetVideoTelephony videoTelephony
- = callPeer.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
- List<Component> videos = null;
- Component video = null;
-
- if (videoTelephony != null)
- {
- videos = videoTelephony.getVisualComponents(callPeer);
- if ((videos != null) && !videos.isEmpty())
- {
- for (Component aVideo : videos)
- {
- ConferenceMember conferenceMember
- = videoTelephony.getConferenceMember(
- callPeer,
- aVideo);
-
- if (isConferenceMemberCallPeer(
- conferenceMember,
- callPeer))
- {
- video = aVideo;
- break;
- }
- }
- }
- }
-
- ConferenceParticipantContainer cpc = null;
-
- if (callPeerPanel instanceof ConferenceParticipantContainer)
- {
- cpc = (ConferenceParticipantContainer) callPeerPanel;
- if (cpc.getParticipant() == callPeer)
- cpc.setVideo(video);
- else
- cpc = null;
- }
- if (cpc == null)
- cpc = new ConferenceParticipantContainer(callPeer, video);
- callPeerPanel = cpc;
-
- // Update the conferenceMemberContainers of the cpc.
- updateConferenceMemberContainers(cpc, videos, videoTelephony);
- }
- return callPeerPanel;
- }
-
- /**
- * {@inheritDoc}
- *
- * If {@link #SHOW_TOOLBARS} is <tt>false</tt>, disables the use of
- * <tt>ConferenceParticipantContainer</tt>. A reason for such a value of
- * <tt>SHOW_TOOLBARS</tt> may be that the functionality implemented in the
- * model may not fully support mapping of visual <tt>Component</tt>s
- * displaying video to telephony conference participants (e.g. in telephony
- * conferences utilizing the Jitsi VideoBridge server-side technology). In
- * such a case displays the videos only, does not map videos to participants
- * and does not display participants who do not have videos.
- */
- @Override
- protected void updateViewFromModelInEventDispatchThread()
- {
- if (SHOW_TOOLBARS)
- {
- super.updateViewFromModelInEventDispatchThread();
- return;
- }
-
- /*
- * Determine the set of visual Components displaying video streaming
- * between the local peer/user and the remote peers which are to be
- * depicted by this instance.
- */
- Component localVideo = null;
- Set<Component> videos = new HashSet<Component>();
-
- for (Call call : callConference.getCalls())
- {
- OperationSetVideoTelephony videoTelephony
- = call.getProtocolProvider().getOperationSet(
- OperationSetVideoTelephony.class);
-
- if (videoTelephony == null)
- continue;
-
- Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- CallPeer callPeer = callPeerIter.next();
-
- /*
- * TODO VideoConferenceCallPanel respects
- * UIVideoHandler2.isLocalVideoVisible() in order to react to
- * the associated button at the bottom of the CallPanel.
- * However, it does not add a close button on top of the local
- * video in contrast to OneToOneCallPeerPanel. Overall, the
- * result is questionable.
- */
- if (uiVideoHandler.isLocalVideoVisible()
- && (localVideo == null))
- {
- try
- {
- localVideo
- = videoTelephony.getLocalVisualComponent(callPeer);
- }
- catch (OperationFailedException ofe)
- {
- /*
- * We'll just try to get the local video through another
- * CallPeer then.
- */
- }
- if (localVideo != null)
- videos.add(localVideo);
- }
-
- List<Component> callPeerRemoteVideos
- = videoTelephony.getVisualComponents(callPeer);
-
- videos.addAll(callPeerRemoteVideos);
- }
- }
-
- /*
- * Remove the Components of this view which are no longer present in the
- * model.
- */
- Iterator<Component> thisVideoIter = this.videos.iterator();
-
- while (thisVideoIter.hasNext())
- {
- Component thisVideo = thisVideoIter.next();
-
- if (!videos.contains(thisVideo))
- {
- thisVideoIter.remove();
- videoContainer.remove(thisVideo);
- }
-
- /*
- * If a video is known to be depicted by this view and is still
- * present in the model, then we could remove it from the set of
- * videos present in the model in order to prevent going through the
- * procedure of adding it to this view. However, we choose to play
- * on the safe side.
- */
- }
-
- /*
- * Add the Components of the model which are not depicted by this view.
- */
- for (Component video : videos)
- {
- if (!UIVideoHandler2.isAncestor(videoContainer, video))
- {
- this.videos.add(video);
- videoContainer.add(
- video,
- (video == localVideo)
- ? VideoLayout.LOCAL
- : VideoLayout.CENTER_REMOTE);
- }
- }
- }
-
- @Override
- protected void viewForModelAdded(
- ConferenceCallPeerRenderer callPeerPanel,
- CallPeer callPeer)
- {
- videoContainer.add(
- callPeerPanel.getComponent(),
- VideoLayout.CENTER_REMOTE);
- if ((callPeer != null)
- && (callPeerPanel instanceof ConferenceParticipantContainer))
- {
- addConferenceMemberContainers(
- (ConferenceParticipantContainer) callPeerPanel);
- }
- }
-
- @Override
- protected void viewForModelRemoved(
- ConferenceCallPeerRenderer callPeerPanel,
- CallPeer callPeer)
- {
- videoContainer.remove(callPeerPanel.getComponent());
- if ((callPeer != null)
- && (callPeerPanel instanceof ConferenceParticipantContainer))
- {
- removeConferenceMemberContainers(
- (ConferenceParticipantContainer) callPeerPanel,
- true);
- }
- }
-
- /**
- * Implements an AWT <tt>Component</tt> which contains the user interface
- * elements depicting a specific participant in the telephony conference
- * depicted by a <tt>VideoConferenceCallPanel</tt>.
- */
- private class ConferenceParticipantContainer
- extends TransparentPanel
- implements ConferenceCallPeerRenderer
- {
- /**
- * The list of <tt>ConferenceParticipantContainer</tt>s which represent
- * the <tt>ConferenceMember</tt>s of the participant represented by this
- * instance. Since a <tt>CallPeer</tt> may send the local peer/user
- * multiple videos without providing a way to associate a
- * ConferenceMember with each one of them, the list may contain
- * <tt>ConferenceParticipantContainer</tt>s which do not represent a
- * specific <tt>ConferenceMember</tt> instance but rather a video sent
- * by a <tt>CallPeer</tt> to the local peer/user which looks like (in
- * the terms of <tt>VideoConferenceCallPanel) a member of a conference
- * organized by the <tt>CallPeer</tt> in question.
- * <p>
- * Implements a state which is private to
- * <tt>VideoConferenceCallPanel</tt> and is of no concern to
- * <tt>ConferenceParticipantContainer</tt>.
- * </p>
- */
- List<ConferenceParticipantContainer> conferenceMemberContainers;
-
- /**
- * The indicator which determines whether this instance is to be removed
- * because it has become out-of-date, obsolete, unnecessary.
- * <p>
- * Implements a state which is private to
- * <tt>VideoConferenceCallPanel</tt> and is of no concern to
- * <tt>ConferenceParticipantContainer</tt>.
- * </p>
- */
- boolean toBeRemoved;
-
- /**
- * The <tt>BasicConferenceParticipantPanel</tt> which is displayed at
- * the bottom of this instance, bellow the {@link #video} (i.e.
- * {@link #videoContainer}) and is referred to as the tool bar.
- */
- private final BasicConferenceParticipantPanel<?> toolBar;
-
- /**
- * The visual <tt>Component</tt>, if any, displaying video which is
- * depicted by this instance.
- */
- private Component video;
-
- /**
- * The <tt>VideoContainer</tt> which lays out the video depicted by this
- * instance i.e. {@link #video}.
- */
- private final VideoContainer videoContainer;
-
- /**
- * The <tt>CallPeer</tt> associated with this container, if it has been
- * created to represent a <tt>CallPeer</tt>.
- */
- private CallPeer callPeer;
-
- /**
- * The <tt>conferenceMember</tt> associated with this container, if it
- * has been created to represent a <tt>conferenceMember</tt>.
- */
- private ConferenceMember conferenceMember;
-
- /**
- * Indicates that this container contains information for the local
- * user.
- */
- private boolean isLocalUser;
-
- /**
- * Initializes a new <tt>ConferenceParticipantContainer</tt> instance
- * which is to depict the local peer/user.
- *
- * @param call a <tt>Call</tt> which is to provide information about the
- * local peer/user
- * @param video the visual <tt>Component</tt>, if any, displaying the
- * video streaming from the local peer/user to the remote peer(s)
- */
- public ConferenceParticipantContainer(Call call, Component video)
- {
- this(
- createDefaultPhotoPanel(call),
- video,
- new ConferencePeerPanel(
- VideoConferenceCallPanel.this,
- call,
- true),
- null, null, true);
- }
-
- public ConferenceParticipantContainer(
- CallPeer callPeer,
- Component video)
- {
- this( createDefaultPhotoPanel(callPeer),
- video,
- new ConferencePeerPanel(
- VideoConferenceCallPanel.this,
- callPeer,
- true),
- callPeer, null, false);
- }
-
- private ConferenceParticipantContainer(
- Component noVideo,
- Component video,
- BasicConferenceParticipantPanel<?> toolBar,
- CallPeer callPeer,
- ConferenceMember conferenceMember,
- boolean isLocalUser)
- {
- super(new BorderLayout());
-
- this.callPeer = callPeer;
- this.conferenceMember = conferenceMember;
- this.isLocalUser = isLocalUser;
-
- videoContainer = new VideoContainer(noVideo, false);
- add(videoContainer, BorderLayout.CENTER);
-
- this.toolBar = toolBar;
- if (this.toolBar != null)
- add(this.toolBar, BorderLayout.SOUTH);
-
- if (video != null)
- {
- setVideo(video);
- }
- else
- setVisible(false);
- }
-
- public ConferenceParticipantContainer(
- ConferenceMember conferenceMember,
- Component video)
- {
- this(
- createDefaultPhotoPanel(conferenceMember),
- video,
- new ConferenceMemberPanel(
- VideoConferenceCallPanel.this,
- conferenceMember,
- true),
- null, conferenceMember, false);
- }
-
- public void dispose()
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.dispose();
-
- // Dispose of the conferenceMemberContainers if any.
- /*
- * XXX The field conferenceMemberContainers implements a state
- * private to VideoConferenceCallPanel which the latter makes sure
- * to access on the AWT event dispatching thread only. Since we are
- * going out of our way here to help VideoConferenceCallPanel,
- * ensure that the mentioned synchronization rule is not violated.
- */
- CallManager.assertIsEventDispatchingThread();
- if (conferenceMemberContainers != null)
- {
- for (ConferenceParticipantContainer cmc
- : conferenceMemberContainers)
- {
- cmc.dispose();
- }
- }
- }
-
- public CallPanel getCallPanel()
- {
- return getCallRenderer().getCallContainer();
- }
-
- public SwingCallRenderer getCallRenderer()
- {
- return VideoConferenceCallPanel.this;
- }
-
- public Component getComponent()
- {
- return this;
- }
-
- private ConferenceCallPeerRenderer
- getConferenceCallPeerRendererDelegate()
- {
- return
- (toolBar instanceof ConferenceCallPeerRenderer)
- ? (ConferenceCallPeerRenderer) toolBar
- : null;
- }
-
- /**
- * Gets the conference participant depicted by this instance.
- *
- * @return the conference participant depicted by this instance
- */
- public Object getParticipant()
- {
- return (toolBar == null) ? null : toolBar.getParticipant();
- }
-
- public Component getVideo()
- {
- return video;
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only. Otherwise, returns <tt>false</tt>.
- */
- public boolean isLocalVideoVisible()
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- return (delegate == null) ? false : delegate.isLocalVideoVisible();
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void printDTMFTone(char dtmfChar)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.printDTMFTone(dtmfChar);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityNegotiationStarted(
- CallPeerSecurityNegotiationStartedEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityNegotiationStarted(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityOff(CallPeerSecurityOffEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityOff(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityOn(CallPeerSecurityOnEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityOn(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityPending()
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityPending();
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void securityTimeout(CallPeerSecurityTimeoutEvent ev)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.securityTimeout(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setErrorReason(String reason)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setErrorReason(reason);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setLocalVideoVisible(boolean visible)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setLocalVideoVisible(visible);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setMute(boolean mute)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setMute(mute);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setOnHold(boolean onHold)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setOnHold(onHold);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setPeerImage(byte[] image)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setPeerImage(image);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setPeerName(String name)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setPeerName(name);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setPeerState(
- CallPeerState oldState,
- CallPeerState newState,
- String stateString)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setPeerState(oldState, newState, stateString);
- }
-
- /**
- * {@inheritDoc}
- *
- * Delegates to the <tt>toolBar</tt>, if the latter implements
- * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
- * container only.
- */
- public void setSecurityPanelVisible(boolean visible)
- {
- ConferenceCallPeerRenderer delegate
- = getConferenceCallPeerRendererDelegate();
-
- if (delegate != null)
- delegate.setSecurityPanelVisible(visible);
- }
-
- /**
- * Sets the visual <tt>Component</tt> displaying the video associated
- * with the participant depicted by this instance.
- *
- * @param video the visual <tt>Component</tt> displaying video which is
- * to be associated with the participant depicted by this instance
- */
- void setVideo(Component video)
- {
- if (this.video != video)
- {
- if (this.video != null)
- videoContainer.remove(this.video);
-
- this.video = video;
-
- if (this.video != null)
- {
- setVisible(true);
- videoContainer.add(this.video, VideoLayout.CENTER_REMOTE);
- }
- else
- setVisible(false);
-
- // Update thumbnails container according to video status.
- if (thumbnailContainer != null)
- {
- if (conferenceMember != null)
- thumbnailContainer
- .updateThumbnail(conferenceMember, (video != null));
- else if (callPeer != null)
- thumbnailContainer
- .updateThumbnail(callPeer, (video != null));
- else if (isLocalUser)
- thumbnailContainer
- .updateThumbnail((video != null));
- }
- }
- }
- }
-}
+/*
+ * 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.gui.main.call.conference;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.impl.gui.main.call.*;
+import net.java.sip.communicator.impl.gui.utils.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.plugin.desktoputil.*;
+import net.java.sip.communicator.plugin.desktoputil.TransparentPanel;
+
+import org.jitsi.util.swing.*;
+
+/**
+ * Extends <tt>BasicConferenceCallPanel</tt> to implement a user interface
+ * <tt>Component</tt> which depicts a <tt>CallConference</tt> with audio and
+ * video and is contained in a <tt>CallPanel</tt>.
+ *
+ * @author Yana Stamcheva
+ * @author Lyubomir Marinov
+ */
+public class VideoConferenceCallPanel
+ extends BasicConferenceCallPanel
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>VideoConferenceCallPanel</tt> class
+ * and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(VideoConferenceCallPanel.class);
+
+ /**
+ * The compile-time flag which indicates whether each video displayed by
+ * <tt>VideoConferenceCallPanel</tt> is to be depicted with an associated
+ * tool bar showing information and controls related to the (local or
+ * remote) peer sending the respective video.
+ */
+ private static final boolean SHOW_TOOLBARS = true;
+
+ /**
+ * The facility which aids this instance with the video-related information.
+ */
+ private final UIVideoHandler2 uiVideoHandler;
+
+ /**
+ * The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about
+ * changes in the video-related information.
+ */
+ private final Observer uiVideoHandlerObserver
+ = new Observer()
+ {
+ public void update(Observable o, Object arg)
+ {
+ updateViewFromModel();
+ }
+ };
+
+ /**
+ * The <tt>VideoContainer</tt> which occupies this whole <tt>Component</tt>
+ * and arranges the visual <tt>Component</tt>s displaying the video
+ * streaming between the local peer/user and the remote peer(s).
+ */
+ private final VideoContainer videoContainer;
+
+ /**
+ * The set of visual <tt>Component</tt>s displaying video streaming between
+ * the local peer/user and the remote peers which are depicted by this
+ * instance.
+ */
+ private final Set<Component> videos = new HashSet<Component>();
+
+ /**
+ * The thumbnail container.
+ */
+ private final ThumbnailConferenceCallPanel thumbnailContainer;
+
+ /**
+ * Initializes a new <tt>VideoConferenceCallPanel</tt> instance which is to
+ * be used by a specific <tt>CallPanel</tt> to depict a specific
+ * <tt>CallConference</tt>. The new instance will depict both the
+ * audio-related and the video-related information.
+ *
+ * @param callPanel the <tt>CallPanel</tt> which will use the new instance
+ * to depict the specified <tt>CallConference</tt>.
+ * @param callConference the <tt>CallConference</tt> to be depicted by the
+ * new instance
+ * @param uiVideoHandler the utility which is to aid the new instance in
+ * dealing with the video-related information
+ */
+ public VideoConferenceCallPanel(
+ CallPanel callPanel,
+ CallConference callConference,
+ UIVideoHandler2 uiVideoHandler)
+ {
+ super(callPanel, callConference);
+
+ this.uiVideoHandler = uiVideoHandler;
+
+ thumbnailContainer
+ = new ThumbnailConferenceCallPanel( callPanel,
+ callConference,
+ uiVideoHandler);
+
+ videoContainer = createVideoContainer();
+
+ /*
+ * Our user interface hierarchy has been initialized so we are ready to
+ * begin receiving events warranting updates of this view from its
+ * model.
+ */
+ uiVideoHandler.addObserver(uiVideoHandlerObserver);
+
+ /*
+ * Notify the super that this instance has completed its initialization
+ * and the view that it implements is ready to be updated from the
+ * model.
+ */
+ initializeComplete();
+ }
+
+ private void addConferenceMemberContainers(
+ ConferenceParticipantContainer cpc)
+ {
+ List<ConferenceParticipantContainer> cmcs
+ = cpc.conferenceMemberContainers;
+
+ if ((cmcs != null) && !cmcs.isEmpty())
+ {
+ for (ConferenceParticipantContainer cmc : cmcs)
+ {
+ if (!cmc.toBeRemoved)
+ {
+ videoContainer.add(
+ cmc.getComponent(),
+ VideoLayout.CENTER_REMOTE);
+ }
+ }
+ }
+ }
+
+ private Component createDefaultPhotoPanel(Call call)
+ {
+ OperationSetServerStoredAccountInfo accountInfo
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetServerStoredAccountInfo.class);
+ ImageIcon photoLabelIcon = null;
+
+ if (accountInfo != null)
+ {
+ byte[] accountImage = AccountInfoUtils.getImage(accountInfo);
+
+ // do not set empty images
+ if ((accountImage != null) && (accountImage.length > 0))
+ photoLabelIcon = new ImageIcon(accountImage);
+ }
+ if (photoLabelIcon == null)
+ {
+ photoLabelIcon
+ = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO));
+ }
+
+ return createDefaultPhotoPanel(photoLabelIcon);
+ }
+
+ private Component createDefaultPhotoPanel(CallPeer callPeer)
+ {
+ byte[] peerImage = CallManager.getPeerImage(callPeer);
+ ImageIcon photoLabelIcon
+ = (peerImage == null)
+ ? new ImageIcon(
+ ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
+ : new ImageIcon(peerImage);
+
+ return createDefaultPhotoPanel(photoLabelIcon);
+ }
+
+ private Component createDefaultPhotoPanel(ConferenceMember conferenceMember)
+ {
+ return
+ createDefaultPhotoPanel(
+ new ImageIcon(
+ ImageLoader.getImage(
+ ImageLoader.DEFAULT_USER_PHOTO)));
+ }
+
+ /**
+ * Creates a new <tt>Component</tt> which is to display a specific
+ * <tt>ImageIcon</tt> representing the photo of a participant in a call.
+ *
+ * @param photoLabelIcon the <tt>ImageIcon</tt> which represents the photo
+ * of a participant in a call and which is to be displayed by the new
+ * <tt>Component</tt>
+ * @return a new <tt>Component</tt> which displays the specified
+ * <tt>photoLabelIcon</tt>
+ */
+ private Component createDefaultPhotoPanel(ImageIcon photoLabelIcon)
+ {
+ JLabel photoLabel = new JLabel();
+
+ photoLabel.setIcon(photoLabelIcon);
+
+ @SuppressWarnings("serial")
+ JPanel photoPanel
+ = new TransparentPanel(new GridBagLayout())
+ {
+ /**
+ * @{inheritDoc}
+ */
+ @Override
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ g = g.create();
+ try
+ {
+ AntialiasingManager.activateAntialiasing(g);
+
+ g.setColor(Color.GRAY);
+ g.fillRoundRect(
+ 0, 0, this.getWidth(), this.getHeight(),
+ 6, 6);
+ }
+ finally
+ {
+ g.dispose();
+ }
+ }
+ };
+
+ photoPanel.setPreferredSize(new Dimension(320, 240));
+
+ GridBagConstraints photoPanelConstraints = new GridBagConstraints();
+
+ photoPanelConstraints.anchor = GridBagConstraints.CENTER;
+ photoPanelConstraints.fill = GridBagConstraints.NONE;
+ photoPanel.add(photoLabel, photoPanelConstraints);
+
+ return photoPanel;
+ }
+
+ /**
+ * Initializes a new <tt>VideoContainer</tt> instance which is to contain
+ * the visual/video <tt>Component</tt>s of the telephony conference depicted
+ * by this instance.
+ */
+ private VideoContainer createVideoContainer()
+ {
+ VideoContainer videoContainer = new VideoContainer(null, true);
+
+ JPanel thumbnailPanel = new JPanel(new BorderLayout());
+ thumbnailPanel.setBackground(Color.DARK_GRAY);
+ thumbnailPanel.add(thumbnailContainer, BorderLayout.NORTH);
+
+ add(thumbnailPanel, BorderLayout.EAST);
+ add(videoContainer, BorderLayout.CENTER);
+
+ return videoContainer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dispose()
+ {
+ try
+ {
+ uiVideoHandler.deleteObserver(uiVideoHandlerObserver);
+ }
+ finally
+ {
+ super.dispose();
+ }
+ }
+
+ /**
+ * Determines whether a specific <tt>ConferenceMember</tt> represents the
+ * same conference participant as a specific <tt>CallPeer</tt>. If the
+ * specified <tt>conferenceMember</tt> is <tt>null</tt>, returns
+ * <tt>true</tt>. Otherwise, determines whether the addresses of the
+ * specified <tt>conferenceMember</tt> and the specified <tt>callPeer</tt>
+ * identify one and the same entity.
+ *
+ * @param conferenceMember the <tt>ConferenceMember</tt> to be checked
+ * whether is represents the same conference participant as the specified
+ * <tt>callPeer</tt>. If it is <tt>null</tt>, <tt>true</tt> is returned.
+ * @param callPeer the <tt>CallPeer</tt> to be checked whether it represents
+ * the same conference participant as the specified
+ * <tt>conferenceMember</tt>
+ * @return <tt>true</tt> if the specified <tt>conferenceMember</tt> and the
+ * specified <tt>callPeer</tt> represent the same conference participant or
+ * the specified <tt>conferenceMember</tt> is <tt>null</tt>; otherwise,
+ * <tt>false</tt>
+ */
+ private boolean isConferenceMemberCallPeer(
+ ConferenceMember conferenceMember,
+ CallPeer callPeer)
+ {
+ return
+ (conferenceMember == null)
+ ? true
+ : CallManager.addressesAreEqual(
+ conferenceMember.getAddress(),
+ callPeer.getAddress());
+ }
+
+ /**
+ * Determines whether a specific <tt>ConferenceMember</tt> represents the
+ * local peer/user. Since this instance depicts a whole telephony
+ * conference, the local peer/user may be participating with multiple
+ * <tt>Call</tt>s in it. The <tt>Call</tt>s may be through different
+ * (local) accounts. That's why the implementation determines whether the
+ * address of the specified <tt>conferenceMember</tt> identifies the address
+ * of a (local) accounts involved in the telephony conference depicted by
+ * this instance.
+ *
+ * @param conferenceMember the <tt>ConferenceMember</tt> to be checked
+ * whether it represents the local peer/user
+ * @return <tt>true</tt> if the specified <tt>conferenceMember</tt>
+ * represents the local peer/user; otherwise, <tt>false</tt>
+ */
+ private boolean isConferenceMemberLocalUser(
+ ConferenceMember conferenceMember)
+ {
+ String address = conferenceMember.getAddress();
+
+ for (Call call : callConference.getCalls())
+ {
+ if (CallManager.addressesAreEqual(
+ address,
+ call.getProtocolProvider().getAccountID()
+ .getAccountAddress()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void removeConferenceMemberContainers(
+ ConferenceParticipantContainer cpc,
+ boolean all)
+ {
+ List<ConferenceParticipantContainer> cmcs
+ = cpc.conferenceMemberContainers;
+
+ if ((cmcs != null) && !cmcs.isEmpty())
+ {
+ Iterator<ConferenceParticipantContainer> i = cmcs.iterator();
+
+ while (i.hasNext())
+ {
+ ConferenceParticipantContainer cmc = i.next();
+
+ if (all || cmc.toBeRemoved)
+ {
+ i.remove();
+
+ videoContainer.remove(cmc.getComponent());
+ cmc.dispose();
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the <tt>ConferenceParticipantContainer</tt>s which depict the
+ * <tt>ConferenceMember</tt>s of the <tt>CallPeer</tt> depicted by a
+ * specific <tt>ConferenceParticipantContainer</tt>.
+ *
+ * @param cpc the <tt>ConferenceParticipantContainer</tt> which depicts the
+ * <tt>CallPeer</tt> whose <tt>ConferenceMember</tt>s are to be depicted
+ * @param videos the visual <tt>Component</tt>s displaying video streaming
+ * from the remote peer (represented by <tt>cpc</tt>) to the local peer/user
+ * @param videoTelephony the <tt>OperationSetVideoTelephony</tt> which
+ * retrieved the specified <tt>videos</tt> from the <tt>CallPeer</tt>
+ * depicted by <tt>cpc</tt>. While the <tt>CallPeer</tt> could be queried
+ * for it, such a query would waste more resources at run time given that
+ * the invoker has it already.
+ */
+ private void updateConferenceMemberContainers(
+ ConferenceParticipantContainer cpc,
+ List<Component> videos,
+ OperationSetVideoTelephony videoTelephony)
+ {
+ CallPeer callPeer = (CallPeer) cpc.getParticipant();
+ List<ConferenceParticipantContainer> cmcs
+ = cpc.conferenceMemberContainers;
+
+ /*
+ * Invalidate all conferenceMemberContainers. Then validate which of
+ * them are to remain and which of them are to really be removed
+ * later on.
+ */
+ if (cmcs != null)
+ {
+ for (ConferenceParticipantContainer cmc : cmcs)
+ cmc.toBeRemoved = true;
+ }
+
+ /*
+ * Depict the remote videos. They may or may not be associated with
+ * ConferenceMembers so the ConferenceMembers which have no
+ * associated videos will be depicted afterwards.
+ */
+ if (videos != null)
+ {
+ Component video = cpc.getVideo();
+
+ for (Component conferenceMemberVideo : videos)
+ {
+ /*
+ * One of the remote videos is already used to depict the
+ * callPeer.
+ */
+ if (conferenceMemberVideo == video)
+ continue;
+
+ boolean addNewConferenceParticipantContainer = true;
+ ConferenceMember conferenceMember
+ = videoTelephony.getConferenceMember(
+ callPeer,
+ conferenceMemberVideo);
+
+ if (cmcs == null)
+ {
+ cmcs = new LinkedList<ConferenceParticipantContainer>();
+ cpc.conferenceMemberContainers = cmcs;
+ }
+ else
+ {
+ for (ConferenceParticipantContainer cmc : cmcs)
+ {
+ Object cmcParticipant = cmc.getParticipant();
+
+ if (conferenceMember == null)
+ {
+ if (cmcParticipant == callPeer)
+ {
+ Component cmcVideo = cmc.getVideo();
+
+ if (cmcVideo == null)
+ {
+ cmc.setVideo(conferenceMemberVideo);
+ cmc.toBeRemoved = false;
+ addNewConferenceParticipantContainer
+ = false;
+ break;
+ }
+ else if (cmcVideo == conferenceMemberVideo)
+ {
+ cmc.toBeRemoved = false;
+ addNewConferenceParticipantContainer
+ = false;
+ break;
+ }
+ }
+ }
+ else if (cmcParticipant == conferenceMember)
+ {
+ cmc.setVideo(conferenceMemberVideo);
+ cmc.toBeRemoved = false;
+ addNewConferenceParticipantContainer = false;
+ break;
+ }
+ }
+ }
+
+ if (addNewConferenceParticipantContainer)
+ {
+ ConferenceParticipantContainer cmc
+ = (conferenceMember == null)
+ ? new ConferenceParticipantContainer(
+ callPeer,
+ conferenceMemberVideo)
+ : new ConferenceParticipantContainer(
+ conferenceMember,
+ conferenceMemberVideo);
+
+ cmcs.add(cmc);
+ }
+ }
+ }
+
+ /*
+ * Depict the ConferenceMembers which have not been depicted yet.
+ * They have no associated videos.
+ */
+ List<ConferenceMember> conferenceMembers
+ = callPeer.getConferenceMembers();
+
+ if (!conferenceMembers.isEmpty())
+ {
+ if (cmcs == null)
+ {
+ cmcs = new LinkedList<ConferenceParticipantContainer>();
+ cpc.conferenceMemberContainers = cmcs;
+ }
+ for (ConferenceMember conferenceMember : conferenceMembers)
+ {
+ /*
+ * If the callPeer reports itself as a ConferenceMember, then
+ * we've already depicted it with cpc.
+ */
+ if (isConferenceMemberCallPeer(conferenceMember, callPeer))
+ continue;
+ /*
+ * If the callPeer reports the local peer/user as a
+ * ConferenceMember, then we've already depicted it.
+ */
+ if (isConferenceMemberLocalUser(conferenceMember))
+ continue;
+
+ boolean addNewConferenceParticipantContainer = true;
+
+ for (ConferenceParticipantContainer cmc : cmcs)
+ {
+ if (cmc.getParticipant() == conferenceMember)
+ {
+ /*
+ * It is possible to have a ConferenceMember who is
+ * sending video but we just do not have the SSRC of
+ * that video to associate the video with the
+ * ConferenceMember. In such a case, we may be depicting
+ * the ConferenceMember twice: once with video without a
+ * ConferenceMember and once with a ConferenceMember
+ * without video. This will surely be the case at the
+ * time of this writing with non-focus participants in a
+ * telephony conference hosted on a Jitsi Videobridge.
+ * Such a display is undesirable. If the
+ * conferenceMember is known to send video, we will not
+ * display it until we associated it with a video. This
+ * way, if a ConferenceMember is not sending video, we
+ * will depict it and we can be sure that no video
+ * without a ConferenceMember association will be
+ * depicting it a second time.
+ */
+ if (cmc.toBeRemoved
+ && !conferenceMember
+ .getVideoStatus()
+ .allowsSending())
+ {
+ cmc.setVideo(null);
+ cmc.toBeRemoved = false;
+ }
+ addNewConferenceParticipantContainer = false;
+ break;
+ }
+ }
+
+ if (addNewConferenceParticipantContainer)
+ {
+ ConferenceParticipantContainer cmc
+ = new ConferenceParticipantContainer(
+ conferenceMember,
+ null);
+
+ cmcs.add(cmc);
+ }
+ }
+ }
+
+ if ((cmcs != null) && !cmcs.isEmpty())
+ {
+ removeConferenceMemberContainers(cpc, false);
+ /*
+ * If cpc is already added to the user interface hierarchy of this
+ * instance, then it was there before the update procedure and it
+ * was determined to be appropriate to continue to depict its model.
+ * Consequently, its Component will be neither added to (because it
+ * was already added) nor removed from the user interface hierarchy
+ * of this instance. That's why we have make sure that the
+ * Components of its conferenceMemberContainers are also added to
+ * the user interface.
+ */
+ if (UIVideoHandler2.isAncestor(this, cpc.getComponent()))
+ addConferenceMemberContainers(cpc);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected ConferenceCallPeerRenderer updateViewFromModel(
+ ConferenceCallPeerRenderer callPeerPanel,
+ CallPeer callPeer)
+ {
+ if (callPeer == null)
+ {
+ /*
+ * The local peer/user will be represented by a Call which has a
+ * CallPeer who provides local video. However, if the user has
+ * selected to hide the local video, the local peer/user will not be
+ * represented at all.
+ */
+ Component video = null;
+
+ if (uiVideoHandler.isLocalVideoVisible())
+ {
+ for (Call aCall : callConference.getCalls())
+ {
+ Iterator<? extends CallPeer> callPeerIter
+ = aCall.getCallPeers();
+ OperationSetVideoTelephony videoTelephony
+ = aCall.getProtocolProvider().getOperationSet(
+ OperationSetVideoTelephony.class);
+
+ while (callPeerIter.hasNext())
+ {
+ callPeer = callPeerIter.next();
+
+ if (videoTelephony != null)
+ {
+ try
+ {
+ video
+ = videoTelephony.getLocalVisualComponent(
+ callPeer);
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error(
+ "Failed to retrieve the local video"
+ + " for display",
+ ofe);
+ }
+ if (video != null)
+ break;
+ }
+ }
+ if (video != null)
+ break;
+ }
+ }
+
+ if (callPeer == null)
+ callPeerPanel = null;
+ else
+ {
+ Call call = callPeer.getCall();
+
+ if (callPeerPanel instanceof ConferenceParticipantContainer)
+ {
+ ConferenceParticipantContainer cpc
+ = (ConferenceParticipantContainer) callPeerPanel;
+
+ if (cpc.getParticipant() == call)
+ cpc.setVideo(video);
+ else
+ callPeerPanel = null;
+ }
+ else
+ callPeerPanel = null;
+ if (callPeerPanel == null)
+ {
+ callPeerPanel
+ = new ConferenceParticipantContainer(call, video);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * The specified callPeer will be represented by one of its remote
+ * videos which is not associated with a ConferenceMember or is
+ * associated with a ConferenceMember representing the callPeer
+ * itself.
+ */
+ OperationSetVideoTelephony videoTelephony
+ = callPeer.getProtocolProvider().getOperationSet(
+ OperationSetVideoTelephony.class);
+ List<Component> videos = null;
+ Component video = null;
+
+ if (videoTelephony != null)
+ {
+ videos = videoTelephony.getVisualComponents(callPeer);
+ if ((videos != null) && !videos.isEmpty())
+ {
+ for (Component aVideo : videos)
+ {
+ ConferenceMember conferenceMember
+ = videoTelephony.getConferenceMember(
+ callPeer,
+ aVideo);
+
+ if (isConferenceMemberCallPeer(
+ conferenceMember,
+ callPeer))
+ {
+ video = aVideo;
+ break;
+ }
+ }
+ }
+ }
+
+ ConferenceParticipantContainer cpc = null;
+
+ if (callPeerPanel instanceof ConferenceParticipantContainer)
+ {
+ cpc = (ConferenceParticipantContainer) callPeerPanel;
+ if (cpc.getParticipant() == callPeer)
+ cpc.setVideo(video);
+ else
+ cpc = null;
+ }
+ if (cpc == null)
+ cpc = new ConferenceParticipantContainer(callPeer, video);
+ callPeerPanel = cpc;
+
+ // Update the conferenceMemberContainers of the cpc.
+ updateConferenceMemberContainers(cpc, videos, videoTelephony);
+ }
+ return callPeerPanel;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If {@link #SHOW_TOOLBARS} is <tt>false</tt>, disables the use of
+ * <tt>ConferenceParticipantContainer</tt>. A reason for such a value of
+ * <tt>SHOW_TOOLBARS</tt> may be that the functionality implemented in the
+ * model may not fully support mapping of visual <tt>Component</tt>s
+ * displaying video to telephony conference participants (e.g. in telephony
+ * conferences utilizing the Jitsi Videobridge server-side technology). In
+ * such a case displays the videos only, does not map videos to participants
+ * and does not display participants who do not have videos.
+ */
+ @Override
+ protected void updateViewFromModelInEventDispatchThread()
+ {
+ if (SHOW_TOOLBARS)
+ {
+ super.updateViewFromModelInEventDispatchThread();
+ return;
+ }
+
+ /*
+ * Determine the set of visual Components displaying video streaming
+ * between the local peer/user and the remote peers which are to be
+ * depicted by this instance.
+ */
+ Component localVideo = null;
+ Set<Component> videos = new HashSet<Component>();
+
+ for (Call call : callConference.getCalls())
+ {
+ OperationSetVideoTelephony videoTelephony
+ = call.getProtocolProvider().getOperationSet(
+ OperationSetVideoTelephony.class);
+
+ if (videoTelephony == null)
+ continue;
+
+ Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ CallPeer callPeer = callPeerIter.next();
+
+ /*
+ * TODO VideoConferenceCallPanel respects
+ * UIVideoHandler2.isLocalVideoVisible() in order to react to
+ * the associated button at the bottom of the CallPanel.
+ * However, it does not add a close button on top of the local
+ * video in contrast to OneToOneCallPeerPanel. Overall, the
+ * result is questionable.
+ */
+ if (uiVideoHandler.isLocalVideoVisible()
+ && (localVideo == null))
+ {
+ try
+ {
+ localVideo
+ = videoTelephony.getLocalVisualComponent(callPeer);
+ }
+ catch (OperationFailedException ofe)
+ {
+ /*
+ * We'll just try to get the local video through another
+ * CallPeer then.
+ */
+ }
+ if (localVideo != null)
+ videos.add(localVideo);
+ }
+
+ List<Component> callPeerRemoteVideos
+ = videoTelephony.getVisualComponents(callPeer);
+
+ videos.addAll(callPeerRemoteVideos);
+ }
+ }
+
+ /*
+ * Remove the Components of this view which are no longer present in the
+ * model.
+ */
+ Iterator<Component> thisVideoIter = this.videos.iterator();
+
+ while (thisVideoIter.hasNext())
+ {
+ Component thisVideo = thisVideoIter.next();
+
+ if (!videos.contains(thisVideo))
+ {
+ thisVideoIter.remove();
+ videoContainer.remove(thisVideo);
+ }
+
+ /*
+ * If a video is known to be depicted by this view and is still
+ * present in the model, then we could remove it from the set of
+ * videos present in the model in order to prevent going through the
+ * procedure of adding it to this view. However, we choose to play
+ * on the safe side.
+ */
+ }
+
+ /*
+ * Add the Components of the model which are not depicted by this view.
+ */
+ for (Component video : videos)
+ {
+ if (!UIVideoHandler2.isAncestor(videoContainer, video))
+ {
+ this.videos.add(video);
+ videoContainer.add(
+ video,
+ (video == localVideo)
+ ? VideoLayout.LOCAL
+ : VideoLayout.CENTER_REMOTE);
+ }
+ }
+ }
+
+ @Override
+ protected void viewForModelAdded(
+ ConferenceCallPeerRenderer callPeerPanel,
+ CallPeer callPeer)
+ {
+ videoContainer.add(
+ callPeerPanel.getComponent(),
+ VideoLayout.CENTER_REMOTE);
+ if ((callPeer != null)
+ && (callPeerPanel instanceof ConferenceParticipantContainer))
+ {
+ addConferenceMemberContainers(
+ (ConferenceParticipantContainer) callPeerPanel);
+ }
+ }
+
+ @Override
+ protected void viewForModelRemoved(
+ ConferenceCallPeerRenderer callPeerPanel,
+ CallPeer callPeer)
+ {
+ videoContainer.remove(callPeerPanel.getComponent());
+ if ((callPeer != null)
+ && (callPeerPanel instanceof ConferenceParticipantContainer))
+ {
+ removeConferenceMemberContainers(
+ (ConferenceParticipantContainer) callPeerPanel,
+ true);
+ }
+ }
+
+ /**
+ * Implements an AWT <tt>Component</tt> which contains the user interface
+ * elements depicting a specific participant in the telephony conference
+ * depicted by a <tt>VideoConferenceCallPanel</tt>.
+ */
+ private class ConferenceParticipantContainer
+ extends TransparentPanel
+ implements ConferenceCallPeerRenderer
+ {
+ /**
+ * The list of <tt>ConferenceParticipantContainer</tt>s which represent
+ * the <tt>ConferenceMember</tt>s of the participant represented by this
+ * instance. Since a <tt>CallPeer</tt> may send the local peer/user
+ * multiple videos without providing a way to associate a
+ * ConferenceMember with each one of them, the list may contain
+ * <tt>ConferenceParticipantContainer</tt>s which do not represent a
+ * specific <tt>ConferenceMember</tt> instance but rather a video sent
+ * by a <tt>CallPeer</tt> to the local peer/user which looks like (in
+ * the terms of <tt>VideoConferenceCallPanel) a member of a conference
+ * organized by the <tt>CallPeer</tt> in question.
+ * <p>
+ * Implements a state which is private to
+ * <tt>VideoConferenceCallPanel</tt> and is of no concern to
+ * <tt>ConferenceParticipantContainer</tt>.
+ * </p>
+ */
+ List<ConferenceParticipantContainer> conferenceMemberContainers;
+
+ /**
+ * The indicator which determines whether this instance is to be removed
+ * because it has become out-of-date, obsolete, unnecessary.
+ * <p>
+ * Implements a state which is private to
+ * <tt>VideoConferenceCallPanel</tt> and is of no concern to
+ * <tt>ConferenceParticipantContainer</tt>.
+ * </p>
+ */
+ boolean toBeRemoved;
+
+ /**
+ * The <tt>BasicConferenceParticipantPanel</tt> which is displayed at
+ * the bottom of this instance, bellow the {@link #video} (i.e.
+ * {@link #videoContainer}) and is referred to as the tool bar.
+ */
+ private final BasicConferenceParticipantPanel<?> toolBar;
+
+ /**
+ * The visual <tt>Component</tt>, if any, displaying video which is
+ * depicted by this instance.
+ */
+ private Component video;
+
+ /**
+ * The <tt>VideoContainer</tt> which lays out the video depicted by this
+ * instance i.e. {@link #video}.
+ */
+ private final VideoContainer videoContainer;
+
+ /**
+ * The <tt>CallPeer</tt> associated with this container, if it has been
+ * created to represent a <tt>CallPeer</tt>.
+ */
+ private CallPeer callPeer;
+
+ /**
+ * The <tt>conferenceMember</tt> associated with this container, if it
+ * has been created to represent a <tt>conferenceMember</tt>.
+ */
+ private ConferenceMember conferenceMember;
+
+ /**
+ * Indicates that this container contains information for the local
+ * user.
+ */
+ private boolean isLocalUser;
+
+ /**
+ * Initializes a new <tt>ConferenceParticipantContainer</tt> instance
+ * which is to depict the local peer/user.
+ *
+ * @param call a <tt>Call</tt> which is to provide information about the
+ * local peer/user
+ * @param video the visual <tt>Component</tt>, if any, displaying the
+ * video streaming from the local peer/user to the remote peer(s)
+ */
+ public ConferenceParticipantContainer(Call call, Component video)
+ {
+ this(
+ createDefaultPhotoPanel(call),
+ video,
+ new ConferencePeerPanel(
+ VideoConferenceCallPanel.this,
+ call,
+ true),
+ null, null, true);
+ }
+
+ public ConferenceParticipantContainer(
+ CallPeer callPeer,
+ Component video)
+ {
+ this( createDefaultPhotoPanel(callPeer),
+ video,
+ new ConferencePeerPanel(
+ VideoConferenceCallPanel.this,
+ callPeer,
+ true),
+ callPeer, null, false);
+ }
+
+ private ConferenceParticipantContainer(
+ Component noVideo,
+ Component video,
+ BasicConferenceParticipantPanel<?> toolBar,
+ CallPeer callPeer,
+ ConferenceMember conferenceMember,
+ boolean isLocalUser)
+ {
+ super(new BorderLayout());
+
+ this.callPeer = callPeer;
+ this.conferenceMember = conferenceMember;
+ this.isLocalUser = isLocalUser;
+
+ videoContainer = new VideoContainer(noVideo, false);
+ add(videoContainer, BorderLayout.CENTER);
+
+ this.toolBar = toolBar;
+ if (this.toolBar != null)
+ add(this.toolBar, BorderLayout.SOUTH);
+
+ if (video != null)
+ {
+ setVideo(video);
+ }
+ else
+ setVisible(false);
+ }
+
+ public ConferenceParticipantContainer(
+ ConferenceMember conferenceMember,
+ Component video)
+ {
+ this(
+ createDefaultPhotoPanel(conferenceMember),
+ video,
+ new ConferenceMemberPanel(
+ VideoConferenceCallPanel.this,
+ conferenceMember,
+ true),
+ null, conferenceMember, false);
+ }
+
+ public void dispose()
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.dispose();
+
+ // Dispose of the conferenceMemberContainers if any.
+ /*
+ * XXX The field conferenceMemberContainers implements a state
+ * private to VideoConferenceCallPanel which the latter makes sure
+ * to access on the AWT event dispatching thread only. Since we are
+ * going out of our way here to help VideoConferenceCallPanel,
+ * ensure that the mentioned synchronization rule is not violated.
+ */
+ CallManager.assertIsEventDispatchingThread();
+ if (conferenceMemberContainers != null)
+ {
+ for (ConferenceParticipantContainer cmc
+ : conferenceMemberContainers)
+ {
+ cmc.dispose();
+ }
+ }
+ }
+
+ public CallPanel getCallPanel()
+ {
+ return getCallRenderer().getCallContainer();
+ }
+
+ public SwingCallRenderer getCallRenderer()
+ {
+ return VideoConferenceCallPanel.this;
+ }
+
+ public Component getComponent()
+ {
+ return this;
+ }
+
+ private ConferenceCallPeerRenderer
+ getConferenceCallPeerRendererDelegate()
+ {
+ return
+ (toolBar instanceof ConferenceCallPeerRenderer)
+ ? (ConferenceCallPeerRenderer) toolBar
+ : null;
+ }
+
+ /**
+ * Gets the conference participant depicted by this instance.
+ *
+ * @return the conference participant depicted by this instance
+ */
+ public Object getParticipant()
+ {
+ return (toolBar == null) ? null : toolBar.getParticipant();
+ }
+
+ public Component getVideo()
+ {
+ return video;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only. Otherwise, returns <tt>false</tt>.
+ */
+ public boolean isLocalVideoVisible()
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ return (delegate == null) ? false : delegate.isLocalVideoVisible();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void printDTMFTone(char dtmfChar)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.printDTMFTone(dtmfChar);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void securityNegotiationStarted(
+ CallPeerSecurityNegotiationStartedEvent ev)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.securityNegotiationStarted(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void securityOff(CallPeerSecurityOffEvent ev)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.securityOff(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void securityOn(CallPeerSecurityOnEvent ev)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.securityOn(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void securityPending()
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.securityPending();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void securityTimeout(CallPeerSecurityTimeoutEvent ev)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.securityTimeout(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setErrorReason(String reason)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setErrorReason(reason);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setLocalVideoVisible(boolean visible)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setLocalVideoVisible(visible);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setMute(boolean mute)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setMute(mute);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setOnHold(boolean onHold)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setOnHold(onHold);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setPeerImage(byte[] image)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setPeerImage(image);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setPeerName(String name)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setPeerName(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setPeerState(
+ CallPeerState oldState,
+ CallPeerState newState,
+ String stateString)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setPeerState(oldState, newState, stateString);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Delegates to the <tt>toolBar</tt>, if the latter implements
+ * <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
+ * container only.
+ */
+ public void setSecurityPanelVisible(boolean visible)
+ {
+ ConferenceCallPeerRenderer delegate
+ = getConferenceCallPeerRendererDelegate();
+
+ if (delegate != null)
+ delegate.setSecurityPanelVisible(visible);
+ }
+
+ /**
+ * Sets the visual <tt>Component</tt> displaying the video associated
+ * with the participant depicted by this instance.
+ *
+ * @param video the visual <tt>Component</tt> displaying video which is
+ * to be associated with the participant depicted by this instance
+ */
+ void setVideo(Component video)
+ {
+ if (this.video != video)
+ {
+ if (this.video != null)
+ videoContainer.remove(this.video);
+
+ this.video = video;
+
+ if (this.video != null)
+ {
+ setVisible(true);
+ videoContainer.add(this.video, VideoLayout.CENTER_REMOTE);
+ }
+ else
+ setVisible(false);
+
+ // Update thumbnails container according to video status.
+ if (thumbnailContainer != null)
+ {
+ if (conferenceMember != null)
+ thumbnailContainer
+ .updateThumbnail(conferenceMember, (video != null));
+ else if (callPeer != null)
+ thumbnailContainer
+ .updateThumbnail(callPeer, (video != null));
+ else if (isLocalUser)
+ thumbnailContainer
+ .updateThumbnail((video != null));
+ }
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/libjitsi/LibJitsiActivator.java b/src/net/java/sip/communicator/impl/libjitsi/LibJitsiActivator.java
index a2b21cf..0a1833e 100644
--- a/src/net/java/sip/communicator/impl/libjitsi/LibJitsiActivator.java
+++ b/src/net/java/sip/communicator/impl/libjitsi/LibJitsiActivator.java
@@ -1,60 +1,60 @@
-/*
- * 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.libjitsi;
-
-import java.lang.reflect.*;
-
-import org.jitsi.service.libjitsi.*;
-import org.osgi.framework.*;
-
-public class LibJitsiActivator
- implements BundleActivator
-{
- public void start(BundleContext bundleContext)
- throws Exception
- {
- /*
- * XXX To start/initialize the libjitsi library, simply call
- * LibJitsi#start(). The following is a temporary workaround for the
- * benefit of the Jitsi VideoBridge project (which uses Jitsi's libjitsi
- * bundle and runs on an incomplete OSGi implementation) and not the
- * Jitsi project.
- */
- Method start;
-
- try
- {
- start = LibJitsi.class.getDeclaredMethod("start", Object.class);
- if (Modifier.isStatic(start.getModifiers()))
- {
- start.setAccessible(true);
- if (!start.isAccessible())
- start = null;
- }
- else
- start = null;
- }
- catch (NoSuchMethodException nsme)
- {
- start = null;
- }
- catch (SecurityException se)
- {
- start = null;
- }
- if (start == null)
- LibJitsi.start();
- else
- start.invoke(null, bundleContext);
- }
-
- public void stop(BundleContext bundleContext)
- throws Exception
- {
- LibJitsi.stop();
- }
-}
+/*
+ * 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.libjitsi;
+
+import java.lang.reflect.*;
+
+import org.jitsi.service.libjitsi.*;
+import org.osgi.framework.*;
+
+public class LibJitsiActivator
+ implements BundleActivator
+{
+ public void start(BundleContext bundleContext)
+ throws Exception
+ {
+ /*
+ * XXX To start/initialize the libjitsi library, simply call
+ * LibJitsi#start(). The following is a temporary workaround for the
+ * benefit of the Jitsi Videobridge project (which uses Jitsi's libjitsi
+ * bundle and runs on an incomplete OSGi implementation) and not the
+ * Jitsi project.
+ */
+ Method start;
+
+ try
+ {
+ start = LibJitsi.class.getDeclaredMethod("start", Object.class);
+ if (Modifier.isStatic(start.getModifiers()))
+ {
+ start.setAccessible(true);
+ if (!start.isAccessible())
+ start = null;
+ }
+ else
+ start = null;
+ }
+ catch (NoSuchMethodException nsme)
+ {
+ start = null;
+ }
+ catch (SecurityException se)
+ {
+ start = null;
+ }
+ if (start == null)
+ LibJitsi.start();
+ else
+ start.invoke(null, bundleContext);
+ }
+
+ public void stop(BundleContext bundleContext)
+ throws Exception
+ {
+ LibJitsi.stop();
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerMediaHandlerJabberGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerMediaHandlerJabberGTalkImpl.java
index 233e92c..976baae 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerMediaHandlerJabberGTalkImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallPeerMediaHandlerJabberGTalkImpl.java
@@ -1,604 +1,604 @@
-/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
- *
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
- */
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-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.neomedia.*;
-
-import ch.imvs.sdes4j.srtp.*;
-
-/**
- * An implementation of the <tt>CallPeerMediaHandler</tt> abstract class for the
- * common part of Jabber and Gtalk protocols.
- *
- * @author Vincent Lucas
- * @author Lyubomir Marinov
- */
-public abstract class AbstractCallPeerMediaHandlerJabberGTalkImpl
- <T extends AbstractCallPeerJabberGTalkImpl<?,?,?>>
- extends CallPeerMediaHandler<T>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>CallPeerMediaHandlerJabberImpl</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(AbstractCallPeerMediaHandlerJabberGTalkImpl.class);
-
- /**
- * Indicates if the <tt>CallPeer</tt> will support </tt>inputevt</tt>
- * extension (i.e. will be able to be remote-controlled).
- */
- private boolean localInputEvtAware = false;
-
- /**
- * Creates a new handler that will be managing media streams for
- * <tt>peer</tt>.
- *
- * @param peer that <tt>AbstractCallPeerJabberGTalkImpl</tt> instance that
- * we will be managing media for.
- */
- public AbstractCallPeerMediaHandlerJabberGTalkImpl(T peer)
- {
- super(peer, peer);
- }
-
- /**
- * Gets the <tt>inputevt</tt> support: true for enable, false for disable.
- *
- * @return The state of inputevt support: true for enable, false for
- * disable.
- */
- public boolean getLocalInputEvtAware()
- {
- return this.localInputEvtAware;
- }
-
- /**
- * Enable or disable <tt>inputevt</tt> support (remote-control).
- *
- * @param enable new state of inputevt support
- */
- public void setLocalInputEvtAware(boolean enable)
- {
- localInputEvtAware = enable;
- }
-
- /**
- * Detects and adds ZRTP available encryption method present in the
- * description given in parameter.
- *
- * @param isInitiator True if the local call instance is the initiator of
- * the call. False otherwise.
- * @param description The DESCRIPTION element of the JINGLE element which
- * contains the PAYLOAD-TYPE and (more important here) the ENCRYPTION.
- * @param mediaType The type of media (AUDIO or VIDEO).
- */
- protected void addZrtpAdvertisedEncryptions(
- boolean isInitiator,
- RtpDescriptionPacketExtension description,
- MediaType mediaType)
- {
- CallPeer peer = getPeer();
- Call call = peer.getCall();
-
- /*
- * ZRTP is not supported in telephony conferences utilizing the
- * server-side technology Jitsi Videobridge yet.
- */
- if (call.getConference().isJitsiVideoBridge())
- return;
-
- // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element for
- // a given DESCRIPTION.
- EncryptionPacketExtension encryptionPacketExtension
- = description.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- if(encryptionPacketExtension != null)
- {
- AccountID accountID = peer.getProtocolProvider().getAccountID();
-
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- ZrtpControl.PROTO_NAME)
- && call.isSipZrtpAttribute())
- {
- // ZRTP
- ZrtpHashPacketExtension zrtpHashPacketExtension
- = encryptionPacketExtension.getFirstChildOfType(
- ZrtpHashPacketExtension.class);
-
- if ((zrtpHashPacketExtension != null)
- && (zrtpHashPacketExtension.getValue() != null))
- {
- addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
- }
- }
- }
- }
-
- /**
- * Detects and adds SDES available encryption method present in the
- * description given in parameter.
- *
- * @param isInitiator True if the local call instance is the initiator of
- * the call. False otherwise.
- * @param description The DESCRIPTION element of the JINGLE element which
- * contains the PAYLOAD-TYPE and (more important here) the ENCRYPTION.
- * @param mediaType The type of media (AUDIO or VIDEO).
- */
- protected void addSDesAdvertisedEncryptions(
- boolean isInitiator,
- RtpDescriptionPacketExtension description,
- MediaType mediaType)
- {
- CallPeer peer = getPeer();
-
- /*
- * SDES is not supported in telephony conferences utilizing the
- * server-side technology Jitsi Videobridge yet.
- */
- if (peer.getCall().getConference().isJitsiVideoBridge())
- return;
-
- // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element for
- // a given DESCRIPTION.
- EncryptionPacketExtension encryptionPacketExtension
- = description.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- if(encryptionPacketExtension != null)
- {
- AccountID accountID = peer.getProtocolProvider().getAccountID();
-
- // SDES
- if(accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- SDesControl.PROTO_NAME))
- {
- SrtpControls srtpControls = getSrtpControls();
- SDesControl sdesControl
- = (SDesControl)
- srtpControls.getOrCreate(
- mediaType,
- SrtpControlType.SDES);
- SrtpCryptoAttribute selectedSdes
- = selectSdesCryptoSuite(
- isInitiator,
- sdesControl,
- encryptionPacketExtension);
-
- if(selectedSdes != null)
- {
- //found an SDES answer, remove all other controls
- removeAndCleanupOtherSrtpControls(
- mediaType,
- SrtpControlType.SDES);
- addAdvertisedEncryptionMethod(SrtpControlType.SDES);
- }
- else
- {
- sdesControl.cleanup();
- srtpControls.remove(mediaType, SrtpControlType.SDES);
- }
- }
- }
- // If we were initiating the encryption, and the remote peer does not
- // manage it, then we must remove the unusable SDES srtpControl.
- else if(isInitiator)
- {
- // SDES
- SrtpControl sdesControl
- = getSrtpControls().remove(mediaType, SrtpControlType.SDES);
-
- if (sdesControl != null)
- sdesControl.cleanup();
- }
- }
-
- /**
- * Returns the selected SDES crypto suite selected.
- *
- * @param isInitiator True if the local call instance is the initiator of
- * the call. False otherwise.
- * @param sDesControl The SDES based SRTP MediaStream encryption
- * control.
- * @param encryptionPacketExtension The ENCRYPTION element received from the
- * remote peer. This may contain the SDES crypto suites available for the
- * remote peer.
- *
- * @return The selected SDES crypto suite supported by both the local and
- * the remote peer. Or null, if there is no crypto suite supported by both
- * of the peers.
- */
- protected SrtpCryptoAttribute selectSdesCryptoSuite(
- boolean isInitiator,
- SDesControl sDesControl,
- EncryptionPacketExtension encryptionPacketExtension)
- {
- List<CryptoPacketExtension> cryptoPacketExtensions
- = encryptionPacketExtension.getCryptoList();
- List<SrtpCryptoAttribute> peerAttributes
- = new ArrayList<SrtpCryptoAttribute>(cryptoPacketExtensions.size());
-
- for (CryptoPacketExtension cpe : cryptoPacketExtensions)
- peerAttributes.add(cpe.toSrtpCryptoAttribute());
-
- return
- isInitiator
- ? sDesControl.initiatorSelectAttribute(peerAttributes)
- : sDesControl.responderSelectAttribute(peerAttributes);
- }
-
- /**
- * Returns if the remote peer supports ZRTP.
- *
- * @param encryptionPacketExtension The ENCRYPTION element received from
- * the remote peer. This may contain the ZRTP packet element for the remote
- * peer.
- *
- * @return True if the remote peer supports ZRTP. False, otherwise.
- */
- protected boolean isRemoteZrtpCapable(
- EncryptionPacketExtension encryptionPacketExtension)
- {
- return
- (encryptionPacketExtension.getFirstChildOfType(
- ZrtpHashPacketExtension.class)
- != null);
- }
-
- /**
- * Sets ZRTP element to the ENCRYPTION element of the DESCRIPTION for a
- * given media.
- *
- * @param mediaType The type of media we are modifying the DESCRIPTION to
- * integrate the ENCRYPTION element.
- * @param description The element containing the media DESCRIPTION and its
- * encryption.
- * @param remoteDescription The element containing the media DESCRIPTION and
- * its encryption for the remote peer. Null, if the local peer is the
- * initiator of the call.
- *
- * @return True if the ZRTP element has been added to encryption. False,
- * otherwise.
- */
- protected boolean setZrtpEncryptionToDescription(
- MediaType mediaType,
- RtpDescriptionPacketExtension description,
- RtpDescriptionPacketExtension remoteDescription)
- {
- CallPeer peer = getPeer();
- Call call = peer.getCall();
-
- /*
- * ZRTP is not supported in telephony conferences utilizing the
- * server-side technology Jitsi Videobridge yet.
- */
- if (call.getConference().isJitsiVideoBridge())
- return false;
-
- boolean isRemoteZrtpCapable;
-
- if (remoteDescription == null)
- isRemoteZrtpCapable = true;
- else
- {
- // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element
- // for a given DESCRIPTION.
- EncryptionPacketExtension remoteEncryption
- = remoteDescription.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- isRemoteZrtpCapable
- = (remoteEncryption != null)
- && isRemoteZrtpCapable(remoteEncryption);
- }
-
- boolean zrtpHashSet = false; // Will become true if at least one is set.
-
- if (isRemoteZrtpCapable)
- {
- AccountID accountID = peer.getProtocolProvider().getAccountID();
-
- if(accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- ZrtpControl.PROTO_NAME)
- && call.isSipZrtpAttribute())
- {
- ZrtpControl zrtpControl
- = (ZrtpControl)
- getSrtpControls().getOrCreate(
- mediaType,
- SrtpControlType.ZRTP);
- int numberSupportedVersions
- = zrtpControl.getNumberSupportedVersions();
-
- for (int i = 0; i < numberSupportedVersions; i++)
- {
- String helloHash[] = zrtpControl.getHelloHashSep(i);
-
- if ((helloHash != null) && (helloHash[1].length() > 0))
- {
- ZrtpHashPacketExtension hash
- = new ZrtpHashPacketExtension();
-
- hash.setVersion(helloHash[0]);
- hash.setValue(helloHash[1]);
-
- EncryptionPacketExtension encryption
- = description.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- if (encryption == null)
- {
- encryption = new EncryptionPacketExtension();
- description.addChildExtension(encryption);
- }
- encryption.addChildExtension(hash);
- zrtpHashSet = true;
- }
- }
- }
- }
-
- return zrtpHashSet;
- }
-
- /**
- * Sets SDES element(s) to the ENCRYPTION element of the DESCRIPTION for a
- * given media.
- *
- * @param mediaType The type of media we are modifying the DESCRIPTION to
- * integrate the ENCRYPTION element.
- * @param localDescription The element containing the media DESCRIPTION and
- * its encryption.
- * @param remoteDescription The element containing the media DESCRIPTION and
- * its encryption for the remote peer. Null, if the local peer is the
- * initiator of the call.
- *
- * @return True if the crypto element has been added to encryption. False,
- * otherwise.
- */
- protected boolean setSDesEncryptionToDescription(
- MediaType mediaType,
- RtpDescriptionPacketExtension localDescription,
- RtpDescriptionPacketExtension remoteDescription)
- {
- CallPeer peer = getPeer();
-
- /*
- * SDES is not supported in telephony conferences utilizing the
- * server-side technology Jitsi Videobridge yet.
- */
- if (peer.getCall().getConference().isJitsiVideoBridge())
- return false;
-
- AccountID accountID = peer.getProtocolProvider().getAccountID();
-
- // check if SDES and encryption is enabled at all
- if (accountID.getAccountPropertyBoolean(
- ProtocolProviderFactory.DEFAULT_ENCRYPTION,
- true)
- && accountID.isEncryptionProtocolEnabled(
- SDesControl.PROTO_NAME))
- {
- // get or create the control
- SrtpControls srtpControls = getSrtpControls();
- SDesControl sdesControl
- = (SDesControl)
- srtpControls.getOrCreate(mediaType, SrtpControlType.SDES);
- // set the enabled ciphers suites
- String ciphers
- = accountID.getAccountPropertyString(
- ProtocolProviderFactory.SDES_CIPHER_SUITES);
-
- if (ciphers == null)
- {
- ciphers =
- JabberActivator.getResources().getSettingsString(
- SDesControl.SDES_CIPHER_SUITES);
- }
- sdesControl.setEnabledCiphers(Arrays.asList(ciphers.split(",")));
-
- // act as initiator
- if (remoteDescription == null)
- {
- EncryptionPacketExtension localEncryption
- = localDescription.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- if(localEncryption == null)
- {
- localEncryption = new EncryptionPacketExtension();
- localDescription.addChildExtension(localEncryption);
- }
- for(SrtpCryptoAttribute ca:
- sdesControl.getInitiatorCryptoAttributes())
- {
- CryptoPacketExtension crypto
- = new CryptoPacketExtension(ca);
- localEncryption.addChildExtension(crypto);
- }
-
- return true;
- }
- // act as responder
- else
- {
- // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION
- // element for a given DESCRIPTION.
- EncryptionPacketExtension remoteEncryption
- = remoteDescription.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- if(remoteEncryption != null)
- {
- SrtpCryptoAttribute selectedSdes = selectSdesCryptoSuite(
- false,
- sdesControl,
- remoteEncryption);
-
- if(selectedSdes != null)
- {
- EncryptionPacketExtension localEncryption
- = localDescription.getFirstChildOfType(
- EncryptionPacketExtension.class);
-
- if(localEncryption == null)
- {
- localEncryption = new EncryptionPacketExtension();
- localDescription.addChildExtension(localEncryption);
- }
-
- CryptoPacketExtension crypto
- = new CryptoPacketExtension(selectedSdes);
-
- localEncryption.addChildExtension(crypto);
-
- return true;
- }
- else
- {
- // none of the offered suites match, destroy the sdes
- // control
- sdesControl.cleanup();
- srtpControls.remove(mediaType, SrtpControlType.SDES);
- logger.warn(
- "Received unsupported sdes crypto attribute");
- }
- }
- else
- {
- // peer doesn't offer any SDES attribute, destroy the sdes
- // control
- sdesControl.cleanup();
- srtpControls.remove(mediaType, SrtpControlType.SDES);
- }
- }
- }
-
- return false;
- }
-
- /**
- * Selects the preferred encryption protocol (only used by the callee).
- *
- * @param mediaType The type of media (AUDIO or VIDEO).
- * @param localDescription The element containing the media DESCRIPTION and
- * its encryption.
- * @param remoteDescription The element containing the media DESCRIPTION and
- * its encryption for the remote peer; <tt>null</tt> if the local peer is
- * the initiator of the call.
- */
- protected void setAndAddPreferredEncryptionProtocol(
- MediaType mediaType,
- RtpDescriptionPacketExtension localDescription,
- RtpDescriptionPacketExtension remoteDescription)
- {
- // Sets ZRTP or SDES, depending on the preferences for this account.
- List<String> preferredEncryptionProtocols
- = getPeer()
- .getProtocolProvider()
- .getAccountID()
- .getSortedEnabledEncryptionProtocolList();
-
- for(String preferredEncryptionProtocol : preferredEncryptionProtocols)
- {
- String protoName
- = preferredEncryptionProtocol.substring(
- ProtocolProviderFactory.ENCRYPTION_PROTOCOL.length()
- + 1);
-
- if (setAndAddPreferredEncryptionProtocol(
- protoName,
- mediaType,
- localDescription,
- remoteDescription))
- {
- // Stop once an encryption advertisement has been chosen.
- return;
- }
- }
- }
-
- /**
- * Selects a specific encryption protocol if it is the preferred (only used
- * by the callee).
- *
- * @param protoName the name of the encryption protocol which is to be
- * selected
- * @param mediaType The type of media (AUDIO or VIDEO).
- * @param localDescription The element containing the media DESCRIPTION and
- * its encryption.
- * @param remoteDescription The element containing the media DESCRIPTION and
- * its encryption for the remote peer; <tt>null</tt> if the local peer is
- * the initiator of the call.
- * @return <tt>true</tt> if the specified encryption protocol has been
- * selected; <tt>false</tt>, otherwise
- */
- protected boolean setAndAddPreferredEncryptionProtocol(
- String protoName,
- MediaType mediaType,
- RtpDescriptionPacketExtension localDescription,
- RtpDescriptionPacketExtension remoteDescription)
- {
- /*
- * Neither SDES nor ZRTP is supported in telephony conferences utilizing
- * the server-side technology Jitsi Videobridge yet.
- */
- if (getPeer().isJitsiVideoBridge())
- return false;
-
- // SDES
- if(SDesControl.PROTO_NAME.equals(protoName))
- {
- addSDesAdvertisedEncryptions(
- false,
- remoteDescription,
- mediaType);
- if(setSDesEncryptionToDescription(
- mediaType,
- localDescription,
- remoteDescription))
- {
- // Stop once an encryption advertisement has been chosen.
- return true;
- }
- }
- // ZRTP
- else if(ZrtpControl.PROTO_NAME.equals(protoName))
- {
- if(setZrtpEncryptionToDescription(
- mediaType,
- localDescription,
- remoteDescription))
- {
- addZrtpAdvertisedEncryptions(
- false,
- remoteDescription,
- mediaType);
- // Stop once an encryption advertisement has been chosen.
- return true;
- }
- }
- return false;
- }
-}
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+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.neomedia.*;
+
+import ch.imvs.sdes4j.srtp.*;
+
+/**
+ * An implementation of the <tt>CallPeerMediaHandler</tt> abstract class for the
+ * common part of Jabber and Gtalk protocols.
+ *
+ * @author Vincent Lucas
+ * @author Lyubomir Marinov
+ */
+public abstract class AbstractCallPeerMediaHandlerJabberGTalkImpl
+ <T extends AbstractCallPeerJabberGTalkImpl<?,?,?>>
+ extends CallPeerMediaHandler<T>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>CallPeerMediaHandlerJabberImpl</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(AbstractCallPeerMediaHandlerJabberGTalkImpl.class);
+
+ /**
+ * Indicates if the <tt>CallPeer</tt> will support </tt>inputevt</tt>
+ * extension (i.e. will be able to be remote-controlled).
+ */
+ private boolean localInputEvtAware = false;
+
+ /**
+ * Creates a new handler that will be managing media streams for
+ * <tt>peer</tt>.
+ *
+ * @param peer that <tt>AbstractCallPeerJabberGTalkImpl</tt> instance that
+ * we will be managing media for.
+ */
+ public AbstractCallPeerMediaHandlerJabberGTalkImpl(T peer)
+ {
+ super(peer, peer);
+ }
+
+ /**
+ * Gets the <tt>inputevt</tt> support: true for enable, false for disable.
+ *
+ * @return The state of inputevt support: true for enable, false for
+ * disable.
+ */
+ public boolean getLocalInputEvtAware()
+ {
+ return this.localInputEvtAware;
+ }
+
+ /**
+ * Enable or disable <tt>inputevt</tt> support (remote-control).
+ *
+ * @param enable new state of inputevt support
+ */
+ public void setLocalInputEvtAware(boolean enable)
+ {
+ localInputEvtAware = enable;
+ }
+
+ /**
+ * Detects and adds ZRTP available encryption method present in the
+ * description given in parameter.
+ *
+ * @param isInitiator True if the local call instance is the initiator of
+ * the call. False otherwise.
+ * @param description The DESCRIPTION element of the JINGLE element which
+ * contains the PAYLOAD-TYPE and (more important here) the ENCRYPTION.
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ */
+ protected void addZrtpAdvertisedEncryptions(
+ boolean isInitiator,
+ RtpDescriptionPacketExtension description,
+ MediaType mediaType)
+ {
+ CallPeer peer = getPeer();
+ Call call = peer.getCall();
+
+ /*
+ * ZRTP is not supported in telephony conferences utilizing the
+ * server-side technology Jitsi Videobridge yet.
+ */
+ if (call.getConference().isJitsiVideobridge())
+ return;
+
+ // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element for
+ // a given DESCRIPTION.
+ EncryptionPacketExtension encryptionPacketExtension
+ = description.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ if(encryptionPacketExtension != null)
+ {
+ AccountID accountID = peer.getProtocolProvider().getAccountID();
+
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ ZrtpControl.PROTO_NAME)
+ && call.isSipZrtpAttribute())
+ {
+ // ZRTP
+ ZrtpHashPacketExtension zrtpHashPacketExtension
+ = encryptionPacketExtension.getFirstChildOfType(
+ ZrtpHashPacketExtension.class);
+
+ if ((zrtpHashPacketExtension != null)
+ && (zrtpHashPacketExtension.getValue() != null))
+ {
+ addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
+ }
+ }
+ }
+ }
+
+ /**
+ * Detects and adds SDES available encryption method present in the
+ * description given in parameter.
+ *
+ * @param isInitiator True if the local call instance is the initiator of
+ * the call. False otherwise.
+ * @param description The DESCRIPTION element of the JINGLE element which
+ * contains the PAYLOAD-TYPE and (more important here) the ENCRYPTION.
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ */
+ protected void addSDesAdvertisedEncryptions(
+ boolean isInitiator,
+ RtpDescriptionPacketExtension description,
+ MediaType mediaType)
+ {
+ CallPeer peer = getPeer();
+
+ /*
+ * SDES is not supported in telephony conferences utilizing the
+ * server-side technology Jitsi Videobridge yet.
+ */
+ if (peer.getCall().getConference().isJitsiVideobridge())
+ return;
+
+ // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element for
+ // a given DESCRIPTION.
+ EncryptionPacketExtension encryptionPacketExtension
+ = description.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ if(encryptionPacketExtension != null)
+ {
+ AccountID accountID = peer.getProtocolProvider().getAccountID();
+
+ // SDES
+ if(accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ SDesControl.PROTO_NAME))
+ {
+ SrtpControls srtpControls = getSrtpControls();
+ SDesControl sdesControl
+ = (SDesControl)
+ srtpControls.getOrCreate(
+ mediaType,
+ SrtpControlType.SDES);
+ SrtpCryptoAttribute selectedSdes
+ = selectSdesCryptoSuite(
+ isInitiator,
+ sdesControl,
+ encryptionPacketExtension);
+
+ if(selectedSdes != null)
+ {
+ //found an SDES answer, remove all other controls
+ removeAndCleanupOtherSrtpControls(
+ mediaType,
+ SrtpControlType.SDES);
+ addAdvertisedEncryptionMethod(SrtpControlType.SDES);
+ }
+ else
+ {
+ sdesControl.cleanup();
+ srtpControls.remove(mediaType, SrtpControlType.SDES);
+ }
+ }
+ }
+ // If we were initiating the encryption, and the remote peer does not
+ // manage it, then we must remove the unusable SDES srtpControl.
+ else if(isInitiator)
+ {
+ // SDES
+ SrtpControl sdesControl
+ = getSrtpControls().remove(mediaType, SrtpControlType.SDES);
+
+ if (sdesControl != null)
+ sdesControl.cleanup();
+ }
+ }
+
+ /**
+ * Returns the selected SDES crypto suite selected.
+ *
+ * @param isInitiator True if the local call instance is the initiator of
+ * the call. False otherwise.
+ * @param sDesControl The SDES based SRTP MediaStream encryption
+ * control.
+ * @param encryptionPacketExtension The ENCRYPTION element received from the
+ * remote peer. This may contain the SDES crypto suites available for the
+ * remote peer.
+ *
+ * @return The selected SDES crypto suite supported by both the local and
+ * the remote peer. Or null, if there is no crypto suite supported by both
+ * of the peers.
+ */
+ protected SrtpCryptoAttribute selectSdesCryptoSuite(
+ boolean isInitiator,
+ SDesControl sDesControl,
+ EncryptionPacketExtension encryptionPacketExtension)
+ {
+ List<CryptoPacketExtension> cryptoPacketExtensions
+ = encryptionPacketExtension.getCryptoList();
+ List<SrtpCryptoAttribute> peerAttributes
+ = new ArrayList<SrtpCryptoAttribute>(cryptoPacketExtensions.size());
+
+ for (CryptoPacketExtension cpe : cryptoPacketExtensions)
+ peerAttributes.add(cpe.toSrtpCryptoAttribute());
+
+ return
+ isInitiator
+ ? sDesControl.initiatorSelectAttribute(peerAttributes)
+ : sDesControl.responderSelectAttribute(peerAttributes);
+ }
+
+ /**
+ * Returns if the remote peer supports ZRTP.
+ *
+ * @param encryptionPacketExtension The ENCRYPTION element received from
+ * the remote peer. This may contain the ZRTP packet element for the remote
+ * peer.
+ *
+ * @return True if the remote peer supports ZRTP. False, otherwise.
+ */
+ protected boolean isRemoteZrtpCapable(
+ EncryptionPacketExtension encryptionPacketExtension)
+ {
+ return
+ (encryptionPacketExtension.getFirstChildOfType(
+ ZrtpHashPacketExtension.class)
+ != null);
+ }
+
+ /**
+ * Sets ZRTP element to the ENCRYPTION element of the DESCRIPTION for a
+ * given media.
+ *
+ * @param mediaType The type of media we are modifying the DESCRIPTION to
+ * integrate the ENCRYPTION element.
+ * @param description The element containing the media DESCRIPTION and its
+ * encryption.
+ * @param remoteDescription The element containing the media DESCRIPTION and
+ * its encryption for the remote peer. Null, if the local peer is the
+ * initiator of the call.
+ *
+ * @return True if the ZRTP element has been added to encryption. False,
+ * otherwise.
+ */
+ protected boolean setZrtpEncryptionOnDescription(
+ MediaType mediaType,
+ RtpDescriptionPacketExtension description,
+ RtpDescriptionPacketExtension remoteDescription)
+ {
+ CallPeer peer = getPeer();
+ Call call = peer.getCall();
+
+ /*
+ * ZRTP is not supported in telephony conferences utilizing the
+ * server-side technology Jitsi Videobridge yet.
+ */
+ if (call.getConference().isJitsiVideobridge())
+ return false;
+
+ boolean isRemoteZrtpCapable;
+
+ if (remoteDescription == null)
+ isRemoteZrtpCapable = true;
+ else
+ {
+ // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION element
+ // for a given DESCRIPTION.
+ EncryptionPacketExtension remoteEncryption
+ = remoteDescription.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ isRemoteZrtpCapable
+ = (remoteEncryption != null)
+ && isRemoteZrtpCapable(remoteEncryption);
+ }
+
+ boolean zrtpHashSet = false; // Will become true if at least one is set.
+
+ if (isRemoteZrtpCapable)
+ {
+ AccountID accountID = peer.getProtocolProvider().getAccountID();
+
+ if(accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ ZrtpControl.PROTO_NAME)
+ && call.isSipZrtpAttribute())
+ {
+ ZrtpControl zrtpControl
+ = (ZrtpControl)
+ getSrtpControls().getOrCreate(
+ mediaType,
+ SrtpControlType.ZRTP);
+ int numberSupportedVersions
+ = zrtpControl.getNumberSupportedVersions();
+
+ for (int i = 0; i < numberSupportedVersions; i++)
+ {
+ String helloHash[] = zrtpControl.getHelloHashSep(i);
+
+ if ((helloHash != null) && (helloHash[1].length() > 0))
+ {
+ ZrtpHashPacketExtension hash
+ = new ZrtpHashPacketExtension();
+
+ hash.setVersion(helloHash[0]);
+ hash.setValue(helloHash[1]);
+
+ EncryptionPacketExtension encryption
+ = description.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ if (encryption == null)
+ {
+ encryption = new EncryptionPacketExtension();
+ description.addChildExtension(encryption);
+ }
+ encryption.addChildExtension(hash);
+ zrtpHashSet = true;
+ }
+ }
+ }
+ }
+
+ return zrtpHashSet;
+ }
+
+ /**
+ * Sets SDES element(s) to the ENCRYPTION element of the DESCRIPTION for a
+ * given media.
+ *
+ * @param mediaType The type of media we are modifying the DESCRIPTION to
+ * integrate the ENCRYPTION element.
+ * @param localDescription The element containing the media DESCRIPTION and
+ * its encryption.
+ * @param remoteDescription The element containing the media DESCRIPTION and
+ * its encryption for the remote peer. Null, if the local peer is the
+ * initiator of the call.
+ *
+ * @return True if the crypto element has been added to encryption. False,
+ * otherwise.
+ */
+ protected boolean setSDesEncryptionOnDescription(
+ MediaType mediaType,
+ RtpDescriptionPacketExtension localDescription,
+ RtpDescriptionPacketExtension remoteDescription)
+ {
+ CallPeer peer = getPeer();
+
+ /*
+ * SDES is not supported in telephony conferences utilizing the
+ * server-side technology Jitsi Videobridge yet.
+ */
+ if (peer.getCall().getConference().isJitsiVideobridge())
+ return false;
+
+ AccountID accountID = peer.getProtocolProvider().getAccountID();
+
+ // check if SDES and encryption is enabled at all
+ if (accountID.getAccountPropertyBoolean(
+ ProtocolProviderFactory.DEFAULT_ENCRYPTION,
+ true)
+ && accountID.isEncryptionProtocolEnabled(
+ SDesControl.PROTO_NAME))
+ {
+ // get or create the control
+ SrtpControls srtpControls = getSrtpControls();
+ SDesControl sdesControl
+ = (SDesControl)
+ srtpControls.getOrCreate(mediaType, SrtpControlType.SDES);
+ // set the enabled ciphers suites
+ String ciphers
+ = accountID.getAccountPropertyString(
+ ProtocolProviderFactory.SDES_CIPHER_SUITES);
+
+ if (ciphers == null)
+ {
+ ciphers =
+ JabberActivator.getResources().getSettingsString(
+ SDesControl.SDES_CIPHER_SUITES);
+ }
+ sdesControl.setEnabledCiphers(Arrays.asList(ciphers.split(",")));
+
+ // act as initiator
+ if (remoteDescription == null)
+ {
+ EncryptionPacketExtension localEncryption
+ = localDescription.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ if(localEncryption == null)
+ {
+ localEncryption = new EncryptionPacketExtension();
+ localDescription.addChildExtension(localEncryption);
+ }
+ for(SrtpCryptoAttribute ca:
+ sdesControl.getInitiatorCryptoAttributes())
+ {
+ CryptoPacketExtension crypto
+ = new CryptoPacketExtension(ca);
+ localEncryption.addChildExtension(crypto);
+ }
+
+ return true;
+ }
+ // act as responder
+ else
+ {
+ // Conforming to XEP-0167 schema there is 0 or 1 ENCRYPTION
+ // element for a given DESCRIPTION.
+ EncryptionPacketExtension remoteEncryption
+ = remoteDescription.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ if(remoteEncryption != null)
+ {
+ SrtpCryptoAttribute selectedSdes = selectSdesCryptoSuite(
+ false,
+ sdesControl,
+ remoteEncryption);
+
+ if(selectedSdes != null)
+ {
+ EncryptionPacketExtension localEncryption
+ = localDescription.getFirstChildOfType(
+ EncryptionPacketExtension.class);
+
+ if(localEncryption == null)
+ {
+ localEncryption = new EncryptionPacketExtension();
+ localDescription.addChildExtension(localEncryption);
+ }
+
+ CryptoPacketExtension crypto
+ = new CryptoPacketExtension(selectedSdes);
+
+ localEncryption.addChildExtension(crypto);
+
+ return true;
+ }
+ else
+ {
+ // none of the offered suites match, destroy the sdes
+ // control
+ sdesControl.cleanup();
+ srtpControls.remove(mediaType, SrtpControlType.SDES);
+ logger.warn(
+ "Received unsupported sdes crypto attribute");
+ }
+ }
+ else
+ {
+ // peer doesn't offer any SDES attribute, destroy the sdes
+ // control
+ sdesControl.cleanup();
+ srtpControls.remove(mediaType, SrtpControlType.SDES);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Selects the preferred encryption protocol (only used by the callee).
+ *
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ * @param localDescription The element containing the media DESCRIPTION and
+ * its encryption.
+ * @param remoteDescription The element containing the media DESCRIPTION and
+ * its encryption for the remote peer; <tt>null</tt> if the local peer is
+ * the initiator of the call.
+ */
+ protected void setAndAddPreferredEncryptionProtocol(
+ MediaType mediaType,
+ RtpDescriptionPacketExtension localDescription,
+ RtpDescriptionPacketExtension remoteDescription)
+ {
+ // Sets ZRTP or SDES, depending on the preferences for this account.
+ List<String> preferredEncryptionProtocols
+ = getPeer()
+ .getProtocolProvider()
+ .getAccountID()
+ .getSortedEnabledEncryptionProtocolList();
+
+ for(String preferredEncryptionProtocol : preferredEncryptionProtocols)
+ {
+ String protoName
+ = preferredEncryptionProtocol.substring(
+ ProtocolProviderFactory.ENCRYPTION_PROTOCOL.length()
+ + 1);
+
+ if (setAndAddPreferredEncryptionProtocol(
+ protoName,
+ mediaType,
+ localDescription,
+ remoteDescription))
+ {
+ // Stop once an encryption advertisement has been chosen.
+ return;
+ }
+ }
+ }
+
+ /**
+ * Selects a specific encryption protocol if it is the preferred (only used
+ * by the callee).
+ *
+ * @param protoName the name of the encryption protocol which is to be
+ * selected
+ * @param mediaType The type of media (AUDIO or VIDEO).
+ * @param localDescription The element containing the media DESCRIPTION and
+ * its encryption.
+ * @param remoteDescription The element containing the media DESCRIPTION and
+ * its encryption for the remote peer; <tt>null</tt> if the local peer is
+ * the initiator of the call.
+ * @return <tt>true</tt> if the specified encryption protocol has been
+ * selected; <tt>false</tt>, otherwise
+ */
+ protected boolean setAndAddPreferredEncryptionProtocol(
+ String protoName,
+ MediaType mediaType,
+ RtpDescriptionPacketExtension localDescription,
+ RtpDescriptionPacketExtension remoteDescription)
+ {
+ /*
+ * Neither SDES nor ZRTP is supported in telephony conferences utilizing
+ * the server-side technology Jitsi Videobridge yet.
+ */
+ if (getPeer().isJitsiVideobridge())
+ return false;
+
+ // SDES
+ if(SDesControl.PROTO_NAME.equals(protoName))
+ {
+ addSDesAdvertisedEncryptions(
+ false,
+ remoteDescription,
+ mediaType);
+ if(setSDesEncryptionOnDescription(
+ mediaType,
+ localDescription,
+ remoteDescription))
+ {
+ // Stop once an encryption advertisement has been chosen.
+ return true;
+ }
+ }
+ // ZRTP
+ else if(ZrtpControl.PROTO_NAME.equals(protoName))
+ {
+ if(setZrtpEncryptionOnDescription(
+ mediaType,
+ localDescription,
+ remoteDescription))
+ {
+ addZrtpAdvertisedEncryptions(
+ false,
+ remoteDescription,
+ mediaType);
+ // Stop once an encryption advertisement has been chosen.
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java
index 7281eaa..5af5b1e 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java
@@ -41,7 +41,7 @@ public class CallJabberImpl
private static final Logger logger = Logger.getLogger(CallJabberImpl.class);
/**
- * The Jitsi VideoBridge conference which the local peer represented by this
+ * The Jitsi Videobridge conference which the local peer represented by this
* instance is a focus of.
*/
private ColibriConferenceIQ colibri;
@@ -158,7 +158,7 @@ public class CallJabberImpl
CallPeerJabberImpl peer,
Map<ContentPacketExtension,ContentPacketExtension> contentMap)
{
- if (!getConference().isJitsiVideoBridge())
+ if (!getConference().isJitsiVideobridge())
return null;
/*
@@ -180,12 +180,12 @@ public class CallJabberImpl
ProtocolProviderServiceJabberImpl protocolProvider
= getProtocolProvider();
- String jitsiVideoBridge
+ String jitsiVideobridge
= (colibri == null)
- ? protocolProvider.getJitsiVideoBridge()
+ ? protocolProvider.getJitsiVideobridge()
: colibri.getFrom();
- if ((jitsiVideoBridge == null) || (jitsiVideoBridge.length() == 0))
+ if ((jitsiVideobridge == null) || (jitsiVideobridge.length() == 0))
{
logger.error(
"Failed to allocate colibri channels: no videobridge"
@@ -243,7 +243,7 @@ public class CallJabberImpl
localChannelRequest.addPayloadType(ptpe);
// DTLS-SRTP
setDtlsEncryptionToChannel(
- jitsiVideoBridge,
+ jitsiVideobridge,
peer,
mediaType,
localChannelRequest);
@@ -269,7 +269,7 @@ public class CallJabberImpl
= connection.createPacketCollector(
new PacketIDFilter(conferenceRequest.getPacketID()));
- conferenceRequest.setTo(jitsiVideoBridge);
+ conferenceRequest.setTo(jitsiVideobridge);
conferenceRequest.setType(IQ.Type.GET);
connection.sendPacket(conferenceRequest);
@@ -770,7 +770,7 @@ public class CallJabberImpl
* processed by this instance and no further processing is to be performed
* by other possible processors of <tt>ColibriConferenceIQ</tt>s; otherwise,
* <tt>false</tt>. Because a <tt>ColibriConferenceIQ</tt> request sent from
- * the Jitsi VideoBridge server to the application as its client concerns a
+ * the Jitsi Videobridge server to the application as its client concerns a
* specific <tt>CallJabberImpl</tt> implementation, no further processing by
* other <tt>CallJabberImpl</tt> instances is necessary once the
* <tt>ColibriConferenceIQ</tt> is processed by the associated
@@ -782,7 +782,7 @@ public class CallJabberImpl
{
/*
* This instance has not set up any conference using the Jitsi
- * VideoBridge server-side technology yet so it cannot be bothered
+ * Videobridge server-side technology yet so it cannot be bothered
* with related requests.
*/
return false;
@@ -792,7 +792,7 @@ public class CallJabberImpl
/*
* Remove the local Channels (from the specified conferenceIQ) i.e.
* the Channels on which the local peer/user is sending to the Jitsi
- * VideoBridge server because they concern this Call only and not
+ * Videobridge server because they concern this Call only and not
* its CallPeers.
*/
for (MediaType mediaType : MediaType.values())
@@ -833,7 +833,7 @@ public class CallJabberImpl
else
{
/*
- * This instance has set up a conference using the Jitsi VideoBridge
+ * This instance has set up a conference using the Jitsi Videobridge
* server-side technology but it is not the one referred to by the
* specified conferenceIQ i.e. the specified conferenceIQ does not
* concern this instance.
@@ -1186,7 +1186,7 @@ public class CallJabberImpl
* purposes of transmitting media between the local peer and the Jitsi
* Videobridge server.
*
- * @param jitsiVideoBridge the address/JID of the Jitsi Videobridge
+ * @param jitsiVideobridge the address/JID of the Jitsi Videobridge
* @param peer the <tt>CallPeer</tt> associated with the method invocation
* @param mediaType the <tt>MediaType</tt> of the media to be transmitted
* over the DTLS-SRTP session
@@ -1194,7 +1194,7 @@ public class CallJabberImpl
* the state of the remote DTLS-SRTP endpoint.
*/
private void setDtlsEncryptionToChannel(
- String jitsiVideoBridge,
+ String jitsiVideobridge,
CallPeerJabberImpl peer,
MediaType mediaType,
ColibriConferenceIQ.Channel channel)
@@ -1209,7 +1209,7 @@ public class CallJabberImpl
&& accountID.isEncryptionProtocolEnabled(
DtlsControl.PROTO_NAME)
&& protocolProvider.isFeatureSupported(
- jitsiVideoBridge,
+ jitsiVideobridge,
ProtocolProviderServiceJabberImpl
.URN_XMPP_JINGLE_DTLS_SRTP))
{
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java
index 726d12f..110bb6c 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerJabberImpl.java
@@ -1,1623 +1,1627 @@
-/*
- * 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.lang.reflect.*;
-import java.util.*;
-
-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.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.util.*;
-import org.jivesoftware.smackx.packet.*;
-
-/**
- * Implements a Jabber <tt>CallPeer</tt>.
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public class CallPeerJabberImpl
- extends AbstractCallPeerJabberGTalkImpl
- <CallJabberImpl, CallPeerMediaHandlerJabberImpl, JingleIQ>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(CallPeerJabberImpl.class);
-
- /**
- * If the call is cancelled before session-initiate is sent.
- */
- private boolean cancelled = false;
-
- /**
- * Synchronization object for candidates available.
- */
- private final Object candSyncRoot = new Object();
-
- /**
- * If the content-add does not contains candidates.
- */
- private boolean contentAddWithNoCands = false;
-
- /**
- * If we have processed the session initiate.
- */
- private boolean sessionInitiateProcessed = false;
-
- /**
- * Synchronization object. Synchronization object? Wow, who would have
- * thought! ;) Would be great to have a word on what we are syncing with it
- */
- private final Object sessionInitiateSyncRoot = new Object();
-
- /**
- * Synchronization object for SID.
- */
- private final Object sidSyncRoot = new Object();
-
- /**
- * The current value of the 'senders' field of the audio content in the
- * Jingle session with this <tt>CallPeer</tt>.
- * <tt>null</tt> should be interpreted as 'both', which is the default in
- * Jingle if the XML attribute is missing.
- */
- private SendersEnum audioSenders = SendersEnum.none;
-
- /**
- * The current value of the 'senders' field of the video content in the
- * Jingle session with this <tt>CallPeer</tt>.
- * <tt>null</tt> should be interpreted as 'both', which is the default in
- * Jingle if the XML attribute is missing.
- */
- private SendersEnum videoSenders = SendersEnum.none;
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param peerAddress the Jabber address of the new call peer.
- * @param owningCall the call that contains this call peer.
- */
- public CallPeerJabberImpl(String peerAddress,
- CallJabberImpl owningCall)
- {
- super(peerAddress, owningCall);
-
- setMediaHandler(new CallPeerMediaHandlerJabberImpl(this));
- }
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param peerAddress the Jabber address of the new call peer.
- * @param owningCall the call that contains this call peer.
- * @param sessionIQ The session-initiate <tt>JingleIQ</tt> which was
- * received from <tt>peerAddress</tt> and caused the creation of this
- * <tt>CallPeerJabberImpl</tt>
- */
- public CallPeerJabberImpl(String peerAddress,
- CallJabberImpl owningCall,
- JingleIQ sessionIQ)
- {
- this(peerAddress, owningCall);
- this.sessionInitIQ = sessionIQ;
- }
-
- /**
- * Send a session-accept <tt>JingleIQ</tt> to this <tt>CallPeer</tt>
- * @throws OperationFailedException if we fail to create or send the
- * response.
- */
- public synchronized void answer()
- throws OperationFailedException
- {
- Iterable<ContentPacketExtension> answer;
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- try
- {
- mediaHandler
- .getTransportManager().wrapupConnectivityEstablishment();
- answer = mediaHandler.generateSessionAccept();
- for (ContentPacketExtension c : answer)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch(Exception exc)
- {
- logger.info("Failed to answer an incoming call", exc);
-
- //send an error response
- String reasonText = "Error: " + exc.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.FAILED_APPLICATION,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- JingleIQ response
- = JinglePacketFactory.createSessionAccept(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- getSID(),
- answer);
-
- //send the packet first and start the stream later in case the media
- //relay needs to see it before letting hole punching techniques through.
- getProtocolProvider().getConnection().sendPacket(response);
-
- try
- {
- mediaHandler.start();
- }
- catch(UndeclaredThrowableException e)
- {
- Throwable exc = e.getUndeclaredThrowable();
-
- logger.info("Failed to establish a connection", exc);
-
- //send an error response
- String reasonText = "Error: " + exc.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.GENERAL_ERROR,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- //tell everyone we are connected so that the audio notifications would
- //stop
- setState(CallPeerState.CONNECTED);
- }
-
- /**
- * Returns the session ID of the Jingle session associated with this call.
- *
- * @return the session ID of the Jingle session associated with this call.
- */
- @Override
- public String getSID()
- {
- return sessionInitIQ != null ? sessionInitIQ.getSID() : null;
- }
-
- /**
- * Returns the IQ ID of the Jingle session-initiate packet associated with
- * this call.
- *
- * @return the IQ ID of the Jingle session-initiate packet associated with
- * this call.
- */
- public JingleIQ getSessionIQ()
- {
- return sessionInitIQ;
- }
-
- /**
- * Ends the call with this <tt>CallPeer</tt>. Depending on the state
- * of the peer the method would send a CANCEL, BYE, or BUSY_HERE message
- * and set the new state to DISCONNECTED.
- *
- * @param failed indicates if the hangup is following to a call failure or
- * simply a disconnect
- * @param reasonText the text, if any, to be set on the
- * <tt>ReasonPacketExtension</tt> as the value of its
- * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be
- * set on the <tt>ReasonPacketExtension</tt> as the value of its
- * <tt>otherExtension</tt> property
- */
- public void hangup(boolean failed,
- String reasonText,
- PacketExtension reasonOtherExtension)
- {
- CallPeerState prevPeerState = getState();
-
- // do nothing if the call is already ended
- if (CallPeerState.DISCONNECTED.equals(prevPeerState)
- || CallPeerState.FAILED.equals(prevPeerState))
- {
- if (logger.isDebugEnabled())
- logger.debug("Ignoring a request to hangup a call peer "
- + "that is already DISCONNECTED");
- return;
- }
-
- setState(
- failed ? CallPeerState.FAILED : CallPeerState.DISCONNECTED,
- reasonText);
-
- JingleIQ responseIQ = null;
-
- if (prevPeerState.equals(CallPeerState.CONNECTED)
- || CallPeerState.isOnHold(prevPeerState))
- {
- responseIQ = JinglePacketFactory.createBye(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (CallPeerState.CONNECTING.equals(prevPeerState)
- || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
- || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState))
- {
- String jingleSID = getSID();
-
- if(jingleSID == null)
- {
- synchronized(sidSyncRoot)
- {
- // we cancelled the call too early because the jingleSID
- // is null (i.e. the session-initiate has not been created)
- // and no need to send the session-terminate
- cancelled = true;
- return;
- }
- }
-
- responseIQ = JinglePacketFactory.createCancel(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (prevPeerState.equals(CallPeerState.INCOMING_CALL))
- {
- responseIQ = JinglePacketFactory.createBusy(
- getProtocolProvider().getOurJID(), peerJID, getSID());
- }
- else if (prevPeerState.equals(CallPeerState.BUSY)
- || prevPeerState.equals(CallPeerState.FAILED))
- {
- // For FAILED and BUSY we only need to update CALL_STATUS
- // as everything else has been done already.
- }
- else
- {
- logger.info("Could not determine call peer state!");
- }
-
- if (responseIQ != null)
- {
- if (reasonOtherExtension != null)
- {
- ReasonPacketExtension reason
- = (ReasonPacketExtension)
- responseIQ.getExtension(
- ReasonPacketExtension.ELEMENT_NAME,
- ReasonPacketExtension.NAMESPACE);
-
- if (reason != null)
- {
- reason.setOtherExtension(reasonOtherExtension);
- }
- else if(reasonOtherExtension instanceof ReasonPacketExtension)
- {
- responseIQ.setReason(
- (ReasonPacketExtension)reasonOtherExtension);
- }
- }
-
- getProtocolProvider().getConnection().sendPacket(responseIQ);
- }
- }
-
- /**
- * Creates and sends a session-initiate {@link JingleIQ}.
- *
- * @param sessionInitiateExtensions a collection of additional and optional
- * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
- * {@link JingleIQ} which is to initiate the session with this
- * <tt>CallPeerJabberImpl</tt>
- * @throws OperationFailedException exception
- */
- protected synchronized void initiateSession(
- Iterable<PacketExtension> sessionInitiateExtensions)
- throws OperationFailedException
- {
- initiator = false;
-
- //Create the media description that we'd like to send to the other side.
- List<ContentPacketExtension> offer
- = getMediaHandler().createContentList();
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- synchronized(sidSyncRoot)
- {
- sessionInitIQ
- = JinglePacketFactory.createSessionInitiate(
- protocolProvider.getOurJID(),
- this.peerJID,
- JingleIQ.generateSID(),
- offer);
-
- if(cancelled)
- {
- // we cancelled the call too early so no need to send the
- // session-initiate to peer
- getMediaHandler().getTransportManager().close();
- return;
- }
- }
-
- if (sessionInitiateExtensions != null)
- {
- for (PacketExtension sessionInitiateExtension
- : sessionInitiateExtensions)
- {
- sessionInitIQ.addExtension(sessionInitiateExtension);
- }
- }
-
- protocolProvider.getConnection().sendPacket(sessionInitIQ);
- }
-
- /**
- * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
- * been received. This <tt>CallPeerJabberImpl</tt> uses the part of the
- * information provided in the specified <tt>conferenceIQ</tt> which
- * concerns it only.
- *
- * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
- * received
- */
- void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * CallPeerJabberImpl does not itself/directly know the specifics
- * related to the channels allocated on the Jitsi VideoBridge server.
- * The channels contain transport and media-related information so
- * forward the notification to CallPeerMediaHandlerJabberImpl.
- */
- getMediaHandler().processColibriConferenceIQ(conferenceIQ);
- }
-
- /**
- * Processes the content-accept {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer has accepted
- */
- public void processContentAccept(JingleIQ content)
- {
- List<ContentPacketExtension> contents = content.getContentList();
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- try
- {
- mediaHandler
- .getTransportManager().wrapupConnectivityEstablishment();
- mediaHandler.processAnswer(contents);
- for (ContentPacketExtension c : contents)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch (Exception e)
- {
- logger.warn("Failed to process a content-accept", e);
-
- // Send an error response.
- String reason = "Error: " + e.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reason);
-
- setState(CallPeerState.FAILED, reason);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- mediaHandler.start();
- }
-
- /**
- * Processes the content-add {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be added
- */
- public void processContentAdd(final JingleIQ content)
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- List<ContentPacketExtension> contents = content.getContentList();
- Iterable<ContentPacketExtension> answerContents;
- JingleIQ contentIQ;
- boolean noCands = false;
- MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO);
-
- if(logger.isInfoEnabled())
- logger.info("Looking for candidates in content-add.");
- try
- {
- if(!contentAddWithNoCands)
- {
- mediaHandler.processOffer(contents);
-
- /*
- * Gingle transport will not put candidate in session-initiate
- * and content-add.
- */
- for(ContentPacketExtension c : contents)
- {
- if(JingleUtils.getFirstCandidate(c, 1) == null)
- {
- contentAddWithNoCands = true;
- noCands = true;
- }
- }
- }
-
- // if no candidates are present, launch a new Thread which will
- // process and wait for the connectivity establishment (otherwise
- // the existing thread will be blocked and thus cannot receive
- // transport-info with candidates
- if(noCands)
- {
- new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- synchronized(candSyncRoot)
- {
- candSyncRoot.wait();
- }
- }
- catch(InterruptedException e)
- {
- }
-
- processContentAdd(content);
- contentAddWithNoCands = false;
- }
- }.start();
- if(logger.isInfoEnabled())
- logger.info("No candidates found in content-add, started "
- + "new thread.");
- return;
- }
-
- mediaHandler.getTransportManager().
- wrapupConnectivityEstablishment();
- if(logger.isInfoEnabled())
- logger.info("Wrapping up connectivity establishment");
- answerContents = mediaHandler.generateSessionAccept();
- contentIQ = null;
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred", e);
-
- answerContents = null;
- contentIQ
- = JinglePacketFactory.createContentReject(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID(),
- answerContents);
- }
-
- if(contentIQ == null)
- {
- /* send content-accept */
- contentIQ
- = JinglePacketFactory.createContentAccept(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID(),
- answerContents);
- for (ContentPacketExtension c : answerContents)
- setSenders(getMediaType(c), c.getSenders());
- }
-
- getProtocolProvider().getConnection().sendPacket(contentIQ);
- mediaHandler.start();
-
- /*
- * If a remote peer turns her video on in a conference which is hosted
- * by the local peer and the local peer is not streaming her local
- * video, reinvite the other remote peers to enable RTP translation.
- */
- if (oldVideoStream == null)
- {
- MediaStream newVideoStream
- = mediaHandler.getStream(MediaType.VIDEO);
-
- if ((newVideoStream != null)
- && mediaHandler.isRTPTranslationEnabled(MediaType.VIDEO))
- {
- try
- {
- getCall().modifyVideoContent();
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Failed to enable RTP translation", ofe);
- }
- }
- }
- }
-
- /**
- * Processes the content-modify {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be modified
- */
- public void processContentModify(JingleIQ content)
- {
- ContentPacketExtension ext = content.getContentList().get(0);
- MediaType mediaType = getMediaType(ext);
-
- try
- {
- boolean modify
- = (ext.getFirstChildOfType(RtpDescriptionPacketExtension.class)
- != null);
-
- getMediaHandler().reinitContent(ext.getName(), ext, modify);
-
- setSenders(mediaType, ext.getSenders());
-
- if (MediaType.VIDEO.equals(mediaType))
- getCall().modifyVideoContent();
- }
- catch(Exception e)
- {
- logger.info("Failed to process an incoming content-modify", e);
-
- // Send an error response.
- String reason = "Error: " + e.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reason);
-
- setState(CallPeerState.FAILED, reason);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
- }
-
- /**
- * Processes the content-reject {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ}
- */
- public void processContentReject(JingleIQ content)
- {
- if(content.getContentList().isEmpty())
- {
- //send an error response;
- JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
- "Error: content rejected");
-
- setState(CallPeerState.FAILED, "Error: content rejected");
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
- }
-
- /**
- * Processes the content-remove {@link JingleIQ}.
- *
- * @param content The {@link JingleIQ} that contains content that remote
- * peer wants to be removed
- */
- public void processContentRemove(JingleIQ content)
- {
- List<ContentPacketExtension> contents = content.getContentList();
- boolean videoContentRemoved = false;
-
- if (!contents.isEmpty())
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- for(ContentPacketExtension c : contents)
- {
- mediaHandler.removeContent(c.getName());
-
- MediaType mediaType = getMediaType(c);
- setSenders(mediaType, SendersEnum.none);
-
- if (MediaType.VIDEO.equals(mediaType))
- videoContentRemoved = true;
- }
-
- /*
- * TODO XEP-0166: Jingle says: If the content-remove results in zero
- * content definitions for the session, the entity that receives the
- * content-remove SHOULD send a session-terminate action to the
- * other party (since a session with no content definitions is
- * void).
- */
- }
-
- if (videoContentRemoved)
- {
- // removing of the video content might affect the other sessions
- // in the call
- try
- {
- getCall().modifyVideoContent();
- }
- catch (Exception e)
- {
- logger.warn("Failed to update Jingle sessions");
- }
- }
- }
-
- /**
- * Processes a session-accept {@link JingleIQ}.
- *
- * @param sessionInitIQ The session-accept {@link JingleIQ} to process.
- */
- public void processSessionAccept(JingleIQ sessionInitIQ)
- {
- this.sessionInitIQ = sessionInitIQ;
-
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- List<ContentPacketExtension> answer = sessionInitIQ.getContentList();
-
- try
- {
- mediaHandler
- .getTransportManager().wrapupConnectivityEstablishment();
- mediaHandler.processAnswer(answer);
- for (ContentPacketExtension c : answer)
- setSenders(getMediaType(c), c.getSenders());
- }
- catch(Exception exc)
- {
- if (logger.isInfoEnabled())
- logger.info("Failed to process a session-accept", exc);
-
- //send an error response;
- JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
- exc.getClass().getName() + ": " + exc.getMessage());
-
- setState(CallPeerState.FAILED, "Error: " + exc.getMessage());
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- //tell everyone we are connected so that the audio notifications would
- //stop
- setState(CallPeerState.CONNECTED);
-
- mediaHandler.start();
-
- /*
- * If video was added to the call after we sent the session-initiate
- * to this peer, it needs to be added to this peer's session with a
- * content-add.
- */
- sendModifyVideoContent();
- }
-
- /**
- * Handles the specified session <tt>info</tt> packet according to its
- * content.
- *
- * @param info the {@link SessionInfoPacketExtension} that we just received.
- */
- public void processSessionInfo(SessionInfoPacketExtension info)
- {
- switch (info.getType())
- {
- case ringing:
- setState(CallPeerState.ALERTING_REMOTE_SIDE);
- break;
- case hold:
- getMediaHandler().setRemotelyOnHold(true);
- reevalRemoteHoldStatus();
- break;
- case unhold:
- case active:
- getMediaHandler().setRemotelyOnHold(false);
- reevalRemoteHoldStatus();
- break;
- default:
- logger.warn("Received SessionInfoPacketExtension of unknown type");
- }
- }
-
- /**
- * Processes the session initiation {@link JingleIQ} that we were created
- * with, passing its content to the media handler and then sends either a
- * "session-info/ringing" or a "session-terminate" response.
- *
- * @param sessionInitIQ The {@link JingleIQ} that created the session that
- * we are handling here.
- */
- protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ)
- {
- // Do initiate the session.
- this.sessionInitIQ = sessionInitIQ;
- this.initiator = true;
-
- // This is the SDP offer that came from the initial session-initiate.
- // Contrary to SIP, we are guaranteed to have content because XEP-0166
- // says: "A session consists of at least one content type at a time."
- List<ContentPacketExtension> offer = sessionInitIQ.getContentList();
-
- try
- {
- getMediaHandler().processOffer(offer);
-
- CoinPacketExtension coin = null;
-
- for(PacketExtension ext : sessionInitIQ.getExtensions())
- {
- if(ext.getElementName().equals(
- CoinPacketExtension.ELEMENT_NAME))
- {
- coin = (CoinPacketExtension)ext;
- break;
- }
- }
-
- /* does the call peer acts as a conference focus ? */
- if(coin != null)
- {
- setConferenceFocus(Boolean.parseBoolean(
- (String)coin.getAttribute("isfocus")));
- }
- }
- catch(Exception ex)
- {
- logger.info("Failed to process an incoming session initiate", ex);
-
- //send an error response;
- String reasonText = "Error: " + ex.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- Reason.INCOMPATIBLE_PARAMETERS,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
- return;
- }
-
- // If we do not get the info about the remote peer yet. Get it right
- // now.
- if(this.getDiscoveryInfo() == null)
- {
- String calleeURI = sessionInitIQ.getFrom();
- retrieveDiscoveryInfo(calleeURI);
- }
-
- //send a ringing response
- if (logger.isTraceEnabled())
- logger.trace("will send ringing response: ");
-
- getProtocolProvider().getConnection().sendPacket(
- JinglePacketFactory.createRinging(sessionInitIQ));
-
- synchronized(sessionInitiateSyncRoot)
- {
- sessionInitiateProcessed = true;
- sessionInitiateSyncRoot.notify();
- }
-
- //if this is a 3264 initiator, let's give them an early peek at our
- //answer so that they could start ICE (SIP-2-Jingle gateways won't
- //be able to send their candidates unless they have this)
- DiscoverInfo discoverInfo = getDiscoveryInfo();
- if ((discoverInfo != null)
- && discoverInfo.containsFeature(
- ProtocolProviderServiceJabberImpl.URN_IETF_RFC_3264))
- {
- getProtocolProvider().getConnection().sendPacket(
- JinglePacketFactory.createDescriptionInfo(
- sessionInitIQ.getTo(),
- sessionInitIQ.getFrom(),
- sessionInitIQ.getSID(),
- getMediaHandler().getLocalContentList()));
- }
- }
-
- /**
- * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
- * reason to the user, if there is one.
- *
- * @param jingleIQ the {@link JingleIQ} that's terminating our session.
- */
- public void processSessionTerminate(JingleIQ jingleIQ)
- {
- String reasonStr = "Call ended by remote side.";
- ReasonPacketExtension reasonExt = jingleIQ.getReason();
-
- if(reasonExt != null)
- {
- Reason reason = reasonExt.getReason();
-
- if(reason != null)
- reasonStr += " Reason: " + reason.toString() + ".";
-
- String text = reasonExt.getText();
-
- if(text != null)
- reasonStr += " " + text;
- }
-
- setState(CallPeerState.DISCONNECTED, reasonStr);
- }
-
- /**
- * Processes a specific "XEP-0251: Jingle Session Transfer"
- * <tt>transfer</tt> packet (extension).
- *
- * @param transfer the "XEP-0251: Jingle Session Transfer" transfer packet
- * (extension) to process
- * @throws OperationFailedException if anything goes wrong while processing
- * the specified <tt>transfer</tt> packet (extension)
- */
- public void processTransfer(TransferPacketExtension transfer)
- throws OperationFailedException
- {
- String attendantAddress = transfer.getFrom();
-
- if (attendantAddress == null)
- {
- throw new OperationFailedException(
- "Session transfer must contain a \'from\' attribute value.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
-
- String calleeAddress = transfer.getTo();
-
- if (calleeAddress == null)
- {
- throw new OperationFailedException(
- "Session transfer must contain a \'to\' attribute value.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
-
- // Checks if the transfer remote peer is contained by the roster of this
- // account.
- Roster roster = getProtocolProvider().getConnection().getRoster();
- if(!roster.contains(StringUtils.parseBareAddress(calleeAddress)))
- {
- String failedMessage =
- "Tranfer impossible:\n"
- + "Account roster does not contain tansfer peer: "
- + StringUtils.parseBareAddress(calleeAddress);
- setState(CallPeerState.FAILED, failedMessage);
- logger.info(failedMessage);
- }
-
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = (OperationSetBasicTelephonyJabberImpl)
- getProtocolProvider()
- .getOperationSet(OperationSetBasicTelephony.class);
- CallJabberImpl calleeCall = new CallJabberImpl(basicTelephony);
- TransferPacketExtension calleeTransfer = new TransferPacketExtension();
- String sid = transfer.getSID();
-
- calleeTransfer.setFrom(attendantAddress);
- if (sid != null)
- {
- calleeTransfer.setSID(sid);
- calleeTransfer.setTo(calleeAddress);
- }
- basicTelephony.createOutgoingCall(
- calleeCall,
- calleeAddress,
- Arrays.asList(new PacketExtension[] { calleeTransfer }));
- }
-
- /**
- * Processes the <tt>transport-info</tt> {@link JingleIQ}.
- *
- * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process
- */
- public void processTransportInfo(JingleIQ jingleIQ)
- {
- /*
- * The transport-info action is used to exchange transport candidates so
- * it only concerns the mediaHandler.
- */
- try
- {
- if(isInitiator())
- {
- synchronized(sessionInitiateSyncRoot)
- {
- if(!sessionInitiateProcessed)
- {
- try
- {
- sessionInitiateSyncRoot.wait();
- }
- catch(InterruptedException e)
- {
- }
- }
- }
- }
-
- getMediaHandler().processTransportInfo(
- jingleIQ.getContentList());
- }
- catch (OperationFailedException ofe)
- {
- logger.warn("Failed to process an incoming transport-info", ofe);
-
- //send an error response
- String reasonText = "Error: " + ofe.getMessage();
- JingleIQ errResp
- = JinglePacketFactory.createSessionTerminate(
- getProtocolProvider().getOurJID(),
- peerJID,
- sessionInitIQ.getSID(),
- Reason.GENERAL_ERROR,
- reasonText);
-
- setState(CallPeerState.FAILED, reasonText);
- getProtocolProvider().getConnection().sendPacket(errResp);
-
- return;
- }
-
- synchronized(candSyncRoot)
- {
- candSyncRoot.notify();
- }
- }
-
- /**
- * Puts the <tt>CallPeer</tt> represented by this instance on or off hold.
- *
- * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
- * <tt>false</tt>, otherwise
- *
- * @throws OperationFailedException if we fail to construct or send the
- * INVITE request putting the remote side on/off hold.
- */
- public void putOnHold(boolean onHold)
- throws OperationFailedException
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- mediaHandler.setLocallyOnHold(onHold);
-
- SessionInfoType type;
-
- if(onHold)
- type = SessionInfoType.hold;
- else
- {
- type = SessionInfoType.unhold;
- getMediaHandler().reinitAllContents();
- }
-
- //we are now on hold and need to realize this before potentially
- //spoiling it all with an exception while sending the packet :).
- reevalLocalHoldStatus();
-
- JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo(
- getProtocolProvider().getOurJID(),
- peerJID,
- getSID(),
- type);
-
- getProtocolProvider().getConnection().sendPacket(onHoldIQ);
- }
-
- /**
- * Send a <tt>content-add</tt> to add video setup.
- */
- private void sendAddVideoContent()
- {
- List<ContentPacketExtension> contents;
-
- try
- {
- contents = getMediaHandler().createContentList(MediaType.VIDEO);
- }
- catch(Exception exc)
- {
- logger.warn("Failed to gather content for video type", exc);
- return;
- }
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentAdd(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- contents);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- }
-
- /**
- * Sends a <tt>content</tt> message to reflect changes in the setup such as
- * the local peer/user becoming a conference focus.
- */
- public void sendCoinSessionInfo()
- {
- JingleIQ sessionInfoIQ
- = JinglePacketFactory.createSessionInfo(
- getProtocolProvider().getOurJID(),
- this.peerJID,
- getSID());
- CoinPacketExtension coinExt
- = new CoinPacketExtension(getCall().isConferenceFocus());
-
- sessionInfoIQ.addExtension(coinExt);
- getProtocolProvider().getConnection().sendPacket(sessionInfoIQ);
- }
-
- /**
- * Returns the <tt>MediaDirection</tt> that should be set for the content
- * of type <tt>mediaType</tt> in the Jingle session for this
- * <tt>CallPeer</tt>.
- * If we are the focus of a conference and are doing RTP translation,
- * takes into account the other <tt>CallPeer</tt>s in the <tt>Call</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> for which to return the
- * <tt>MediaDirection</tt>
- * @return the <tt>MediaDirection</tt> that should be used for the content
- * of type <tt>mediaType</tt> in the Jingle session for this
- * <tt>CallPeer</tt>.
- */
- private MediaDirection getDirectionForJingle(MediaType mediaType)
- {
- MediaDirection direction = MediaDirection.INACTIVE;
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- // If we are streaming media, the direction should allow sending
- if ( (MediaType.AUDIO == mediaType &&
- mediaHandler.isLocalAudioTransmissionEnabled()) ||
- (MediaType.VIDEO == mediaType &&
- isLocalVideoStreaming()))
- direction = direction.or(MediaDirection.SENDONLY);
-
- // If we are receiving media from this CallPeer, the direction should
- // allow receiving
- SendersEnum senders = getSenders(mediaType);
- if (senders == null || senders == SendersEnum.both ||
- (isInitiator() && senders == SendersEnum.initiator) ||
- (!isInitiator() && senders == SendersEnum.responder))
- direction = direction.or(MediaDirection.RECVONLY);
-
- // If we are the focus of a conference and we are receiving media from
- // another CallPeer in the same Call, the direction should allow sending
- if (getCall().isConferenceFocus())
- {
- for (CallPeerJabberImpl peer : getCall().getCallPeerList())
- {
- if (peer != this)
- {
- senders = peer.getSenders(mediaType);
- if (senders == null || senders == SendersEnum.both ||
- (peer.isInitiator()
- && senders == SendersEnum.initiator) ||
- (!peer.isInitiator()
- && senders == SendersEnum.responder))
- {
- direction = direction.or(MediaDirection.SENDONLY);
- break;
- }
- }
- }
- }
-
- return direction;
- }
-
- /**
- * Send, if necessary, a jingle <tt>content</tt> message to reflect change
- * in video setup. Whether the jingle session should have a video content,
- * and if so, the value of the <tt>senders</tt> field is determined
- * based on whether we are streaming local video and, if we are the focus
- * of a conference, on the other peers in the conference.
- * The message can be content-modify if video content exists (and the
- * <tt>senders</tt> field changes), content-add or content-remove.
- *
- * @return <tt>true</tt> if a jingle <tt>content</tt> message was sent.
- */
- public boolean sendModifyVideoContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- MediaDirection direction = getDirectionForJingle(MediaType.VIDEO);
-
- ContentPacketExtension remoteContent
- = mediaHandler.getLocalContent(MediaType.VIDEO.toString());
-
- if (remoteContent == null)
- {
- if (direction == MediaDirection.INACTIVE)
- {
- // no video content, none needed
- return false;
- }
- else
- {
- if (getState() == CallPeerState.CONNECTED)
- {
- if (logger.isInfoEnabled())
- logger.info("Adding video content for " + this);
- sendAddVideoContent();
- return true;
- }
- return false;
- }
- }
- else
- {
- if (direction == MediaDirection.INACTIVE)
- {
- sendRemoveVideoContent();
- return true;
- }
- }
-
- SendersEnum senders = getSenders(MediaType.VIDEO);
- if (senders == null)
- senders = SendersEnum.both;
-
- SendersEnum newSenders = SendersEnum.none;
- if (MediaDirection.SENDRECV == direction)
- newSenders = SendersEnum.both;
- else if (MediaDirection.RECVONLY == direction)
- newSenders = isInitiator()
- ? SendersEnum.initiator : SendersEnum.responder;
- else if (MediaDirection.SENDONLY == direction)
- newSenders = isInitiator()
- ? SendersEnum.responder : SendersEnum.initiator;
-
- /*
- * Send Content-Modify
- */
- ContentPacketExtension ext = new ContentPacketExtension();
- String remoteContentName = remoteContent.getName();
-
- ext.setSenders(newSenders);
- ext.setCreator(remoteContent.getCreator());
- ext.setName(remoteContentName);
-
- if (newSenders != senders)
- {
- if (logger.isInfoEnabled())
- logger.info("Sending content modify, senders: "
- + senders + "->" + newSenders);
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentModify(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- ext);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- }
-
- try
- {
- mediaHandler.reinitContent(remoteContentName, ext, false);
- mediaHandler.start();
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred during media reinitialization", e);
- }
-
- return (newSenders != senders);
- }
-
- /**
- * Send a <tt>content</tt> message to reflect change in video setup (start
- * or stop).
- */
- public void sendModifyVideoResolutionContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- ContentPacketExtension remoteContent
- = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
- ContentPacketExtension content;
-
- logger.info("send modify-content to change resolution");
-
- // send content-modify with RTP description
-
- // create content list with resolution
- try
- {
- content = mediaHandler.createContentForMedia(MediaType.VIDEO);
- }
- catch (Exception e)
- {
- logger.warn("Failed to gather content for video type", e);
- return;
- }
-
- // if we are only receiving video senders is null
- SendersEnum senders = remoteContent.getSenders();
-
- if (senders != null)
- content.setSenders(senders);
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentModify(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- content);
-
- protocolProvider.getConnection().sendPacket(contentIQ);
-
- try
- {
- mediaHandler.reinitContent(remoteContent.getName(), content, false);
- mediaHandler.start();
- }
- catch(Exception e)
- {
- logger.warn("Exception occurred when media reinitialization", e);
- }
- }
-
- /**
- * Send a <tt>content-remove</tt> to remove video setup.
- */
- private void sendRemoveVideoContent()
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
-
- ContentPacketExtension content = new ContentPacketExtension();
- ContentPacketExtension remoteContent
- = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
- if (remoteContent == null)
- return;
- String remoteContentName = remoteContent.getName();
-
- content.setName(remoteContentName);
- content.setCreator(remoteContent.getCreator());
- content.setSenders(remoteContent.getSenders());
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
- JingleIQ contentIQ
- = JinglePacketFactory.createContentRemove(
- protocolProvider.getOurJID(),
- this.peerJID,
- getSID(),
- Arrays.asList(content));
-
- protocolProvider.getConnection().sendPacket(contentIQ);
- mediaHandler.removeContent(remoteContentName);
- setSenders(MediaType.VIDEO, SendersEnum.none);
- }
-
- /**
- * Sends local candidate addresses from the local peer to the remote peer
- * using the <tt>transport-info</tt> {@link JingleIQ}.
- *
- * @param contents the local candidate addresses to be sent from the local
- * peer to the remote peer using the <tt>transport-info</tt>
- * {@link JingleIQ}
- */
- protected void sendTransportInfo(Iterable<ContentPacketExtension> contents)
- {
- // if the call is canceled, do not start sending candidates in
- // transport-info
- if(cancelled)
- return;
-
- JingleIQ transportInfo = new JingleIQ();
-
- for (ContentPacketExtension content : contents)
- transportInfo.addContent(content);
-
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- transportInfo.setAction(JingleAction.TRANSPORT_INFO);
- transportInfo.setFrom(protocolProvider.getOurJID());
- transportInfo.setSID(getSID());
- transportInfo.setTo(getAddress());
- transportInfo.setType(IQ.Type.SET);
-
- PacketCollector collector
- = protocolProvider.getConnection().createPacketCollector(
- new PacketIDFilter(transportInfo.getPacketID()));
-
- protocolProvider.getConnection().sendPacket(transportInfo);
- collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
- collector.cancel();
- }
-
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
- {
- CallPeerState oldState = getState();
- try
- {
- /*
- * We need to dispose of the transport manager before the
- * 'call' field is set to null, because if Jitsi VideoBridge is in
- * use, it (the call) is needed in order to expire the
- * VideoBridge channels.
- */
- if (CallPeerState.DISCONNECTED.equals(newState)
- || CallPeerState.FAILED.equals(newState))
- getMediaHandler().getTransportManager().close();
- }
- finally
- {
- super.setState(newState, reason, reasonCode);
- }
-
- if (CallPeerState.isOnHold(oldState)
- && CallPeerState.CONNECTED.equals(newState))
- {
- try
- {
- getCall().modifyVideoContent();
- }
- catch (OperationFailedException ofe)
- {
- logger.error("Failed to update call video state after " +
- "'hold' status removed for "+this);
- }
- }
- }
-
- /**
- * Transfer (in the sense of call transfer) this <tt>CallPeer</tt> to a
- * specific callee address which may optionally be participating in an
- * active <tt>Call</tt>.
- *
- * @param to the address of the callee to transfer this <tt>CallPeer</tt> to
- * @param sid the Jingle session ID of the active <tt>Call</tt> between the
- * local peer and the callee in the case of attended transfer; <tt>null</tt>
- * in the case of unattended transfer
- * @throws OperationFailedException if something goes wrong
- */
- protected void transfer(String to, String sid)
- throws OperationFailedException
- {
- JingleIQ transferSessionInfo = new JingleIQ();
- ProtocolProviderServiceJabberImpl protocolProvider
- = getProtocolProvider();
-
- transferSessionInfo.setAction(JingleAction.SESSION_INFO);
- transferSessionInfo.setFrom(protocolProvider.getOurJID());
- transferSessionInfo.setSID(getSID());
- transferSessionInfo.setTo(getAddress());
- transferSessionInfo.setType(IQ.Type.SET);
-
- TransferPacketExtension transfer = new TransferPacketExtension();
-
- // Attended transfer.
- if (sid != null)
- {
- /*
- * Not really sure what the value of the "from" attribute of the
- * "transfer" element should be but the examples in "XEP-0251:
- * Jingle Session Transfer" has it in the case of attended transfer.
- */
- transfer.setFrom(protocolProvider.getOurJID());
- transfer.setSID(sid);
-
- // Puts on hold the 2 calls before making the attended transfer.
- OperationSetBasicTelephonyJabberImpl basicTelephony
- = (OperationSetBasicTelephonyJabberImpl)
- protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
- CallPeerJabberImpl callPeer = basicTelephony.getActiveCallPeer(sid);
- if(callPeer != null)
- {
- if(!CallPeerState.isOnHold(callPeer.getState()))
- {
- callPeer.putOnHold(true);
- }
- }
-
- if(!CallPeerState.isOnHold(this.getState()))
- {
- this.putOnHold(true);
- }
- }
- transfer.setTo(to);
-
- transferSessionInfo.addExtension(transfer);
-
- Connection connection = protocolProvider.getConnection();
- PacketCollector collector = connection.createPacketCollector(
- new PacketIDFilter(transferSessionInfo.getPacketID()));
- protocolProvider.getConnection().sendPacket(transferSessionInfo);
-
- Packet result
- = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
-
- if(result == null)
- {
- // Log the failed transfer call and notify the user.
- throw new OperationFailedException(
- "No response to the \"transfer\" request.",
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
- else if (((IQ) result).getType() != IQ.Type.RESULT)
- {
- // Log the failed transfer call and notify the user.
- throw new OperationFailedException(
- "Remote peer does not manage call \"transfer\"."
- + "Response to the \"transfer\" request is: "
- + ((IQ) result).getType(),
- OperationFailedException.ILLEGAL_ARGUMENT);
- }
- else
- {
- String message = ((sid == null) ? "Unattended" : "Attended")
- + " transfer to: "
- + to;
- // Implements the SIP behavior: once the transfer is accepted, the
- // current call is closed.
- hangup(
- false,
- message,
- new ReasonPacketExtension(Reason.SUCCESS,
- message,
- new TransferredPacketExtension()));
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public String getEntity()
- {
- return getAddress();
- }
-
- /**
- * {@inheritDoc}
- *
- * In Jingle there isn't an actual "direction" parameter. We use the
- * <tt>senders</tt> field to calculate the direction.
- */
- @Override
- public MediaDirection getDirection(MediaType mediaType)
- {
- SendersEnum senders = getSenders(mediaType);
- if (senders == SendersEnum.none)
- return MediaDirection.INACTIVE;
- else if (senders == null || senders == SendersEnum.both)
- return MediaDirection.SENDRECV;
- else if (senders == SendersEnum.initiator)
- return isInitiator()
- ? MediaDirection.RECVONLY
- : MediaDirection.SENDONLY;
- else //senders == SendersEnum.responder
- return isInitiator()
- ? MediaDirection.SENDONLY
- : MediaDirection.RECVONLY;
- }
-
- /**
- * Get the current value of the <tt>senders</tt> field of the content with
- * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
- * @param mediaType the <tt>MediaType</tt> for which to get the current
- * value of the <tt>senders</tt> field.
- * @return the current value of the <tt>senders</tt> field of the content
- * with name <tt>mediaType</tt> in the Jingle session with this
- * <tt>CallPeer</tt>.
- */
- public SendersEnum getSenders(MediaType mediaType)
- {
- if (MediaType.AUDIO.equals(mediaType))
- return audioSenders;
- else if (MediaType.VIDEO.equals(mediaType))
- return videoSenders;
- else
- throw new IllegalArgumentException("mediaType");
- }
-
- /**
- * Set the current value of the <tt>senders</tt> field of the content with
- * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
- * @param mediaType the <tt>MediaType</tt> for which to get the current
- * value of the <tt>senders</tt> field.
- * @param senders the value to set
- */
- public void setSenders(MediaType mediaType, SendersEnum senders)
- {
- if (mediaType == null)
- return;
- else if (MediaType.AUDIO.equals(mediaType))
- this.audioSenders = senders;
- else if (MediaType.VIDEO.equals(mediaType))
- this.videoSenders = senders;
- else
- throw new IllegalArgumentException("mediaType");
- }
-
- /**
- * Gets the <tt>MediaType</tt> of <tt>content</tt>. If <tt>content</tt>
- * does not have a <tt>description</tt> child and therefore not
- * <tt>MediaType</tt> can be associated with it, tries to take the
- * <tt>MediaType</tt> from the session's already established contents with
- * the same name as <tt>content</tt>
- * @param content the <tt>ContentPacketExtention</tt> for which to get the
- * <tt>MediaType</tt>
- * @return the <tt>MediaType</tt> of <tt>content</tt>.
- */
- public MediaType getMediaType (ContentPacketExtension content)
- {
- String contentName = content.getName();
- if (contentName == null)
- return null;
-
- MediaType mediaType = JingleUtils.getMediaType(content);
- if (mediaType == null)
- {
- CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
- for (MediaType m : MediaType.values())
- {
- ContentPacketExtension sessionContent
- = mediaHandler.getRemoteContent(m.toString());
- if (sessionContent == null)
- sessionContent = mediaHandler.getLocalContent(m.toString());
-
- if (sessionContent != null
- && contentName.equals(sessionContent.getName()))
- {
- mediaType = m;
- break;
- }
- }
- }
-
- return mediaType;
- }
-}
+/*
+ * 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.lang.reflect.*;
+import java.util.*;
+
+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.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Implements a Jabber <tt>CallPeer</tt>.
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Boris Grozev
+ */
+public class CallPeerJabberImpl
+ extends AbstractCallPeerJabberGTalkImpl
+ <CallJabberImpl, CallPeerMediaHandlerJabberImpl, JingleIQ>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>CallPeerJabberImpl</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(CallPeerJabberImpl.class);
+
+ /**
+ * If the call is cancelled before session-initiate is sent.
+ */
+ private boolean cancelled = false;
+
+ /**
+ * Synchronization object for candidates available.
+ */
+ private final Object candSyncRoot = new Object();
+
+ /**
+ * If the content-add does not contains candidates.
+ */
+ private boolean contentAddWithNoCands = false;
+
+ /**
+ * If we have processed the session initiate.
+ */
+ private boolean sessionInitiateProcessed = false;
+
+ /**
+ * Synchronization object. Synchronization object? Wow, who would have
+ * thought! ;) Would be great to have a word on what we are syncing with it
+ */
+ private final Object sessionInitiateSyncRoot = new Object();
+
+ /**
+ * Synchronization object for SID.
+ */
+ private final Object sidSyncRoot = new Object();
+
+ /**
+ * The current value of the 'senders' field of the audio content in the
+ * Jingle session with this <tt>CallPeer</tt>.
+ * <tt>null</tt> should be interpreted as 'both', which is the default in
+ * Jingle if the XML attribute is missing.
+ */
+ private SendersEnum audioSenders = SendersEnum.none;
+
+ /**
+ * The current value of the 'senders' field of the video content in the
+ * Jingle session with this <tt>CallPeer</tt>.
+ * <tt>null</tt> should be interpreted as 'both', which is the default in
+ * Jingle if the XML attribute is missing.
+ */
+ private SendersEnum videoSenders = SendersEnum.none;
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param peerAddress the Jabber address of the new call peer.
+ * @param owningCall the call that contains this call peer.
+ */
+ public CallPeerJabberImpl(String peerAddress,
+ CallJabberImpl owningCall)
+ {
+ super(peerAddress, owningCall);
+
+ setMediaHandler(new CallPeerMediaHandlerJabberImpl(this));
+ }
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param peerAddress the Jabber address of the new call peer.
+ * @param owningCall the call that contains this call peer.
+ * @param sessionIQ The session-initiate <tt>JingleIQ</tt> which was
+ * received from <tt>peerAddress</tt> and caused the creation of this
+ * <tt>CallPeerJabberImpl</tt>
+ */
+ public CallPeerJabberImpl(String peerAddress,
+ CallJabberImpl owningCall,
+ JingleIQ sessionIQ)
+ {
+ this(peerAddress, owningCall);
+ this.sessionInitIQ = sessionIQ;
+ }
+
+ /**
+ * Send a session-accept <tt>JingleIQ</tt> to this <tt>CallPeer</tt>
+ * @throws OperationFailedException if we fail to create or send the
+ * response.
+ */
+ public synchronized void answer()
+ throws OperationFailedException
+ {
+ Iterable<ContentPacketExtension> answer;
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ try
+ {
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ answer = mediaHandler.generateSessionAccept();
+ for (ContentPacketExtension c : answer)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+ catch(Exception exc)
+ {
+ logger.info("Failed to answer an incoming call", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ Reason.FAILED_APPLICATION,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ JingleIQ response
+ = JinglePacketFactory.createSessionAccept(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ getSID(),
+ answer);
+
+ //send the packet first and start the stream later in case the media
+ //relay needs to see it before letting hole punching techniques through.
+ getProtocolProvider().getConnection().sendPacket(response);
+
+ try
+ {
+ mediaHandler.start();
+ }
+ catch(UndeclaredThrowableException e)
+ {
+ Throwable exc = e.getUndeclaredThrowable();
+
+ logger.info("Failed to establish a connection", exc);
+
+ //send an error response
+ String reasonText = "Error: " + exc.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ //tell everyone we are connected so that the audio notifications would
+ //stop
+ setState(CallPeerState.CONNECTED);
+ }
+
+ /**
+ * Returns the session ID of the Jingle session associated with this call.
+ *
+ * @return the session ID of the Jingle session associated with this call.
+ */
+ @Override
+ public String getSID()
+ {
+ return sessionInitIQ != null ? sessionInitIQ.getSID() : null;
+ }
+
+ /**
+ * Returns the IQ ID of the Jingle session-initiate packet associated with
+ * this call.
+ *
+ * @return the IQ ID of the Jingle session-initiate packet associated with
+ * this call.
+ */
+ public JingleIQ getSessionIQ()
+ {
+ return sessionInitIQ;
+ }
+
+ /**
+ * Ends the call with this <tt>CallPeer</tt>. Depending on the state
+ * of the peer the method would send a CANCEL, BYE, or BUSY_HERE message
+ * and set the new state to DISCONNECTED.
+ *
+ * @param failed indicates if the hangup is following to a call failure or
+ * simply a disconnect
+ * @param reasonText the text, if any, to be set on the
+ * <tt>ReasonPacketExtension</tt> as the value of its
+ * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be
+ * set on the <tt>ReasonPacketExtension</tt> as the value of its
+ * <tt>otherExtension</tt> property
+ */
+ public void hangup(boolean failed,
+ String reasonText,
+ PacketExtension reasonOtherExtension)
+ {
+ CallPeerState prevPeerState = getState();
+
+ // do nothing if the call is already ended
+ if (CallPeerState.DISCONNECTED.equals(prevPeerState)
+ || CallPeerState.FAILED.equals(prevPeerState))
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Ignoring a request to hangup a call peer "
+ + "that is already DISCONNECTED");
+ return;
+ }
+
+ setState(
+ failed ? CallPeerState.FAILED : CallPeerState.DISCONNECTED,
+ reasonText);
+
+ JingleIQ responseIQ = null;
+
+ if (prevPeerState.equals(CallPeerState.CONNECTED)
+ || CallPeerState.isOnHold(prevPeerState))
+ {
+ responseIQ = JinglePacketFactory.createBye(
+ getProtocolProvider().getOurJID(), peerJID, getSID());
+ }
+ else if (CallPeerState.CONNECTING.equals(prevPeerState)
+ || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
+ || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState))
+ {
+ String jingleSID = getSID();
+
+ if(jingleSID == null)
+ {
+ synchronized(sidSyncRoot)
+ {
+ // we cancelled the call too early because the jingleSID
+ // is null (i.e. the session-initiate has not been created)
+ // and no need to send the session-terminate
+ cancelled = true;
+ return;
+ }
+ }
+
+ responseIQ = JinglePacketFactory.createCancel(
+ getProtocolProvider().getOurJID(), peerJID, getSID());
+ }
+ else if (prevPeerState.equals(CallPeerState.INCOMING_CALL))
+ {
+ responseIQ = JinglePacketFactory.createBusy(
+ getProtocolProvider().getOurJID(), peerJID, getSID());
+ }
+ else if (prevPeerState.equals(CallPeerState.BUSY)
+ || prevPeerState.equals(CallPeerState.FAILED))
+ {
+ // For FAILED and BUSY we only need to update CALL_STATUS
+ // as everything else has been done already.
+ }
+ else
+ {
+ logger.info("Could not determine call peer state!");
+ }
+
+ if (responseIQ != null)
+ {
+ if (reasonOtherExtension != null)
+ {
+ ReasonPacketExtension reason
+ = (ReasonPacketExtension)
+ responseIQ.getExtension(
+ ReasonPacketExtension.ELEMENT_NAME,
+ ReasonPacketExtension.NAMESPACE);
+
+ if (reason != null)
+ {
+ reason.setOtherExtension(reasonOtherExtension);
+ }
+ else if(reasonOtherExtension instanceof ReasonPacketExtension)
+ {
+ responseIQ.setReason(
+ (ReasonPacketExtension)reasonOtherExtension);
+ }
+ }
+
+ getProtocolProvider().getConnection().sendPacket(responseIQ);
+ }
+ }
+
+ /**
+ * Creates and sends a session-initiate {@link JingleIQ}.
+ *
+ * @param sessionInitiateExtensions a collection of additional and optional
+ * <tt>PacketExtension</tt>s to be added to the <tt>session-initiate</tt>
+ * {@link JingleIQ} which is to initiate the session with this
+ * <tt>CallPeerJabberImpl</tt>
+ * @throws OperationFailedException exception
+ */
+ protected synchronized void initiateSession(
+ Iterable<PacketExtension> sessionInitiateExtensions)
+ throws OperationFailedException
+ {
+ initiator = false;
+
+ //Create the media description that we'd like to send to the other side.
+ List<ContentPacketExtension> offer
+ = getMediaHandler().createContentList();
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ synchronized(sidSyncRoot)
+ {
+ sessionInitIQ
+ = JinglePacketFactory.createSessionInitiate(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ JingleIQ.generateSID(),
+ offer);
+
+ if(cancelled)
+ {
+ // we cancelled the call too early so no need to send the
+ // session-initiate to peer
+ getMediaHandler().getTransportManager().close();
+ return;
+ }
+ }
+
+ if (sessionInitiateExtensions != null)
+ {
+ for (PacketExtension sessionInitiateExtension
+ : sessionInitiateExtensions)
+ {
+ sessionInitIQ.addExtension(sessionInitiateExtension);
+ }
+ }
+
+ protocolProvider.getConnection().sendPacket(sessionInitIQ);
+ }
+
+ /**
+ * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
+ * been received. This <tt>CallPeerJabberImpl</tt> uses the part of the
+ * information provided in the specified <tt>conferenceIQ</tt> which
+ * concerns it only.
+ *
+ * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
+ * received
+ */
+ void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
+ {
+ /*
+ * CallPeerJabberImpl does not itself/directly know the specifics
+ * related to the channels allocated on the Jitsi Videobridge server.
+ * The channels contain transport and media-related information so
+ * forward the notification to CallPeerMediaHandlerJabberImpl.
+ */
+ getMediaHandler().processColibriConferenceIQ(conferenceIQ);
+ }
+
+ /**
+ * Processes the content-accept {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer has accepted
+ */
+ public void processContentAccept(JingleIQ content)
+ {
+ List<ContentPacketExtension> contents = content.getContentList();
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ try
+ {
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ mediaHandler.processAnswer(contents);
+ for (ContentPacketExtension c : contents)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+ catch (Exception e)
+ {
+ logger.warn("Failed to process a content-accept", e);
+
+ // Send an error response.
+ String reason = "Error: " + e.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ sessionInitIQ.getSID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reason);
+
+ setState(CallPeerState.FAILED, reason);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ mediaHandler.start();
+ }
+
+ /**
+ * Processes the content-add {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer wants to be added
+ */
+ public void processContentAdd(final JingleIQ content)
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ List<ContentPacketExtension> contents = content.getContentList();
+ Iterable<ContentPacketExtension> answerContents;
+ JingleIQ contentIQ;
+ boolean noCands = false;
+ MediaStream oldVideoStream = mediaHandler.getStream(MediaType.VIDEO);
+
+ if(logger.isInfoEnabled())
+ logger.info("Looking for candidates in content-add.");
+ try
+ {
+ if(!contentAddWithNoCands)
+ {
+ mediaHandler.processOffer(contents);
+
+ /*
+ * Gingle transport will not put candidate in session-initiate
+ * and content-add.
+ */
+ for(ContentPacketExtension c : contents)
+ {
+ if(JingleUtils.getFirstCandidate(c, 1) == null)
+ {
+ contentAddWithNoCands = true;
+ noCands = true;
+ }
+ }
+ }
+
+ // if no candidates are present, launch a new Thread which will
+ // process and wait for the connectivity establishment (otherwise
+ // the existing thread will be blocked and thus cannot receive
+ // transport-info with candidates
+ if(noCands)
+ {
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ synchronized(candSyncRoot)
+ {
+ candSyncRoot.wait();
+ }
+ }
+ catch(InterruptedException e)
+ {
+ }
+
+ processContentAdd(content);
+ contentAddWithNoCands = false;
+ }
+ }.start();
+ if(logger.isInfoEnabled())
+ logger.info("No candidates found in content-add, started "
+ + "new thread.");
+ return;
+ }
+
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ if(logger.isInfoEnabled())
+ logger.info("Wrapping up connectivity establishment");
+ answerContents = mediaHandler.generateSessionAccept();
+ contentIQ = null;
+ }
+ catch(Exception e)
+ {
+ logger.warn("Exception occurred", e);
+
+ answerContents = null;
+ contentIQ
+ = JinglePacketFactory.createContentReject(
+ getProtocolProvider().getOurJID(),
+ this.peerJID,
+ getSID(),
+ answerContents);
+ }
+
+ if(contentIQ == null)
+ {
+ /* send content-accept */
+ contentIQ
+ = JinglePacketFactory.createContentAccept(
+ getProtocolProvider().getOurJID(),
+ this.peerJID,
+ getSID(),
+ answerContents);
+ for (ContentPacketExtension c : answerContents)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+
+ getProtocolProvider().getConnection().sendPacket(contentIQ);
+ mediaHandler.start();
+
+ /*
+ * If a remote peer turns her video on in a conference which is hosted
+ * by the local peer and the local peer is not streaming her local
+ * video, reinvite the other remote peers to enable RTP translation.
+ */
+ if (oldVideoStream == null)
+ {
+ MediaStream newVideoStream
+ = mediaHandler.getStream(MediaType.VIDEO);
+
+ if ((newVideoStream != null)
+ && mediaHandler.isRTPTranslationEnabled(MediaType.VIDEO))
+ {
+ try
+ {
+ getCall().modifyVideoContent();
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error("Failed to enable RTP translation", ofe);
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes the content-modify {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer wants to be modified
+ */
+ public void processContentModify(JingleIQ content)
+ {
+ ContentPacketExtension ext = content.getContentList().get(0);
+ MediaType mediaType = getMediaType(ext);
+
+ try
+ {
+ boolean modify
+ = (ext.getFirstChildOfType(RtpDescriptionPacketExtension.class)
+ != null);
+
+ getMediaHandler().reinitContent(ext.getName(), ext, modify);
+
+ setSenders(mediaType, ext.getSenders());
+
+ if (MediaType.VIDEO.equals(mediaType))
+ getCall().modifyVideoContent();
+ }
+ catch(Exception e)
+ {
+ logger.info("Failed to process an incoming content-modify", e);
+
+ // Send an error response.
+ String reason = "Error: " + e.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ sessionInitIQ.getSID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reason);
+
+ setState(CallPeerState.FAILED, reason);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+ }
+
+ /**
+ * Processes the content-reject {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ}
+ */
+ public void processContentReject(JingleIQ content)
+ {
+ if(content.getContentList().isEmpty())
+ {
+ //send an error response;
+ JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
+ "Error: content rejected");
+
+ setState(CallPeerState.FAILED, "Error: content rejected");
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+ }
+
+ /**
+ * Processes the content-remove {@link JingleIQ}.
+ *
+ * @param content The {@link JingleIQ} that contains content that remote
+ * peer wants to be removed
+ */
+ public void processContentRemove(JingleIQ content)
+ {
+ List<ContentPacketExtension> contents = content.getContentList();
+ boolean videoContentRemoved = false;
+
+ if (!contents.isEmpty())
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ for(ContentPacketExtension c : contents)
+ {
+ mediaHandler.removeContent(c.getName());
+
+ MediaType mediaType = getMediaType(c);
+ setSenders(mediaType, SendersEnum.none);
+
+ if (MediaType.VIDEO.equals(mediaType))
+ videoContentRemoved = true;
+ }
+
+ /*
+ * TODO XEP-0166: Jingle says: If the content-remove results in zero
+ * content definitions for the session, the entity that receives the
+ * content-remove SHOULD send a session-terminate action to the
+ * other party (since a session with no content definitions is
+ * void).
+ */
+ }
+
+ if (videoContentRemoved)
+ {
+ // removing of the video content might affect the other sessions
+ // in the call
+ try
+ {
+ getCall().modifyVideoContent();
+ }
+ catch (Exception e)
+ {
+ logger.warn("Failed to update Jingle sessions");
+ }
+ }
+ }
+
+ /**
+ * Processes a session-accept {@link JingleIQ}.
+ *
+ * @param sessionInitIQ The session-accept {@link JingleIQ} to process.
+ */
+ public void processSessionAccept(JingleIQ sessionInitIQ)
+ {
+ this.sessionInitIQ = sessionInitIQ;
+
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ List<ContentPacketExtension> answer = sessionInitIQ.getContentList();
+
+ try
+ {
+ mediaHandler
+ .getTransportManager()
+ .wrapupConnectivityEstablishment();
+ mediaHandler.processAnswer(answer);
+ for (ContentPacketExtension c : answer)
+ setSenders(getMediaType(c), c.getSenders());
+ }
+ catch(Exception exc)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Failed to process a session-accept", exc);
+
+ //send an error response;
+ JingleIQ errResp = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(), sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(), Reason.INCOMPATIBLE_PARAMETERS,
+ exc.getClass().getName() + ": " + exc.getMessage());
+
+ setState(CallPeerState.FAILED, "Error: " + exc.getMessage());
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ //tell everyone we are connected so that the audio notifications would
+ //stop
+ setState(CallPeerState.CONNECTED);
+
+ mediaHandler.start();
+
+ /*
+ * If video was added to the call after we sent the session-initiate
+ * to this peer, it needs to be added to this peer's session with a
+ * content-add.
+ */
+ sendModifyVideoContent();
+ }
+
+ /**
+ * Handles the specified session <tt>info</tt> packet according to its
+ * content.
+ *
+ * @param info the {@link SessionInfoPacketExtension} that we just received.
+ */
+ public void processSessionInfo(SessionInfoPacketExtension info)
+ {
+ switch (info.getType())
+ {
+ case ringing:
+ setState(CallPeerState.ALERTING_REMOTE_SIDE);
+ break;
+ case hold:
+ getMediaHandler().setRemotelyOnHold(true);
+ reevalRemoteHoldStatus();
+ break;
+ case unhold:
+ case active:
+ getMediaHandler().setRemotelyOnHold(false);
+ reevalRemoteHoldStatus();
+ break;
+ default:
+ logger.warn("Received SessionInfoPacketExtension of unknown type");
+ }
+ }
+
+ /**
+ * Processes the session initiation {@link JingleIQ} that we were created
+ * with, passing its content to the media handler and then sends either a
+ * "session-info/ringing" or a "session-terminate" response.
+ *
+ * @param sessionInitIQ The {@link JingleIQ} that created the session that
+ * we are handling here.
+ */
+ protected synchronized void processSessionInitiate(JingleIQ sessionInitIQ)
+ {
+ // Do initiate the session.
+ this.sessionInitIQ = sessionInitIQ;
+ this.initiator = true;
+
+ // This is the SDP offer that came from the initial session-initiate.
+ // Contrary to SIP, we are guaranteed to have content because XEP-0166
+ // says: "A session consists of at least one content type at a time."
+ List<ContentPacketExtension> offer = sessionInitIQ.getContentList();
+
+ try
+ {
+ getMediaHandler().processOffer(offer);
+
+ CoinPacketExtension coin = null;
+
+ for(PacketExtension ext : sessionInitIQ.getExtensions())
+ {
+ if(ext.getElementName().equals(
+ CoinPacketExtension.ELEMENT_NAME))
+ {
+ coin = (CoinPacketExtension)ext;
+ break;
+ }
+ }
+
+ /* does the call peer acts as a conference focus ? */
+ if(coin != null)
+ {
+ setConferenceFocus(Boolean.parseBoolean(
+ (String)coin.getAttribute("isfocus")));
+ }
+ }
+ catch(Exception ex)
+ {
+ logger.info("Failed to process an incoming session initiate", ex);
+
+ //send an error response;
+ String reasonText = "Error: " + ex.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ Reason.INCOMPATIBLE_PARAMETERS,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+ return;
+ }
+
+ // If we do not get the info about the remote peer yet. Get it right
+ // now.
+ if(this.getDiscoveryInfo() == null)
+ {
+ String calleeURI = sessionInitIQ.getFrom();
+ retrieveDiscoveryInfo(calleeURI);
+ }
+
+ //send a ringing response
+ if (logger.isTraceEnabled())
+ logger.trace("will send ringing response: ");
+
+ getProtocolProvider().getConnection().sendPacket(
+ JinglePacketFactory.createRinging(sessionInitIQ));
+
+ synchronized(sessionInitiateSyncRoot)
+ {
+ sessionInitiateProcessed = true;
+ sessionInitiateSyncRoot.notify();
+ }
+
+ //if this is a 3264 initiator, let's give them an early peek at our
+ //answer so that they could start ICE (SIP-2-Jingle gateways won't
+ //be able to send their candidates unless they have this)
+ DiscoverInfo discoverInfo = getDiscoveryInfo();
+ if ((discoverInfo != null)
+ && discoverInfo.containsFeature(
+ ProtocolProviderServiceJabberImpl.URN_IETF_RFC_3264))
+ {
+ getProtocolProvider().getConnection().sendPacket(
+ JinglePacketFactory.createDescriptionInfo(
+ sessionInitIQ.getTo(),
+ sessionInitIQ.getFrom(),
+ sessionInitIQ.getSID(),
+ getMediaHandler().getLocalContentList()));
+ }
+ }
+
+ /**
+ * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a
+ * reason to the user, if there is one.
+ *
+ * @param jingleIQ the {@link JingleIQ} that's terminating our session.
+ */
+ public void processSessionTerminate(JingleIQ jingleIQ)
+ {
+ String reasonStr = "Call ended by remote side.";
+ ReasonPacketExtension reasonExt = jingleIQ.getReason();
+
+ if(reasonExt != null)
+ {
+ Reason reason = reasonExt.getReason();
+
+ if(reason != null)
+ reasonStr += " Reason: " + reason.toString() + ".";
+
+ String text = reasonExt.getText();
+
+ if(text != null)
+ reasonStr += " " + text;
+ }
+
+ setState(CallPeerState.DISCONNECTED, reasonStr);
+ }
+
+ /**
+ * Processes a specific "XEP-0251: Jingle Session Transfer"
+ * <tt>transfer</tt> packet (extension).
+ *
+ * @param transfer the "XEP-0251: Jingle Session Transfer" transfer packet
+ * (extension) to process
+ * @throws OperationFailedException if anything goes wrong while processing
+ * the specified <tt>transfer</tt> packet (extension)
+ */
+ public void processTransfer(TransferPacketExtension transfer)
+ throws OperationFailedException
+ {
+ String attendantAddress = transfer.getFrom();
+
+ if (attendantAddress == null)
+ {
+ throw new OperationFailedException(
+ "Session transfer must contain a \'from\' attribute value.",
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+
+ String calleeAddress = transfer.getTo();
+
+ if (calleeAddress == null)
+ {
+ throw new OperationFailedException(
+ "Session transfer must contain a \'to\' attribute value.",
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+
+ // Checks if the transfer remote peer is contained by the roster of this
+ // account.
+ Roster roster = getProtocolProvider().getConnection().getRoster();
+ if(!roster.contains(StringUtils.parseBareAddress(calleeAddress)))
+ {
+ String failedMessage =
+ "Tranfer impossible:\n"
+ + "Account roster does not contain tansfer peer: "
+ + StringUtils.parseBareAddress(calleeAddress);
+ setState(CallPeerState.FAILED, failedMessage);
+ logger.info(failedMessage);
+ }
+
+ OperationSetBasicTelephonyJabberImpl basicTelephony
+ = (OperationSetBasicTelephonyJabberImpl)
+ getProtocolProvider()
+ .getOperationSet(OperationSetBasicTelephony.class);
+ CallJabberImpl calleeCall = new CallJabberImpl(basicTelephony);
+ TransferPacketExtension calleeTransfer = new TransferPacketExtension();
+ String sid = transfer.getSID();
+
+ calleeTransfer.setFrom(attendantAddress);
+ if (sid != null)
+ {
+ calleeTransfer.setSID(sid);
+ calleeTransfer.setTo(calleeAddress);
+ }
+ basicTelephony.createOutgoingCall(
+ calleeCall,
+ calleeAddress,
+ Arrays.asList(new PacketExtension[] { calleeTransfer }));
+ }
+
+ /**
+ * Processes the <tt>transport-info</tt> {@link JingleIQ}.
+ *
+ * @param jingleIQ the <tt>transport-info</tt> {@link JingleIQ} to process
+ */
+ public void processTransportInfo(JingleIQ jingleIQ)
+ {
+ /*
+ * The transport-info action is used to exchange transport candidates so
+ * it only concerns the mediaHandler.
+ */
+ try
+ {
+ if(isInitiator())
+ {
+ synchronized(sessionInitiateSyncRoot)
+ {
+ if(!sessionInitiateProcessed)
+ {
+ try
+ {
+ sessionInitiateSyncRoot.wait();
+ }
+ catch(InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+
+ getMediaHandler().processTransportInfo(
+ jingleIQ.getContentList());
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.warn("Failed to process an incoming transport-info", ofe);
+
+ //send an error response
+ String reasonText = "Error: " + ofe.getMessage();
+ JingleIQ errResp
+ = JinglePacketFactory.createSessionTerminate(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ sessionInitIQ.getSID(),
+ Reason.GENERAL_ERROR,
+ reasonText);
+
+ setState(CallPeerState.FAILED, reasonText);
+ getProtocolProvider().getConnection().sendPacket(errResp);
+
+ return;
+ }
+
+ synchronized(candSyncRoot)
+ {
+ candSyncRoot.notify();
+ }
+ }
+
+ /**
+ * Puts the <tt>CallPeer</tt> represented by this instance on or off hold.
+ *
+ * @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
+ * <tt>false</tt>, otherwise
+ *
+ * @throws OperationFailedException if we fail to construct or send the
+ * INVITE request putting the remote side on/off hold.
+ */
+ public void putOnHold(boolean onHold)
+ throws OperationFailedException
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ mediaHandler.setLocallyOnHold(onHold);
+
+ SessionInfoType type;
+
+ if(onHold)
+ type = SessionInfoType.hold;
+ else
+ {
+ type = SessionInfoType.unhold;
+ getMediaHandler().reinitAllContents();
+ }
+
+ //we are now on hold and need to realize this before potentially
+ //spoiling it all with an exception while sending the packet :).
+ reevalLocalHoldStatus();
+
+ JingleIQ onHoldIQ = JinglePacketFactory.createSessionInfo(
+ getProtocolProvider().getOurJID(),
+ peerJID,
+ getSID(),
+ type);
+
+ getProtocolProvider().getConnection().sendPacket(onHoldIQ);
+ }
+
+ /**
+ * Send a <tt>content-add</tt> to add video setup.
+ */
+ private void sendAddVideoContent()
+ {
+ List<ContentPacketExtension> contents;
+
+ try
+ {
+ contents = getMediaHandler().createContentList(MediaType.VIDEO);
+ }
+ catch(Exception exc)
+ {
+ logger.warn("Failed to gather content for video type", exc);
+ return;
+ }
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentAdd(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ contents);
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+ }
+
+ /**
+ * Sends a <tt>content</tt> message to reflect changes in the setup such as
+ * the local peer/user becoming a conference focus.
+ */
+ public void sendCoinSessionInfo()
+ {
+ JingleIQ sessionInfoIQ
+ = JinglePacketFactory.createSessionInfo(
+ getProtocolProvider().getOurJID(),
+ this.peerJID,
+ getSID());
+ CoinPacketExtension coinExt
+ = new CoinPacketExtension(getCall().isConferenceFocus());
+
+ sessionInfoIQ.addExtension(coinExt);
+ getProtocolProvider().getConnection().sendPacket(sessionInfoIQ);
+ }
+
+ /**
+ * Returns the <tt>MediaDirection</tt> that should be set for the content
+ * of type <tt>mediaType</tt> in the Jingle session for this
+ * <tt>CallPeer</tt>.
+ * If we are the focus of a conference and are doing RTP translation,
+ * takes into account the other <tt>CallPeer</tt>s in the <tt>Call</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> for which to return the
+ * <tt>MediaDirection</tt>
+ * @return the <tt>MediaDirection</tt> that should be used for the content
+ * of type <tt>mediaType</tt> in the Jingle session for this
+ * <tt>CallPeer</tt>.
+ */
+ private MediaDirection getDirectionForJingle(MediaType mediaType)
+ {
+ MediaDirection direction = MediaDirection.INACTIVE;
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ // If we are streaming media, the direction should allow sending
+ if ( (MediaType.AUDIO == mediaType &&
+ mediaHandler.isLocalAudioTransmissionEnabled()) ||
+ (MediaType.VIDEO == mediaType &&
+ isLocalVideoStreaming()))
+ direction = direction.or(MediaDirection.SENDONLY);
+
+ // If we are receiving media from this CallPeer, the direction should
+ // allow receiving
+ SendersEnum senders = getSenders(mediaType);
+ if (senders == null || senders == SendersEnum.both ||
+ (isInitiator() && senders == SendersEnum.initiator) ||
+ (!isInitiator() && senders == SendersEnum.responder))
+ direction = direction.or(MediaDirection.RECVONLY);
+
+ // If we are the focus of a conference and we are receiving media from
+ // another CallPeer in the same Call, the direction should allow sending
+ if (getCall().isConferenceFocus())
+ {
+ for (CallPeerJabberImpl peer : getCall().getCallPeerList())
+ {
+ if (peer != this)
+ {
+ senders = peer.getSenders(mediaType);
+ if (senders == null || senders == SendersEnum.both ||
+ (peer.isInitiator()
+ && senders == SendersEnum.initiator) ||
+ (!peer.isInitiator()
+ && senders == SendersEnum.responder))
+ {
+ direction = direction.or(MediaDirection.SENDONLY);
+ break;
+ }
+ }
+ }
+ }
+
+ return direction;
+ }
+
+ /**
+ * Send, if necessary, a jingle <tt>content</tt> message to reflect change
+ * in video setup. Whether the jingle session should have a video content,
+ * and if so, the value of the <tt>senders</tt> field is determined
+ * based on whether we are streaming local video and, if we are the focus
+ * of a conference, on the other peers in the conference.
+ * The message can be content-modify if video content exists (and the
+ * <tt>senders</tt> field changes), content-add or content-remove.
+ *
+ * @return <tt>true</tt> if a jingle <tt>content</tt> message was sent.
+ */
+ public boolean sendModifyVideoContent()
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ MediaDirection direction = getDirectionForJingle(MediaType.VIDEO);
+
+ ContentPacketExtension remoteContent
+ = mediaHandler.getLocalContent(MediaType.VIDEO.toString());
+
+ if (remoteContent == null)
+ {
+ if (direction == MediaDirection.INACTIVE)
+ {
+ // no video content, none needed
+ return false;
+ }
+ else
+ {
+ if (getState() == CallPeerState.CONNECTED)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Adding video content for " + this);
+ sendAddVideoContent();
+ return true;
+ }
+ return false;
+ }
+ }
+ else
+ {
+ if (direction == MediaDirection.INACTIVE)
+ {
+ sendRemoveVideoContent();
+ return true;
+ }
+ }
+
+ SendersEnum senders = getSenders(MediaType.VIDEO);
+ if (senders == null)
+ senders = SendersEnum.both;
+
+ SendersEnum newSenders = SendersEnum.none;
+ if (MediaDirection.SENDRECV == direction)
+ newSenders = SendersEnum.both;
+ else if (MediaDirection.RECVONLY == direction)
+ newSenders = isInitiator()
+ ? SendersEnum.initiator : SendersEnum.responder;
+ else if (MediaDirection.SENDONLY == direction)
+ newSenders = isInitiator()
+ ? SendersEnum.responder : SendersEnum.initiator;
+
+ /*
+ * Send Content-Modify
+ */
+ ContentPacketExtension ext = new ContentPacketExtension();
+ String remoteContentName = remoteContent.getName();
+
+ ext.setSenders(newSenders);
+ ext.setCreator(remoteContent.getCreator());
+ ext.setName(remoteContentName);
+
+ if (newSenders != senders)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Sending content modify, senders: "
+ + senders + "->" + newSenders);
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentModify(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ ext);
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+ }
+
+ try
+ {
+ mediaHandler.reinitContent(remoteContentName, ext, false);
+ mediaHandler.start();
+ }
+ catch(Exception e)
+ {
+ logger.warn("Exception occurred during media reinitialization", e);
+ }
+
+ return (newSenders != senders);
+ }
+
+ /**
+ * Send a <tt>content</tt> message to reflect change in video setup (start
+ * or stop).
+ */
+ public void sendModifyVideoResolutionContent()
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ ContentPacketExtension remoteContent
+ = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
+ ContentPacketExtension content;
+
+ logger.info("send modify-content to change resolution");
+
+ // send content-modify with RTP description
+
+ // create content list with resolution
+ try
+ {
+ content = mediaHandler.createContentForMedia(MediaType.VIDEO);
+ }
+ catch (Exception e)
+ {
+ logger.warn("Failed to gather content for video type", e);
+ return;
+ }
+
+ // if we are only receiving video senders is null
+ SendersEnum senders = remoteContent.getSenders();
+
+ if (senders != null)
+ content.setSenders(senders);
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentModify(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ content);
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+
+ try
+ {
+ mediaHandler.reinitContent(remoteContent.getName(), content, false);
+ mediaHandler.start();
+ }
+ catch(Exception e)
+ {
+ logger.warn("Exception occurred when media reinitialization", e);
+ }
+ }
+
+ /**
+ * Send a <tt>content-remove</tt> to remove video setup.
+ */
+ private void sendRemoveVideoContent()
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+
+ ContentPacketExtension content = new ContentPacketExtension();
+ ContentPacketExtension remoteContent
+ = mediaHandler.getRemoteContent(MediaType.VIDEO.toString());
+ if (remoteContent == null)
+ return;
+ String remoteContentName = remoteContent.getName();
+
+ content.setName(remoteContentName);
+ content.setCreator(remoteContent.getCreator());
+ content.setSenders(remoteContent.getSenders());
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+ JingleIQ contentIQ
+ = JinglePacketFactory.createContentRemove(
+ protocolProvider.getOurJID(),
+ this.peerJID,
+ getSID(),
+ Arrays.asList(content));
+
+ protocolProvider.getConnection().sendPacket(contentIQ);
+ mediaHandler.removeContent(remoteContentName);
+ setSenders(MediaType.VIDEO, SendersEnum.none);
+ }
+
+ /**
+ * Sends local candidate addresses from the local peer to the remote peer
+ * using the <tt>transport-info</tt> {@link JingleIQ}.
+ *
+ * @param contents the local candidate addresses to be sent from the local
+ * peer to the remote peer using the <tt>transport-info</tt>
+ * {@link JingleIQ}
+ */
+ protected void sendTransportInfo(Iterable<ContentPacketExtension> contents)
+ {
+ // if the call is canceled, do not start sending candidates in
+ // transport-info
+ if(cancelled)
+ return;
+
+ JingleIQ transportInfo = new JingleIQ();
+
+ for (ContentPacketExtension content : contents)
+ transportInfo.addContent(content);
+
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ transportInfo.setAction(JingleAction.TRANSPORT_INFO);
+ transportInfo.setFrom(protocolProvider.getOurJID());
+ transportInfo.setSID(getSID());
+ transportInfo.setTo(getAddress());
+ transportInfo.setType(IQ.Type.SET);
+
+ PacketCollector collector
+ = protocolProvider.getConnection().createPacketCollector(
+ new PacketIDFilter(transportInfo.getPacketID()));
+
+ protocolProvider.getConnection().sendPacket(transportInfo);
+ collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ collector.cancel();
+ }
+
+ @Override
+ public void setState(CallPeerState newState, String reason, int reasonCode)
+ {
+ CallPeerState oldState = getState();
+ try
+ {
+ /*
+ * We need to dispose of the transport manager before the
+ * 'call' field is set to null, because if Jitsi Videobridge is in
+ * use, it (the call) is needed in order to expire the
+ * Videobridge channels.
+ */
+ if (CallPeerState.DISCONNECTED.equals(newState)
+ || CallPeerState.FAILED.equals(newState))
+ getMediaHandler().getTransportManager().close();
+ }
+ finally
+ {
+ super.setState(newState, reason, reasonCode);
+ }
+
+ if (CallPeerState.isOnHold(oldState)
+ && CallPeerState.CONNECTED.equals(newState))
+ {
+ try
+ {
+ getCall().modifyVideoContent();
+ }
+ catch (OperationFailedException ofe)
+ {
+ logger.error("Failed to update call video state after " +
+ "'hold' status removed for "+this);
+ }
+ }
+ }
+
+ /**
+ * Transfer (in the sense of call transfer) this <tt>CallPeer</tt> to a
+ * specific callee address which may optionally be participating in an
+ * active <tt>Call</tt>.
+ *
+ * @param to the address of the callee to transfer this <tt>CallPeer</tt> to
+ * @param sid the Jingle session ID of the active <tt>Call</tt> between the
+ * local peer and the callee in the case of attended transfer; <tt>null</tt>
+ * in the case of unattended transfer
+ * @throws OperationFailedException if something goes wrong
+ */
+ protected void transfer(String to, String sid)
+ throws OperationFailedException
+ {
+ JingleIQ transferSessionInfo = new JingleIQ();
+ ProtocolProviderServiceJabberImpl protocolProvider
+ = getProtocolProvider();
+
+ transferSessionInfo.setAction(JingleAction.SESSION_INFO);
+ transferSessionInfo.setFrom(protocolProvider.getOurJID());
+ transferSessionInfo.setSID(getSID());
+ transferSessionInfo.setTo(getAddress());
+ transferSessionInfo.setType(IQ.Type.SET);
+
+ TransferPacketExtension transfer = new TransferPacketExtension();
+
+ // Attended transfer.
+ if (sid != null)
+ {
+ /*
+ * Not really sure what the value of the "from" attribute of the
+ * "transfer" element should be but the examples in "XEP-0251:
+ * Jingle Session Transfer" has it in the case of attended transfer.
+ */
+ transfer.setFrom(protocolProvider.getOurJID());
+ transfer.setSID(sid);
+
+ // Puts on hold the 2 calls before making the attended transfer.
+ OperationSetBasicTelephonyJabberImpl basicTelephony
+ = (OperationSetBasicTelephonyJabberImpl)
+ protocolProvider.getOperationSet(
+ OperationSetBasicTelephony.class);
+ CallPeerJabberImpl callPeer = basicTelephony.getActiveCallPeer(sid);
+ if(callPeer != null)
+ {
+ if(!CallPeerState.isOnHold(callPeer.getState()))
+ {
+ callPeer.putOnHold(true);
+ }
+ }
+
+ if(!CallPeerState.isOnHold(this.getState()))
+ {
+ this.putOnHold(true);
+ }
+ }
+ transfer.setTo(to);
+
+ transferSessionInfo.addExtension(transfer);
+
+ Connection connection = protocolProvider.getConnection();
+ PacketCollector collector = connection.createPacketCollector(
+ new PacketIDFilter(transferSessionInfo.getPacketID()));
+ protocolProvider.getConnection().sendPacket(transferSessionInfo);
+
+ Packet result
+ = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ if(result == null)
+ {
+ // Log the failed transfer call and notify the user.
+ throw new OperationFailedException(
+ "No response to the \"transfer\" request.",
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+ else if (((IQ) result).getType() != IQ.Type.RESULT)
+ {
+ // Log the failed transfer call and notify the user.
+ throw new OperationFailedException(
+ "Remote peer does not manage call \"transfer\"."
+ + "Response to the \"transfer\" request is: "
+ + ((IQ) result).getType(),
+ OperationFailedException.ILLEGAL_ARGUMENT);
+ }
+ else
+ {
+ String message = ((sid == null) ? "Unattended" : "Attended")
+ + " transfer to: "
+ + to;
+ // Implements the SIP behavior: once the transfer is accepted, the
+ // current call is closed.
+ hangup(
+ false,
+ message,
+ new ReasonPacketExtension(Reason.SUCCESS,
+ message,
+ new TransferredPacketExtension()));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getEntity()
+ {
+ return getAddress();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * In Jingle there isn't an actual "direction" parameter. We use the
+ * <tt>senders</tt> field to calculate the direction.
+ */
+ @Override
+ public MediaDirection getDirection(MediaType mediaType)
+ {
+ SendersEnum senders = getSenders(mediaType);
+ if (senders == SendersEnum.none)
+ return MediaDirection.INACTIVE;
+ else if (senders == null || senders == SendersEnum.both)
+ return MediaDirection.SENDRECV;
+ else if (senders == SendersEnum.initiator)
+ return isInitiator()
+ ? MediaDirection.RECVONLY
+ : MediaDirection.SENDONLY;
+ else //senders == SendersEnum.responder
+ return isInitiator()
+ ? MediaDirection.SENDONLY
+ : MediaDirection.RECVONLY;
+ }
+
+ /**
+ * Get the current value of the <tt>senders</tt> field of the content with
+ * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
+ * @param mediaType the <tt>MediaType</tt> for which to get the current
+ * value of the <tt>senders</tt> field.
+ * @return the current value of the <tt>senders</tt> field of the content
+ * with name <tt>mediaType</tt> in the Jingle session with this
+ * <tt>CallPeer</tt>.
+ */
+ public SendersEnum getSenders(MediaType mediaType)
+ {
+ if (MediaType.AUDIO.equals(mediaType))
+ return audioSenders;
+ else if (MediaType.VIDEO.equals(mediaType))
+ return videoSenders;
+ else
+ throw new IllegalArgumentException("mediaType");
+ }
+
+ /**
+ * Set the current value of the <tt>senders</tt> field of the content with
+ * name <tt>mediaType</tt> in the Jingle session with this <tt>CallPeer</tt>
+ * @param mediaType the <tt>MediaType</tt> for which to get the current
+ * value of the <tt>senders</tt> field.
+ * @param senders the value to set
+ */
+ public void setSenders(MediaType mediaType, SendersEnum senders)
+ {
+ if (mediaType == null)
+ return;
+ else if (MediaType.AUDIO.equals(mediaType))
+ this.audioSenders = senders;
+ else if (MediaType.VIDEO.equals(mediaType))
+ this.videoSenders = senders;
+ else
+ throw new IllegalArgumentException("mediaType");
+ }
+
+ /**
+ * Gets the <tt>MediaType</tt> of <tt>content</tt>. If <tt>content</tt>
+ * does not have a <tt>description</tt> child and therefore not
+ * <tt>MediaType</tt> can be associated with it, tries to take the
+ * <tt>MediaType</tt> from the session's already established contents with
+ * the same name as <tt>content</tt>
+ * @param content the <tt>ContentPacketExtention</tt> for which to get the
+ * <tt>MediaType</tt>
+ * @return the <tt>MediaType</tt> of <tt>content</tt>.
+ */
+ public MediaType getMediaType (ContentPacketExtension content)
+ {
+ String contentName = content.getName();
+ if (contentName == null)
+ return null;
+
+ MediaType mediaType = JingleUtils.getMediaType(content);
+ if (mediaType == null)
+ {
+ CallPeerMediaHandlerJabberImpl mediaHandler = getMediaHandler();
+ for (MediaType m : MediaType.values())
+ {
+ ContentPacketExtension sessionContent
+ = mediaHandler.getRemoteContent(m.toString());
+ if (sessionContent == null)
+ sessionContent = mediaHandler.getLocalContent(m.toString());
+
+ if (sessionContent != null
+ && contentName.equals(sessionContent.getName()))
+ {
+ mediaType = m;
+ break;
+ }
+ }
+ }
+
+ return mediaType;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java
index 2961bef..9adc434 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTelephonyConferencingJabberImpl.java
@@ -1,577 +1,570 @@
-/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
- *
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
- */
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.util.xml.*;
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-import org.jivesoftware.smack.packet.IQ.Type;
-import org.jivesoftware.smack.util.*;
-import org.jivesoftware.smackx.packet.*;
-
-/**
- * Implements <tt>OperationSetTelephonyConferencing</tt> for Jabber.
- *
- * @author Lyubomir Marinov
- * @author Sebastien Vincent
- * @author Boris Grozev
- */
-public class OperationSetTelephonyConferencingJabberImpl
- extends AbstractOperationSetTelephonyConferencing<
- ProtocolProviderServiceJabberImpl,
- OperationSetBasicTelephonyJabberImpl,
- CallJabberImpl,
- CallPeerJabberImpl,
- String>
- implements RegistrationStateChangeListener,
- PacketListener,
- PacketFilter
-
-{
- /**
- * The <tt>Logger</tt> used by the
- * <tt>OperationSetTelephonyConferencingJabberImpl</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class);
-
- /**
- * The minimum interval in milliseconds between COINs sent to a single
- * <tt>CallPeer</tt>.
- */
- private static final int COIN_MIN_INTERVAL = 200;
-
- /**
- * Synchronization object.
- */
- private final Object lock = new Object();
-
- /**
- * Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt>
- * instance which is to provide telephony conferencing services for the
- * specified Jabber <tt>ProtocolProviderService</tt> implementation.
- *
- * @param parentProvider the Jabber <tt>ProtocolProviderService</tt>
- * implementation which has requested the creation of the new instance and
- * for which the new instance is to provide telephony conferencing services
- */
- public OperationSetTelephonyConferencingJabberImpl(
- ProtocolProviderServiceJabberImpl parentProvider)
- {
- super(parentProvider);
- }
-
- /**
- * Notifies all <tt>CallPeer</tt>s associated with a specific <tt>Call</tt>
- * about changes in the telephony conference-related information. In
- * contrast, {@link #notifyAll()} notifies all <tt>CallPeer</tt>s associated
- * with the telephony conference in which a specific <tt>Call</tt> is
- * participating.
- *
- * @param call the <tt>Call</tt> whose <tt>CallPeer</tt>s are to be notified
- * about changes in the telephony conference-related information
- */
- @Override
- protected void notifyCallPeers(Call call)
- {
- if (call.isConferenceFocus())
- {
- synchronized (lock)
- {
- // send conference-info to all CallPeers of the specified call.
- for (Iterator<? extends CallPeer> i = call.getCallPeers();
- i.hasNext();)
- {
- notify(i.next());
- }
- }
- }
- }
-
- /**
- * Notifies a specific <tt>CallPeer</tt> about changes in the telephony
- * conference-related information.
- *
- * @param callPeer the <tt>CallPeer</tt> to notify.
- */
- private void notify(CallPeer callPeer)
- {
- if(!(callPeer instanceof CallPeerJabberImpl))
- return;
-
- //Don't send COINs to peers with might not be ready to accept COINs yet
- CallPeerState peerState = callPeer.getState();
- if (peerState == CallPeerState.CONNECTING
- || peerState == CallPeerState.UNKNOWN
- || peerState == CallPeerState.INITIATING_CALL
- || peerState == CallPeerState.DISCONNECTED
- || peerState == CallPeerState.FAILED)
- return;
-
- final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer;
-
- final long timeSinceLastCoin = System.currentTimeMillis()
- - callPeerJabber.getLastConferenceInfoSentTimestamp();
- if (timeSinceLastCoin < COIN_MIN_INTERVAL)
- {
- if (callPeerJabber.isConfInfoScheduled())
- return;
-
- logger.info("Scheduling to send a COIN to " + callPeerJabber);
- callPeerJabber.setConfInfoScheduled(true);
- new Thread(new Runnable(){
- @Override
- public void run()
- {
- try
- {
- Thread.sleep(1 + COIN_MIN_INTERVAL - timeSinceLastCoin);
- }
- catch (InterruptedException ie) {}
-
- OperationSetTelephonyConferencingJabberImpl.this
- .notify(callPeerJabber);
- }
- }).start();
-
- return;
- }
-
- // check that callPeer supports COIN before sending him a
- // conference-info
- String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress());
-
- // XXX if this generates actual disco#info requests we might want to
- // cache it.
- try
- {
- DiscoverInfo discoverInfo
- = parentProvider.getDiscoveryManager().discoverInfo(to);
-
- if (!discoverInfo.containsFeature(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN))
- {
- logger.info(callPeer.getAddress() + " does not support COIN");
- callPeerJabber.setConfInfoScheduled(false);
- return;
- }
- }
- catch (XMPPException xmppe)
- {
- logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
- }
-
- ConferenceInfoDocument currentConfInfo
- = getCurrentConferenceInfo(callPeerJabber);
- ConferenceInfoDocument lastSentConfInfo
- = callPeerJabber.getLastConferenceInfoSent();
-
- ConferenceInfoDocument diff;
-
- if (lastSentConfInfo == null)
- diff = currentConfInfo;
- else
- diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
-
- if (diff != null)
- {
- int newVersion
- = lastSentConfInfo == null
- ? 1
- : lastSentConfInfo.getVersion() + 1;
- diff.setVersion(newVersion);
-
- IQ iq = getConferenceInfo(callPeerJabber, diff);
-
- if (iq != null)
- {
- parentProvider.getConnection().sendPacket(iq);
-
- // We save currentConfInfo, because it is of state "full", while
- // diff could be a partial
- currentConfInfo.setVersion(newVersion);
- callPeerJabber.setLastConferenceInfoSent(currentConfInfo);
- callPeerJabber.setLastConferenceInfoSentTimestamp(
- System.currentTimeMillis());
- }
- }
- callPeerJabber.setConfInfoScheduled(false);
- }
-
- /**
- * Generates the conference-info IQ to be sent to a specific
- * <tt>CallPeer</tt> in order to notify it of the current state of the
- * conference managed by the local peer.
- *
- * @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
- * @param confInfo the <tt>ConferenceInformationDocument</tt> which is to be
- * included in the IQ
- * @return the conference-info IQ to be sent to the specified
- * <tt>callPeer</tt> in order to notify it of the current state of the
- * conference managed by the local peer
- */
- private IQ getConferenceInfo(CallPeerJabberImpl callPeer,
- final ConferenceInfoDocument confInfo)
- {
- String callPeerSID = callPeer.getSID();
-
- if (callPeerSID == null)
- return null;
-
- IQ iq = new IQ(){
- @Override
- public String getChildElementXML()
- {
- return confInfo.toXml();
- }
- };
-
- CallJabberImpl call = callPeer.getCall();
-
- iq.setFrom(call.getProtocolProvider().getOurJID());
- iq.setTo(callPeer.getAddress());
- iq.setType(Type.SET);
-
- return iq;
- }
-
- /**
- * Implementation of method <tt>registrationStateChange</tt> from
- * interface RegistrationStateChangeListener for setting up (or down)
- * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available
- *
- * @param evt the event received
- */
- @Override
- public void registrationStateChanged(RegistrationStateChangeEvent evt)
- {
- super.registrationStateChanged(evt);
-
- RegistrationState registrationState = evt.getNewState();
-
- if (RegistrationState.REGISTERED.equals(registrationState))
- {
- if(logger.isDebugEnabled())
- logger.debug("Subscribes to Coin packets");
- subscribeForCoinPackets();
- }
- else if (RegistrationState.UNREGISTERED.equals(registrationState))
- {
- if(logger.isDebugEnabled())
- logger.debug("Unsubscribes to Coin packets");
- unsubscribeForCoinPackets();
- }
- }
-
- /**
- * Creates a new outgoing <tt>Call</tt> into which conference callees are to
- * be invited by this <tt>OperationSetTelephonyConferencing</tt>.
- *
- * @return a new outgoing <tt>Call</tt> into which conference callees are to
- * be invited by this <tt>OperationSetTelephonyConferencing</tt>
- * @throws OperationFailedException if anything goes wrong
- */
- @Override
- protected CallJabberImpl createOutgoingCall()
- throws OperationFailedException
- {
- return new CallJabberImpl(getBasicTelephony());
- }
-
- /**
- * {@inheritDoc}
- *
- * Implements the protocol-dependent part of the logic of inviting a callee
- * to a <tt>Call</tt>. The protocol-independent part of that logic is
- * implemented by
- * {@link AbstractOperationSetTelephonyConferencing#inviteCalleeToCall(String,Call)}.
- */
- @Override
- protected CallPeer doInviteCalleeToCall(
- String calleeAddress,
- CallJabberImpl call)
- throws OperationFailedException
- {
- return
- getBasicTelephony().createOutgoingCall(
- call,
- calleeAddress,
- Arrays.asList(
- new PacketExtension[]
- {
- new CoinPacketExtension(true)
- }));
- }
-
- /**
- * Parses a <tt>String</tt> value which represents a callee address
- * specified by the user into an object which is to actually represent the
- * callee during the invitation to a conference <tt>Call</tt>.
- *
- * @param calleeAddressString a <tt>String</tt> value which represents a
- * callee address to be parsed into an object which is to actually represent
- * the callee during the invitation to a conference <tt>Call</tt>
- * @return an object which is to actually represent the specified
- * <tt>calleeAddressString</tt> during the invitation to a conference
- * <tt>Call</tt>
- * @throws OperationFailedException if parsing the specified
- * <tt>calleeAddressString</tt> fails
- */
- @Override
- protected String parseAddressString(String calleeAddressString)
- throws OperationFailedException
- {
- return getBasicTelephony().getFullCalleeURI(calleeAddressString);
- }
-
- /**
- * Subscribes us to notifications about incoming Coin packets.
- */
- private void subscribeForCoinPackets()
- {
- parentProvider.getConnection().addPacketListener(this, this);
- }
-
- /**
- * Unsubscribes us from notifications about incoming Coin packets.
- */
- private void unsubscribeForCoinPackets()
- {
- XMPPConnection connection = parentProvider.getConnection();
-
- if (connection != null)
- connection.removePacketListener(this);
- }
-
- /**
- * Tests whether or not the specified packet should be handled by this
- * operation set. This method is called by smack prior to packet delivery
- * and it would only accept <tt>CoinIQ</tt>s.
- *
- * @param packet the packet to test.
- * @return true if and only if <tt>packet</tt> passes the filter.
- */
- public boolean accept(Packet packet)
- {
- return (packet instanceof CoinIQ);
- }
-
- /**
- * Handles incoming jingle packets and passes them to the corresponding
- * method based on their action.
- *
- * @param packet the packet to process.
- */
- public void processPacket(Packet packet)
- {
- CoinIQ coinIQ = (CoinIQ) packet;
- String errorMessage = null;
-
- //first ack all "set" requests.
- IQ.Type type = coinIQ.getType();
- if (type == IQ.Type.SET)
- {
- IQ ack = IQ.createResultIQ(coinIQ);
-
- parentProvider.getConnection().sendPacket(ack);
- }
- else if(type == IQ.Type.ERROR)
- {
- XMPPError error = coinIQ.getError();
- if(error != null)
- {
- String msg = error.getMessage();
- errorMessage = ((msg != null)? (msg + " ") : "")
- + "Error code: " + error.getCode();
- }
-
- logger.error("Received error in COIN packet. "+errorMessage);
- }
-
- String sid = coinIQ.getSID();
-
- if (sid != null)
- {
- CallPeerJabberImpl callPeer
- = getBasicTelephony().getActiveCallsRepository().findCallPeer(
- sid);
-
-
- if (callPeer != null)
- {
- if(type == IQ.Type.ERROR)
- {
- callPeer.fireConferenceMemberErrorEvent(errorMessage);
- return;
- }
-
- if (logger.isDebugEnabled())
- logger.debug("Processing COIN from " + coinIQ.getFrom()
- + " (version=" + coinIQ.getVersion() + ")");
-
- handleCoin(callPeer, coinIQ);
- }
- }
- }
-
- /**
- * Handles a specific <tt>CoinIQ</tt> sent from a specific
- * <tt>CallPeer</tt>.
- *
- * @param callPeer the <tt>CallPeer</tt> from which the specified
- * <tt>CoinIQ</tt> was sent
- * @param coinIQ the <tt>CoinIQ</tt> which was sent from the specified
- * <tt>callPeer</tt>
- */
- private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ)
- {
- try
- {
- setConferenceInfoXML(callPeer, coinIQ.getChildElementXML());
- }
- catch (XMLException e)
- {
- logger.error("Could not handle received COIN from " + callPeer
- + ": " + coinIQ);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * For COINs (XEP-0298), we use the attributes of the
- * <tt>conference-info</tt> element to piggyback a Jingle SID. This is
- * temporary and should be removed once we choose a better way to pass the
- * SID.
- */
- protected ConferenceInfoDocument getCurrentConferenceInfo(
- MediaAwareCallPeer callPeer)
- {
- ConferenceInfoDocument confInfo
- = super.getCurrentConferenceInfo(callPeer);
-
- if (callPeer instanceof CallPeerJabberImpl
- && confInfo != null)
- {
- confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID());
- }
- return confInfo;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getLocalEntity(CallPeer callPeer)
- {
- JingleIQ sessionIQ = ((CallPeerJabberImpl)callPeer).getSessionIQ();
- String from = sessionIQ.getFrom();
- String chatRoomName = StringUtils.parseBareAddress(from);
- OperationSetMultiUserChatJabberImpl opSetMUC
- = (OperationSetMultiUserChatJabberImpl)parentProvider.getOperationSet(OperationSetMultiUserChat.class);
- ChatRoom room = null;
- room = opSetMUC.getChatRoom(chatRoomName);
-
- if(room != null)
- return "xmpp:" + chatRoomName + "/" + room.getUserNickname();
-
- return "xmpp:" + parentProvider.getOurJID();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getLocalDisplayName()
- {
- return null;
- }
-
- /**
- * {@inheritDoc}
- *
- * The URI of the returned <tt>ConferenceDescription</tt> is the occupant
- * JID with which we have joined the room.
- *
- * If a videobridge is available for our <tt>ProtocolProviderService</tt>
- * we use it. TODO: this should be relaxed when we refactor the videobrdige
- * implementation, so that any videobrdige (on any protocol provider) can
- * be used.
- *
- */
- @Override
- public ConferenceDescription setupConference(final ChatRoom chatRoom)
- {
- OperationSetVideoBridge videoBridge
- = parentProvider.getOperationSet(OperationSetVideoBridge.class);
- boolean isVideoBridge = (videoBridge != null) && videoBridge.isActive();
-
- CallJabberImpl call = new CallJabberImpl(getBasicTelephony());
- call.setAutoAnswer(true);
-
- String uri = "xmpp:" + chatRoom.getIdentifier() +
- "/" + chatRoom.getUserNickname();
-
- ConferenceDescription cd
- = new ConferenceDescription(uri, call.getCallID());
-
- call.addCallChangeListener(new CallChangeListener()
- {
-
- @Override
- public void callStateChanged(CallChangeEvent evt)
- {
- if(CallState.CALL_ENDED.equals(evt.getNewValue()))
- {
- chatRoom.publishConference(null, null);
- }
-
- }
-
- @Override
- public void callPeerRemoved(CallPeerEvent evt)
- {
-
- }
-
- @Override
- public void callPeerAdded(CallPeerEvent evt)
- {
-
- }
- });
- if (isVideoBridge)
- {
- call.setConference(new MediaAwareCallConference(true));
-
-
- //For videobridge we set the transports to RAW-UDP, otherwise
- //we leave them empty (meaning both RAW-UDP and ICE could be used)
- cd.addTransport(
- ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0);
- }
-
- if (logger.isInfoEnabled())
- {
- logger.info("Setup a conference with uri=" + uri + " and callid=" +
- call.getCallID() + ". Videobridge in use: " + isVideoBridge);
- }
-
- return cd;
- }
-}
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.util.xml.*;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Implements <tt>OperationSetTelephonyConferencing</tt> for Jabber.
+ *
+ * @author Lyubomir Marinov
+ * @author Sebastien Vincent
+ * @author Boris Grozev
+ */
+public class OperationSetTelephonyConferencingJabberImpl
+ extends AbstractOperationSetTelephonyConferencing<
+ ProtocolProviderServiceJabberImpl,
+ OperationSetBasicTelephonyJabberImpl,
+ CallJabberImpl,
+ CallPeerJabberImpl,
+ String>
+ implements RegistrationStateChangeListener,
+ PacketListener,
+ PacketFilter
+
+{
+ /**
+ * The <tt>Logger</tt> used by the
+ * <tt>OperationSetTelephonyConferencingJabberImpl</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(OperationSetTelephonyConferencingJabberImpl.class);
+
+ /**
+ * The minimum interval in milliseconds between COINs sent to a single
+ * <tt>CallPeer</tt>.
+ */
+ private static final int COIN_MIN_INTERVAL = 200;
+
+ /**
+ * Synchronization object.
+ */
+ private final Object lock = new Object();
+
+ /**
+ * Initializes a new <tt>OperationSetTelephonyConferencingJabberImpl</tt>
+ * instance which is to provide telephony conferencing services for the
+ * specified Jabber <tt>ProtocolProviderService</tt> implementation.
+ *
+ * @param parentProvider the Jabber <tt>ProtocolProviderService</tt>
+ * implementation which has requested the creation of the new instance and
+ * for which the new instance is to provide telephony conferencing services
+ */
+ public OperationSetTelephonyConferencingJabberImpl(
+ ProtocolProviderServiceJabberImpl parentProvider)
+ {
+ super(parentProvider);
+ }
+
+ /**
+ * Notifies all <tt>CallPeer</tt>s associated with a specific <tt>Call</tt>
+ * about changes in the telephony conference-related information. In
+ * contrast, {@link #notifyAll()} notifies all <tt>CallPeer</tt>s associated
+ * with the telephony conference in which a specific <tt>Call</tt> is
+ * participating.
+ *
+ * @param call the <tt>Call</tt> whose <tt>CallPeer</tt>s are to be notified
+ * about changes in the telephony conference-related information
+ */
+ @Override
+ protected void notifyCallPeers(Call call)
+ {
+ if (call.isConferenceFocus())
+ {
+ synchronized (lock)
+ {
+ // send conference-info to all CallPeers of the specified call.
+ for (Iterator<? extends CallPeer> i = call.getCallPeers();
+ i.hasNext();)
+ {
+ notify(i.next());
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies a specific <tt>CallPeer</tt> about changes in the telephony
+ * conference-related information.
+ *
+ * @param callPeer the <tt>CallPeer</tt> to notify.
+ */
+ private void notify(CallPeer callPeer)
+ {
+ if(!(callPeer instanceof CallPeerJabberImpl))
+ return;
+
+ //Don't send COINs to peers with might not be ready to accept COINs yet
+ CallPeerState peerState = callPeer.getState();
+ if (peerState == CallPeerState.CONNECTING
+ || peerState == CallPeerState.UNKNOWN
+ || peerState == CallPeerState.INITIATING_CALL
+ || peerState == CallPeerState.DISCONNECTED
+ || peerState == CallPeerState.FAILED)
+ return;
+
+ final CallPeerJabberImpl callPeerJabber = (CallPeerJabberImpl)callPeer;
+
+ final long timeSinceLastCoin = System.currentTimeMillis()
+ - callPeerJabber.getLastConferenceInfoSentTimestamp();
+ if (timeSinceLastCoin < COIN_MIN_INTERVAL)
+ {
+ if (callPeerJabber.isConfInfoScheduled())
+ return;
+
+ logger.info("Scheduling to send a COIN to " + callPeerJabber);
+ callPeerJabber.setConfInfoScheduled(true);
+ new Thread(new Runnable(){
+ @Override
+ public void run()
+ {
+ try
+ {
+ Thread.sleep(1 + COIN_MIN_INTERVAL - timeSinceLastCoin);
+ }
+ catch (InterruptedException ie) {}
+
+ OperationSetTelephonyConferencingJabberImpl.this
+ .notify(callPeerJabber);
+ }
+ }).start();
+
+ return;
+ }
+
+ // check that callPeer supports COIN before sending him a
+ // conference-info
+ String to = getBasicTelephony().getFullCalleeURI(callPeer.getAddress());
+
+ // XXX if this generates actual disco#info requests we might want to
+ // cache it.
+ try
+ {
+ DiscoverInfo discoverInfo
+ = parentProvider.getDiscoveryManager().discoverInfo(to);
+
+ if (!discoverInfo.containsFeature(
+ ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_COIN))
+ {
+ logger.info(callPeer.getAddress() + " does not support COIN");
+ callPeerJabber.setConfInfoScheduled(false);
+ return;
+ }
+ }
+ catch (XMPPException xmppe)
+ {
+ logger.warn("Failed to retrieve DiscoverInfo for " + to, xmppe);
+ }
+
+ ConferenceInfoDocument currentConfInfo
+ = getCurrentConferenceInfo(callPeerJabber);
+ ConferenceInfoDocument lastSentConfInfo
+ = callPeerJabber.getLastConferenceInfoSent();
+
+ ConferenceInfoDocument diff;
+
+ if (lastSentConfInfo == null)
+ diff = currentConfInfo;
+ else
+ diff = getConferenceInfoDiff(lastSentConfInfo, currentConfInfo);
+
+ if (diff != null)
+ {
+ int newVersion
+ = lastSentConfInfo == null
+ ? 1
+ : lastSentConfInfo.getVersion() + 1;
+ diff.setVersion(newVersion);
+
+ IQ iq = getConferenceInfo(callPeerJabber, diff);
+
+ if (iq != null)
+ {
+ parentProvider.getConnection().sendPacket(iq);
+
+ // We save currentConfInfo, because it is of state "full", while
+ // diff could be a partial
+ currentConfInfo.setVersion(newVersion);
+ callPeerJabber.setLastConferenceInfoSent(currentConfInfo);
+ callPeerJabber.setLastConferenceInfoSentTimestamp(
+ System.currentTimeMillis());
+ }
+ }
+ callPeerJabber.setConfInfoScheduled(false);
+ }
+
+ /**
+ * Generates the conference-info IQ to be sent to a specific
+ * <tt>CallPeer</tt> in order to notify it of the current state of the
+ * conference managed by the local peer.
+ *
+ * @param callPeer the <tt>CallPeer</tt> to generate conference-info XML for
+ * @param confInfo the <tt>ConferenceInformationDocument</tt> which is to be
+ * included in the IQ
+ * @return the conference-info IQ to be sent to the specified
+ * <tt>callPeer</tt> in order to notify it of the current state of the
+ * conference managed by the local peer
+ */
+ private IQ getConferenceInfo(CallPeerJabberImpl callPeer,
+ final ConferenceInfoDocument confInfo)
+ {
+ String callPeerSID = callPeer.getSID();
+
+ if (callPeerSID == null)
+ return null;
+
+ IQ iq = new IQ(){
+ @Override
+ public String getChildElementXML()
+ {
+ return confInfo.toXml();
+ }
+ };
+
+ CallJabberImpl call = callPeer.getCall();
+
+ iq.setFrom(call.getProtocolProvider().getOurJID());
+ iq.setTo(callPeer.getAddress());
+ iq.setType(Type.SET);
+
+ return iq;
+ }
+
+ /**
+ * Implementation of method <tt>registrationStateChange</tt> from
+ * interface RegistrationStateChangeListener for setting up (or down)
+ * our <tt>JingleManager</tt> when an <tt>XMPPConnection</tt> is available
+ *
+ * @param evt the event received
+ */
+ @Override
+ public void registrationStateChanged(RegistrationStateChangeEvent evt)
+ {
+ super.registrationStateChanged(evt);
+
+ RegistrationState registrationState = evt.getNewState();
+
+ if (RegistrationState.REGISTERED.equals(registrationState))
+ {
+ if(logger.isDebugEnabled())
+ logger.debug("Subscribes to Coin packets");
+ subscribeForCoinPackets();
+ }
+ else if (RegistrationState.UNREGISTERED.equals(registrationState))
+ {
+ if(logger.isDebugEnabled())
+ logger.debug("Unsubscribes to Coin packets");
+ unsubscribeForCoinPackets();
+ }
+ }
+
+ /**
+ * Creates a new outgoing <tt>Call</tt> into which conference callees are to
+ * be invited by this <tt>OperationSetTelephonyConferencing</tt>.
+ *
+ * @return a new outgoing <tt>Call</tt> into which conference callees are to
+ * be invited by this <tt>OperationSetTelephonyConferencing</tt>
+ * @throws OperationFailedException if anything goes wrong
+ */
+ @Override
+ protected CallJabberImpl createOutgoingCall()
+ throws OperationFailedException
+ {
+ return new CallJabberImpl(getBasicTelephony());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Implements the protocol-dependent part of the logic of inviting a callee
+ * to a <tt>Call</tt>. The protocol-independent part of that logic is
+ * implemented by
+ * {@link AbstractOperationSetTelephonyConferencing#inviteCalleeToCall(String,Call)}.
+ */
+ @Override
+ protected CallPeer doInviteCalleeToCall(
+ String calleeAddress,
+ CallJabberImpl call)
+ throws OperationFailedException
+ {
+ return
+ getBasicTelephony().createOutgoingCall(
+ call,
+ calleeAddress,
+ Arrays.asList(
+ new PacketExtension[]
+ {
+ new CoinPacketExtension(true)
+ }));
+ }
+
+ /**
+ * Parses a <tt>String</tt> value which represents a callee address
+ * specified by the user into an object which is to actually represent the
+ * callee during the invitation to a conference <tt>Call</tt>.
+ *
+ * @param calleeAddressString a <tt>String</tt> value which represents a
+ * callee address to be parsed into an object which is to actually represent
+ * the callee during the invitation to a conference <tt>Call</tt>
+ * @return an object which is to actually represent the specified
+ * <tt>calleeAddressString</tt> during the invitation to a conference
+ * <tt>Call</tt>
+ * @throws OperationFailedException if parsing the specified
+ * <tt>calleeAddressString</tt> fails
+ */
+ @Override
+ protected String parseAddressString(String calleeAddressString)
+ throws OperationFailedException
+ {
+ return getBasicTelephony().getFullCalleeURI(calleeAddressString);
+ }
+
+ /**
+ * Subscribes us to notifications about incoming Coin packets.
+ */
+ private void subscribeForCoinPackets()
+ {
+ parentProvider.getConnection().addPacketListener(this, this);
+ }
+
+ /**
+ * Unsubscribes us from notifications about incoming Coin packets.
+ */
+ private void unsubscribeForCoinPackets()
+ {
+ XMPPConnection connection = parentProvider.getConnection();
+
+ if (connection != null)
+ connection.removePacketListener(this);
+ }
+
+ /**
+ * Tests whether or not the specified packet should be handled by this
+ * operation set. This method is called by smack prior to packet delivery
+ * and it would only accept <tt>CoinIQ</tt>s.
+ *
+ * @param packet the packet to test.
+ * @return true if and only if <tt>packet</tt> passes the filter.
+ */
+ public boolean accept(Packet packet)
+ {
+ return (packet instanceof CoinIQ);
+ }
+
+ /**
+ * Handles incoming jingle packets and passes them to the corresponding
+ * method based on their action.
+ *
+ * @param packet the packet to process.
+ */
+ public void processPacket(Packet packet)
+ {
+ CoinIQ coinIQ = (CoinIQ) packet;
+ String errorMessage = null;
+
+ //first ack all "set" requests.
+ IQ.Type type = coinIQ.getType();
+ if (type == IQ.Type.SET)
+ {
+ IQ ack = IQ.createResultIQ(coinIQ);
+
+ parentProvider.getConnection().sendPacket(ack);
+ }
+ else if(type == IQ.Type.ERROR)
+ {
+ XMPPError error = coinIQ.getError();
+ if(error != null)
+ {
+ String msg = error.getMessage();
+ errorMessage = ((msg != null)? (msg + " ") : "")
+ + "Error code: " + error.getCode();
+ }
+
+ logger.error("Received error in COIN packet. "+errorMessage);
+ }
+
+ String sid = coinIQ.getSID();
+
+ if (sid != null)
+ {
+ CallPeerJabberImpl callPeer
+ = getBasicTelephony().getActiveCallsRepository().findCallPeer(
+ sid);
+
+
+ if (callPeer != null)
+ {
+ if(type == IQ.Type.ERROR)
+ {
+ callPeer.fireConferenceMemberErrorEvent(errorMessage);
+ return;
+ }
+
+ if (logger.isDebugEnabled())
+ logger.debug("Processing COIN from " + coinIQ.getFrom()
+ + " (version=" + coinIQ.getVersion() + ")");
+
+ handleCoin(callPeer, coinIQ);
+ }
+ }
+ }
+
+ /**
+ * Handles a specific <tt>CoinIQ</tt> sent from a specific
+ * <tt>CallPeer</tt>.
+ *
+ * @param callPeer the <tt>CallPeer</tt> from which the specified
+ * <tt>CoinIQ</tt> was sent
+ * @param coinIQ the <tt>CoinIQ</tt> which was sent from the specified
+ * <tt>callPeer</tt>
+ */
+ private void handleCoin(CallPeerJabberImpl callPeer, CoinIQ coinIQ)
+ {
+ try
+ {
+ setConferenceInfoXML(callPeer, coinIQ.getChildElementXML());
+ }
+ catch (XMLException e)
+ {
+ logger.error("Could not handle received COIN from " + callPeer
+ + ": " + coinIQ);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For COINs (XEP-0298), we use the attributes of the
+ * <tt>conference-info</tt> element to piggyback a Jingle SID. This is
+ * temporary and should be removed once we choose a better way to pass the
+ * SID.
+ */
+ protected ConferenceInfoDocument getCurrentConferenceInfo(
+ MediaAwareCallPeer callPeer)
+ {
+ ConferenceInfoDocument confInfo
+ = super.getCurrentConferenceInfo(callPeer);
+
+ if (callPeer instanceof CallPeerJabberImpl
+ && confInfo != null)
+ {
+ confInfo.setSid(((CallPeerJabberImpl)callPeer).getSID());
+ }
+ return confInfo;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getLocalEntity(CallPeer callPeer)
+ {
+ JingleIQ sessionIQ = ((CallPeerJabberImpl)callPeer).getSessionIQ();
+ String from = sessionIQ.getFrom();
+ String chatRoomName = StringUtils.parseBareAddress(from);
+ OperationSetMultiUserChatJabberImpl opSetMUC
+ = (OperationSetMultiUserChatJabberImpl)
+ parentProvider.getOperationSet(OperationSetMultiUserChat.class);
+ ChatRoom room = null;
+ room = opSetMUC.getChatRoom(chatRoomName);
+
+ if(room != null)
+ return "xmpp:" + chatRoomName + "/" + room.getUserNickname();
+
+ return "xmpp:" + parentProvider.getOurJID();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getLocalDisplayName()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * The URI of the returned <tt>ConferenceDescription</tt> is the occupant
+ * JID with which we have joined the room.
+ *
+ * If a Videobridge is available for our <tt>ProtocolProviderService</tt>
+ * we use it. TODO: this should be relaxed when we refactor the Videobridge
+ * implementation, so that any Videobridge (on any protocol provider) can
+ * be used.
+ */
+ @Override
+ public ConferenceDescription setupConference(final ChatRoom chatRoom)
+ {
+ OperationSetVideoBridge videoBridge
+ = parentProvider.getOperationSet(OperationSetVideoBridge.class);
+ boolean isVideobridge = (videoBridge != null) && videoBridge.isActive();
+
+ CallJabberImpl call = new CallJabberImpl(getBasicTelephony());
+ call.setAutoAnswer(true);
+
+ String uri = "xmpp:" + chatRoom.getIdentifier() +
+ "/" + chatRoom.getUserNickname();
+
+ ConferenceDescription cd
+ = new ConferenceDescription(uri, call.getCallID());
+
+ call.addCallChangeListener(new CallChangeListener()
+ {
+ @Override
+ public void callStateChanged(CallChangeEvent ev)
+ {
+ if(CallState.CALL_ENDED.equals(ev.getNewValue()))
+ chatRoom.publishConference(null, null);
+ }
+
+ @Override
+ public void callPeerRemoved(CallPeerEvent ev)
+ {
+ }
+
+ @Override
+ public void callPeerAdded(CallPeerEvent ev)
+ {
+ }
+ });
+ if (isVideobridge)
+ {
+ call.setConference(new MediaAwareCallConference(true));
+
+ //For Jitsi Videobridge we set the transports to RAW-UDP, otherwise
+ //we leave them empty (meaning both RAW-UDP and ICE could be used)
+ cd.addTransport(
+ ProtocolProviderServiceJabberImpl.URN_XMPP_JINGLE_RAW_UDP_0);
+ }
+
+ if (logger.isInfoEnabled())
+ {
+ logger.info("Setup a conference with uri=" + uri + " and callid=" +
+ call.getCallID() + ". Videobridge in use: " + isVideobridge);
+ }
+
+ return cd;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java
index bb7084c..eaf69d4 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoBridgeImpl.java
@@ -1,285 +1,285 @@
-/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
- *
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
- */
-package net.java.sip.communicator.impl.protocol.jabber;
-
-import java.util.*;
-
-import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.service.protocol.media.*;
-import net.java.sip.communicator.util.*;
-
-import org.jivesoftware.smack.*;
-import org.jivesoftware.smack.filter.*;
-import org.jivesoftware.smack.packet.*;
-
-/**
- * Implements <tt>OperationSetVideoBridge</tt> for Jabber.
- *
- * @author Yana Stamcheva
- * @author Lyubomir Marinov
- */
-public class OperationSetVideoBridgeImpl
- implements OperationSetVideoBridge,
- PacketFilter,
- PacketListener,
- RegistrationStateChangeListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>OperationSetVideoBridgeImpl</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(OperationSetVideoBridgeImpl.class);
-
- /**
- * The <tt>ProtocolProviderService</tt> implementation which initialized
- * this instance, owns it and is often referred to as its parent.
- */
- private final ProtocolProviderServiceJabberImpl protocolProvider;
-
- /**
- * Creates an instance of <tt>OperationSetVideoBridgeImpl</tt> by
- * specifying the parent <tt>ProtocolProviderService</tt> announcing this
- * operation set.
- *
- * @param protocolProvider the parent Jabber protocol provider
- */
- public OperationSetVideoBridgeImpl(
- ProtocolProviderServiceJabberImpl protocolProvider)
- {
- this.protocolProvider = protocolProvider;
- this.protocolProvider.addRegistrationStateChangeListener(this);
- }
-
- /**
- * Implements {@link PacketFilter}. Determines whether this instance is
- * interested in a specific {@link Packet}.
- * <tt>OperationSetVideoBridgeImpl</tt> returns <tt>true</tt> if the
- * specified <tt>packet</tt> is a {@link ColibriConferenceIQ}; otherwise,
- * <tt>false</tt>.
- *
- * @param packet the <tt>Packet</tt> to be determined whether this instance
- * is interested in it
- * @return <tt>true</tt> if the specified <tt>packet</tt> is a
- * <tt>ColibriConferenceIQ</tt>; otherwise, <tt>false</tt>
- */
- public boolean accept(Packet packet)
- {
- return (packet instanceof ColibriConferenceIQ);
- }
-
- /**
- * Creates a conference call with the specified callees as call peers via a
- * video bridge provided by the parent Jabber provider.
- *
- * @param callees the list of addresses that we should call
- * @return the newly created conference call containing all CallPeers
- * @throws OperationFailedException if establishing the conference call
- * fails
- * @throws OperationNotSupportedException if the provider does not have any
- * conferencing features.
- */
- public Call createConfCall(String[] callees)
- throws OperationFailedException,
- OperationNotSupportedException
- {
- return
- protocolProvider
- .getOperationSet(OperationSetTelephonyConferencing.class)
- .createConfCall(
- callees,
- new MediaAwareCallConference(true));
- }
-
- /**
- * Invites the callee represented by the specified uri to an already
- * existing call using a video bridge provided by the parent Jabber provider.
- * The difference between this method and createConfCall is that
- * inviteCalleeToCall allows a user to add new peers to an already
- * established conference.
- *
- * @param uri the callee to invite to an existing conf call.
- * @param call the call that we should invite the callee to.
- * @return the CallPeer object corresponding to the callee represented by
- * the specified uri.
- * @throws OperationFailedException if inviting the specified callee to the
- * specified call fails
- * @throws OperationNotSupportedException if allowing additional callees to
- * a pre-established call is not supported.
- */
- public CallPeer inviteCalleeToCall(String uri, Call call)
- throws OperationFailedException,
- OperationNotSupportedException
- {
- return
- protocolProvider
- .getOperationSet(OperationSetTelephonyConferencing.class)
- .inviteCalleeToCall(uri, call);
- }
-
- /**
- * Indicates if there's an active video bridge available at this moment. The
- * Jabber provider may announce support for video bridge, but it should not
- * be used for calling until it becomes actually active.
- *
- * @return <tt>true</tt> to indicate that there's currently an active
- * available video bridge, <tt>false</tt> - otherwise
- */
- public boolean isActive()
- {
- String jitsiVideoBridge = protocolProvider.getJitsiVideoBridge();
-
- return ((jitsiVideoBridge != null) && (jitsiVideoBridge.length() > 0));
- }
-
- /**
- * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
- * been received.
- *
- * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
- * received
- */
- private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
- {
- /*
- * The application is not a Jitsi VideoBridge server, it is a client.
- * Consequently, the specified ColibriConferenceIQ is sent to it in
- * relation to the part of the application's functionality which makes
- * requests to a Jitsi VideoBridge server i.e. CallJabberImpl.
- *
- * Additionally, the method processColibriConferenceIQ is presently tasked
- * with processing ColibriConferenceIQ requests only. They are SET IQs
- * sent by the Jitsi VideoBridge server to notify the application about
- * updates in the states of (colibri) conferences organized by the
- * application.
- */
- if (IQ.Type.SET.equals(conferenceIQ.getType())
- && conferenceIQ.getID() != null)
- {
- OperationSetBasicTelephony<?> basicTelephony
- = protocolProvider.getOperationSet(
- OperationSetBasicTelephony.class);
-
- if (basicTelephony != null)
- {
- Iterator<? extends Call> i = basicTelephony.getActiveCalls();
-
- while (i.hasNext())
- {
- Call call = i.next();
-
- if (call instanceof CallJabberImpl)
- {
- CallJabberImpl callJabberImpl = (CallJabberImpl) call;
- MediaAwareCallConference conference
- = callJabberImpl.getConference();
-
- if ((conference != null)
- && conference.isJitsiVideoBridge())
- {
- /*
- * TODO We may want to disallow rogue CallJabberImpl
- * instances which may throw an exception to prevent
- * the conferenceIQ from reaching the CallJabberImpl
- * instance which it was meant for.
- */
- if (callJabberImpl.processColibriConferenceIQ(
- conferenceIQ))
- break;
- }
- }
- }
- }
- }
- }
-
- /**
- * Implements {@link PacketListener}. Notifies this instance that a specific
- * {@link Packet} (which this instance has already expressed interest into
- * by returning <tt>true</tt> from {@link #accept(Packet)}) has been
- * received.
- *
- * @param packet the <tt>Packet</tt> which has been received and which this
- * instance is given a chance to process
- */
- public void processPacket(Packet packet)
- {
- /*
- * As we do elsewhere, acknowledge the receipt of the Packet first and
- * then go about our business with it.
- */
- IQ iq = (IQ) packet;
-
- if (iq.getType() == IQ.Type.SET)
- protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq));
-
- /*
- * Now that the acknowledging is out of the way, do go about our
- * business with the Packet.
- */
- ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq;
- boolean interrupted = false;
-
- try
- {
- processColibriConferenceIQ(conferenceIQ);
- }
- catch (Throwable t)
- {
- logger.error(
- "An error occurred during the processing of a "
- + packet.getClass().getName() + " packet",
- t);
-
- if (t instanceof InterruptedException)
- {
- /*
- * We cleared the interrupted state of the current Thread by
- * catching the InterruptedException. However, we do not really
- * care whether the current Thread has been interrupted - we
- * caught the InterruptedException because we want to swallow
- * any Throwable. Consequently, we should better restore the
- * interrupted state.
- */
- interrupted = true;
- }
- else if (t instanceof ThreadDeath)
- throw (ThreadDeath) t;
- }
- if (interrupted)
- Thread.currentThread().interrupt();
- }
-
- /**
- * {@inheritDoc}
- *
- * Implements {@link RegistrationStateChangeListener}. Notifies this
- * instance that there has been a change in the <tt>RegistrationState</tt>
- * of {@link #protocolProvider}. Subscribes this instance to
- * {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is
- * registered and unsubscribes it as soon as <tt>protocolProvider</tt> is
- * unregistered.
- */
- public void registrationStateChanged(RegistrationStateChangeEvent ev)
- {
- RegistrationState registrationState = ev.getNewState();
-
- if (RegistrationState.REGISTERED.equals(registrationState))
- {
- protocolProvider.getConnection().addPacketListener(this, this);
- }
- else if (RegistrationState.UNREGISTERED.equals(registrationState))
- {
- XMPPConnection connection = protocolProvider.getConnection();
-
- if (connection != null)
- connection.removePacketListener(this);
- }
- }
-}
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.jabber;
+
+import java.util.*;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.media.*;
+import net.java.sip.communicator.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * Implements <tt>OperationSetVideoBridge</tt> for Jabber.
+ *
+ * @author Yana Stamcheva
+ * @author Lyubomir Marinov
+ */
+public class OperationSetVideoBridgeImpl
+ implements OperationSetVideoBridge,
+ PacketFilter,
+ PacketListener,
+ RegistrationStateChangeListener
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>OperationSetVideoBridgeImpl</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(OperationSetVideoBridgeImpl.class);
+
+ /**
+ * The <tt>ProtocolProviderService</tt> implementation which initialized
+ * this instance, owns it and is often referred to as its parent.
+ */
+ private final ProtocolProviderServiceJabberImpl protocolProvider;
+
+ /**
+ * Creates an instance of <tt>OperationSetVideoBridgeImpl</tt> by
+ * specifying the parent <tt>ProtocolProviderService</tt> announcing this
+ * operation set.
+ *
+ * @param protocolProvider the parent Jabber protocol provider
+ */
+ public OperationSetVideoBridgeImpl(
+ ProtocolProviderServiceJabberImpl protocolProvider)
+ {
+ this.protocolProvider = protocolProvider;
+ this.protocolProvider.addRegistrationStateChangeListener(this);
+ }
+
+ /**
+ * Implements {@link PacketFilter}. Determines whether this instance is
+ * interested in a specific {@link Packet}.
+ * <tt>OperationSetVideoBridgeImpl</tt> returns <tt>true</tt> if the
+ * specified <tt>packet</tt> is a {@link ColibriConferenceIQ}; otherwise,
+ * <tt>false</tt>.
+ *
+ * @param packet the <tt>Packet</tt> to be determined whether this instance
+ * is interested in it
+ * @return <tt>true</tt> if the specified <tt>packet</tt> is a
+ * <tt>ColibriConferenceIQ</tt>; otherwise, <tt>false</tt>
+ */
+ public boolean accept(Packet packet)
+ {
+ return (packet instanceof ColibriConferenceIQ);
+ }
+
+ /**
+ * Creates a conference call with the specified callees as call peers via a
+ * video bridge provided by the parent Jabber provider.
+ *
+ * @param callees the list of addresses that we should call
+ * @return the newly created conference call containing all CallPeers
+ * @throws OperationFailedException if establishing the conference call
+ * fails
+ * @throws OperationNotSupportedException if the provider does not have any
+ * conferencing features.
+ */
+ public Call createConfCall(String[] callees)
+ throws OperationFailedException,
+ OperationNotSupportedException
+ {
+ return
+ protocolProvider
+ .getOperationSet(OperationSetTelephonyConferencing.class)
+ .createConfCall(
+ callees,
+ new MediaAwareCallConference(true));
+ }
+
+ /**
+ * Invites the callee represented by the specified uri to an already
+ * existing call using a video bridge provided by the parent Jabber provider.
+ * The difference between this method and createConfCall is that
+ * inviteCalleeToCall allows a user to add new peers to an already
+ * established conference.
+ *
+ * @param uri the callee to invite to an existing conf call.
+ * @param call the call that we should invite the callee to.
+ * @return the CallPeer object corresponding to the callee represented by
+ * the specified uri.
+ * @throws OperationFailedException if inviting the specified callee to the
+ * specified call fails
+ * @throws OperationNotSupportedException if allowing additional callees to
+ * a pre-established call is not supported.
+ */
+ public CallPeer inviteCalleeToCall(String uri, Call call)
+ throws OperationFailedException,
+ OperationNotSupportedException
+ {
+ return
+ protocolProvider
+ .getOperationSet(OperationSetTelephonyConferencing.class)
+ .inviteCalleeToCall(uri, call);
+ }
+
+ /**
+ * Indicates if there's an active video bridge available at this moment. The
+ * Jabber provider may announce support for video bridge, but it should not
+ * be used for calling until it becomes actually active.
+ *
+ * @return <tt>true</tt> to indicate that there's currently an active
+ * available video bridge, <tt>false</tt> - otherwise
+ */
+ public boolean isActive()
+ {
+ String jitsiVideobridge = protocolProvider.getJitsiVideobridge();
+
+ return ((jitsiVideobridge != null) && (jitsiVideobridge.length() > 0));
+ }
+
+ /**
+ * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has
+ * been received.
+ *
+ * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been
+ * received
+ */
+ private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ)
+ {
+ /*
+ * The application is not a Jitsi Videobridge server, it is a client.
+ * Consequently, the specified ColibriConferenceIQ is sent to it in
+ * relation to the part of the application's functionality which makes
+ * requests to a Jitsi Videobridge server i.e. CallJabberImpl.
+ *
+ * Additionally, the method processColibriConferenceIQ is presently tasked
+ * with processing ColibriConferenceIQ requests only. They are SET IQs
+ * sent by the Jitsi Videobridge server to notify the application about
+ * updates in the states of (colibri) conferences organized by the
+ * application.
+ */
+ if (IQ.Type.SET.equals(conferenceIQ.getType())
+ && conferenceIQ.getID() != null)
+ {
+ OperationSetBasicTelephony<?> basicTelephony
+ = protocolProvider.getOperationSet(
+ OperationSetBasicTelephony.class);
+
+ if (basicTelephony != null)
+ {
+ Iterator<? extends Call> i = basicTelephony.getActiveCalls();
+
+ while (i.hasNext())
+ {
+ Call call = i.next();
+
+ if (call instanceof CallJabberImpl)
+ {
+ CallJabberImpl callJabberImpl = (CallJabberImpl) call;
+ MediaAwareCallConference conference
+ = callJabberImpl.getConference();
+
+ if ((conference != null)
+ && conference.isJitsiVideobridge())
+ {
+ /*
+ * TODO We may want to disallow rogue CallJabberImpl
+ * instances which may throw an exception to prevent
+ * the conferenceIQ from reaching the CallJabberImpl
+ * instance which it was meant for.
+ */
+ if (callJabberImpl.processColibriConferenceIQ(
+ conferenceIQ))
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Implements {@link PacketListener}. Notifies this instance that a specific
+ * {@link Packet} (which this instance has already expressed interest into
+ * by returning <tt>true</tt> from {@link #accept(Packet)}) has been
+ * received.
+ *
+ * @param packet the <tt>Packet</tt> which has been received and which this
+ * instance is given a chance to process
+ */
+ public void processPacket(Packet packet)
+ {
+ /*
+ * As we do elsewhere, acknowledge the receipt of the Packet first and
+ * then go about our business with it.
+ */
+ IQ iq = (IQ) packet;
+
+ if (iq.getType() == IQ.Type.SET)
+ protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq));
+
+ /*
+ * Now that the acknowledging is out of the way, do go about our
+ * business with the Packet.
+ */
+ ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq;
+ boolean interrupted = false;
+
+ try
+ {
+ processColibriConferenceIQ(conferenceIQ);
+ }
+ catch (Throwable t)
+ {
+ logger.error(
+ "An error occurred during the processing of a "
+ + packet.getClass().getName() + " packet",
+ t);
+
+ if (t instanceof InterruptedException)
+ {
+ /*
+ * We cleared the interrupted state of the current Thread by
+ * catching the InterruptedException. However, we do not really
+ * care whether the current Thread has been interrupted - we
+ * caught the InterruptedException because we want to swallow
+ * any Throwable. Consequently, we should better restore the
+ * interrupted state.
+ */
+ interrupted = true;
+ }
+ else if (t instanceof ThreadDeath)
+ throw (ThreadDeath) t;
+ }
+ if (interrupted)
+ Thread.currentThread().interrupt();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Implements {@link RegistrationStateChangeListener}. Notifies this
+ * instance that there has been a change in the <tt>RegistrationState</tt>
+ * of {@link #protocolProvider}. Subscribes this instance to
+ * {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is
+ * registered and unsubscribes it as soon as <tt>protocolProvider</tt> is
+ * unregistered.
+ */
+ public void registrationStateChanged(RegistrationStateChangeEvent ev)
+ {
+ RegistrationState registrationState = ev.getNewState();
+
+ if (RegistrationState.REGISTERED.equals(registrationState))
+ {
+ protocolProvider.getConnection().addPacketListener(this, this);
+ }
+ else if (RegistrationState.UNREGISTERED.equals(registrationState))
+ {
+ XMPPConnection connection = protocolProvider.getConnection();
+
+ if (connection != null)
+ connection.removePacketListener(this);
+ }
+ }
+}
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 05f3d51..244cfe9 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
@@ -1,2857 +1,2857 @@
-/*
- * 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.colibri.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
-import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
-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 &quot;XEP-0320: Use of DTLS-SRTP in
- * Jingle Sessions&quot;.
- */
- 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";
-
- /**
- * Google P2P transport URN.
- */
- public static final String URN_GOOGLE_TRANSPORT_P2P
- = "http://www.google.com/transport/p2p";
-
- /**
- * 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";
-
- /**
- * 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<String> supportedFeatures = new ArrayList<String>();
-
- /**
- * The <tt>ServiceDiscoveryManager</tt> is responsible for advertising
- * <tt>supportedFeatures</tt> when asked by a remote client. It can also
- * be used to query remote clients for supported features.
- */
- private ScServiceDiscoveryManager discoveryManager = null;
-
- /**
- * The <tt>OperationSetContactCapabilities</tt> of this
- * <tt>ProtocolProviderService</tt> 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 <tt>RegistrationState</tt> 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)
- {
- 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(serviceName,
- 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));
- 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
- {
- 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);
-
- /* The initial presence message is sent by smack stack and does not
- * include priority information. In case the original status is
- * AVAILABLE, we will not update our presence information (such as
- * our priority) when we registered.
- */
- OperationSetPersistentPresenceJabberImpl opSet =
- (OperationSetPersistentPresenceJabberImpl)
- this.getOperationSet(OperationSetPersistentPresence.class);
-
- try
- {
- opSet.publishPresenceStatus(getJabberStatusEnum().getStatus(
- JabberStatusEnum.AVAILABLE), "");
- }
- catch(Exception e)
- {
- logger.error("Failed to publish presence status");
- }
-
- 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);
- discoveryManager.addFeature(URN_GOOGLE_TRANSPORT_P2P);
- }
-
- /*
- * 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
- * <tt>AccountProperties</tt>.
- *
- * @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 negociations
- // (with different ufrag/passwd) 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);
-
- //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 GTalk dialect provider
- providerManager.addIQProvider( SessionIQ.ELEMENT_NAME,
- SessionIQ.NAMESPACE,
- new SessionIQProvider());
-
- // 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(
- PayloadTypePacketExtension.ELEMENT_NAME,
- ColibriConferenceIQ.NAMESPACE,
- new DefaultPacketExtensionProvider<
- PayloadTypePacketExtension>(
- PayloadTypePacketExtension.class));
- providerManager.addExtensionProvider(
- ParameterPacketExtension.ELEMENT_NAME,
- ColibriConferenceIQ.NAMESPACE,
- new DefaultPacketExtensionProvider<
- ParameterPacketExtension>(
- ParameterPacketExtension.class));
-
- providerManager.addExtensionProvider(
- ConferenceDescriptionPacketExtension.ELEMENT_NAME,
- ConferenceDescriptionPacketExtension.NAMESPACE,
- new ConferenceDescriptionPacketExtension.Provider());
-
- //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));
-
- // 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 (sharee).
- 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);
-
- 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 <tt>XMPPConnection</tt>opened by this provider
- * @return a reference to the <tt>XMPPConnection</tt> last opened by this
- * provider.
- */
- public XMPPConnection getConnection()
- {
- return connection;
- }
-
- /**
- * Determines whether a specific <tt>XMPPException</tt> signals that
- * attempted authentication has failed.
- *
- * @param ex the <tt>XMPPException</tt> which is to be determined whether it
- * signals that attempted authentication has failed
- * @return <tt>true</tt> if the specified <tt>ex</tt> signals that attempted
- * authentication has failed; otherwise, <tt>false</tt>
- */
- 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 <tt>connectionClosed</tt> from <tt>ConnectionListener</tt>
- */
- 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 <tt>connectionClosedOnError</tt> from
- * <tt>ConnectionListener</tt>.
- *
- * @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 <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt>
- *
- * @param i delay in seconds for reconnection.
- */
- public void reconnectingIn(int i)
- {
- if (logger.isInfoEnabled())
- logger.info("reconnectingIn " + i);
- }
-
- /**
- * Implements <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt>
- */
- public void reconnectionSuccessful()
- {
- if (logger.isInfoEnabled())
- logger.info("reconnectionSuccessful");
- }
-
- /**
- * Implements <tt>reconnectionFailed</tt> from
- * <tt>ConnectionListener</tt>.
- *
- * @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 <tt>JabberStatusEnum</tt>.
- *
- * @return the current instance of <tt>JabberStatusEnum</tt>.
- */
- JabberStatusEnum getJabberStatusEnum()
- {
- return jabberStatusEnum;
- }
-
- /**
- * Determines if the given list of <tt>ext features</tt> 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 <tt>true</tt> if the list of ext features is supported;
- * otherwise, <tt>false</tt>
- */
- 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 <tt>features</tt> 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 <tt>true</tt> if the list of features is supported; otherwise,
- * <tt>false</tt>
- */
- 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 <tt>features</tt> 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 <tt>true</tt> if the list of features is supported, otherwise
- * returns <tt>false</tt>
- */
- 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)
- {
- fireRegistrationStateChanged(getRegistrationState(),
- RegistrationState.UNREGISTERED,
- RegistrationStateChangeEvent.REASON_USER_REQUEST,
- "Not trusted certificate");
- 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 <tt>InetAddress</tt> 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 <tt>InetAddress</tt> 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 <tt>Throwable</tt> cause as an
- * error using the current <tt>Logger</tt> and then throws a new
- * <tt>OperationFailedException</tt> with the message, a specific error code
- * and the cause.
- *
- * @param message the message to be logged and then wrapped in a new
- * <tt>OperationFailedException</tt>
- * @param errorCode the error code to be assigned to the new
- * <tt>OperationFailedException</tt>
- * @param cause the <tt>Throwable</tt> that has caused the necessity to log
- * an error and have a new <tt>OperationFailedException</tt> thrown
- * @param logger the logger that we'd like to log the error <tt>message</tt>
- * and <tt>cause</tt>.
- *
- * @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 <tt>serviceName</tt> of
- * <tt>connection</tt>.
- *
- * @return the entity ID of the first Jitsi VideoBridge associated with
- * <tt>connection</tt>
- */
- 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<DiscoverItems.Item> 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);
- }
- }
-}
+/*
+ * 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.colibri.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.gtalk.*;
+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 &quot;XEP-0320: Use of DTLS-SRTP in
+ * Jingle Sessions&quot;.
+ */
+ 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";
+
+ /**
+ * Google P2P transport URN.
+ */
+ public static final String URN_GOOGLE_TRANSPORT_P2P
+ = "http://www.google.com/transport/p2p";
+
+ /**
+ * 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";
+
+ /**
+ * 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<String> supportedFeatures = new ArrayList<String>();
+
+ /**
+ * The <tt>ServiceDiscoveryManager</tt> is responsible for advertising
+ * <tt>supportedFeatures</tt> when asked by a remote client. It can also
+ * be used to query remote clients for supported features.
+ */
+ private ScServiceDiscoveryManager discoveryManager = null;
+
+ /**
+ * The <tt>OperationSetContactCapabilities</tt> of this
+ * <tt>ProtocolProviderService</tt> 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 <tt>RegistrationState</tt> 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)
+ {
+ 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(serviceName,
+ 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));
+ 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
+ {
+ 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);
+
+ /* The initial presence message is sent by smack stack and does not
+ * include priority information. In case the original status is
+ * AVAILABLE, we will not update our presence information (such as
+ * our priority) when we registered.
+ */
+ OperationSetPersistentPresenceJabberImpl opSet =
+ (OperationSetPersistentPresenceJabberImpl)
+ this.getOperationSet(OperationSetPersistentPresence.class);
+
+ try
+ {
+ opSet.publishPresenceStatus(getJabberStatusEnum().getStatus(
+ JabberStatusEnum.AVAILABLE), "");
+ }
+ catch(Exception e)
+ {
+ logger.error("Failed to publish presence status");
+ }
+
+ 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);
+ discoveryManager.addFeature(URN_GOOGLE_TRANSPORT_P2P);
+ }
+
+ /*
+ * 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
+ * <tt>AccountProperties</tt>.
+ *
+ * @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 negociations
+ // (with different ufrag/passwd) 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);
+
+ //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 GTalk dialect provider
+ providerManager.addIQProvider( SessionIQ.ELEMENT_NAME,
+ SessionIQ.NAMESPACE,
+ new SessionIQProvider());
+
+ // 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(
+ PayloadTypePacketExtension.ELEMENT_NAME,
+ ColibriConferenceIQ.NAMESPACE,
+ new DefaultPacketExtensionProvider<
+ PayloadTypePacketExtension>(
+ PayloadTypePacketExtension.class));
+ providerManager.addExtensionProvider(
+ ParameterPacketExtension.ELEMENT_NAME,
+ ColibriConferenceIQ.NAMESPACE,
+ new DefaultPacketExtensionProvider<
+ ParameterPacketExtension>(
+ ParameterPacketExtension.class));
+
+ providerManager.addExtensionProvider(
+ ConferenceDescriptionPacketExtension.ELEMENT_NAME,
+ ConferenceDescriptionPacketExtension.NAMESPACE,
+ new ConferenceDescriptionPacketExtension.Provider());
+
+ //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));
+
+ // 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 (sharee).
+ 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);
+
+ 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 <tt>XMPPConnection</tt>opened by this provider
+ * @return a reference to the <tt>XMPPConnection</tt> last opened by this
+ * provider.
+ */
+ public XMPPConnection getConnection()
+ {
+ return connection;
+ }
+
+ /**
+ * Determines whether a specific <tt>XMPPException</tt> signals that
+ * attempted authentication has failed.
+ *
+ * @param ex the <tt>XMPPException</tt> which is to be determined whether it
+ * signals that attempted authentication has failed
+ * @return <tt>true</tt> if the specified <tt>ex</tt> signals that attempted
+ * authentication has failed; otherwise, <tt>false</tt>
+ */
+ 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 <tt>connectionClosed</tt> from <tt>ConnectionListener</tt>
+ */
+ 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 <tt>connectionClosedOnError</tt> from
+ * <tt>ConnectionListener</tt>.
+ *
+ * @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 <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt>
+ *
+ * @param i delay in seconds for reconnection.
+ */
+ public void reconnectingIn(int i)
+ {
+ if (logger.isInfoEnabled())
+ logger.info("reconnectingIn " + i);
+ }
+
+ /**
+ * Implements <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt>
+ */
+ public void reconnectionSuccessful()
+ {
+ if (logger.isInfoEnabled())
+ logger.info("reconnectionSuccessful");
+ }
+
+ /**
+ * Implements <tt>reconnectionFailed</tt> from
+ * <tt>ConnectionListener</tt>.
+ *
+ * @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 <tt>JabberStatusEnum</tt>.
+ *
+ * @return the current instance of <tt>JabberStatusEnum</tt>.
+ */
+ JabberStatusEnum getJabberStatusEnum()
+ {
+ return jabberStatusEnum;
+ }
+
+ /**
+ * Determines if the given list of <tt>ext features</tt> 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 <tt>true</tt> if the list of ext features is supported;
+ * otherwise, <tt>false</tt>
+ */
+ 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 <tt>features</tt> 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 <tt>true</tt> if the list of features is supported; otherwise,
+ * <tt>false</tt>
+ */
+ 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 <tt>features</tt> 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 <tt>true</tt> if the list of features is supported, otherwise
+ * returns <tt>false</tt>
+ */
+ 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)
+ {
+ fireRegistrationStateChanged(getRegistrationState(),
+ RegistrationState.UNREGISTERED,
+ RegistrationStateChangeEvent.REASON_USER_REQUEST,
+ "Not trusted certificate");
+ 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 <tt>InetAddress</tt> 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 <tt>InetAddress</tt> 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 <tt>Throwable</tt> cause as an
+ * error using the current <tt>Logger</tt> and then throws a new
+ * <tt>OperationFailedException</tt> with the message, a specific error code
+ * and the cause.
+ *
+ * @param message the message to be logged and then wrapped in a new
+ * <tt>OperationFailedException</tt>
+ * @param errorCode the error code to be assigned to the new
+ * <tt>OperationFailedException</tt>
+ * @param cause the <tt>Throwable</tt> that has caused the necessity to log
+ * an error and have a new <tt>OperationFailedException</tt> thrown
+ * @param logger the logger that we'd like to log the error <tt>message</tt>
+ * and <tt>cause</tt>.
+ *
+ * @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 <tt>serviceName</tt> of
+ * <tt>connection</tt>.
+ *
+ * @return the entity ID of the first Jitsi Videobridge associated with
+ * <tt>connection</tt>
+ */
+ 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<DiscoverItems.Item> 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);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java
index e91360c..4350dd3 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/colibri/ColibriStreamConnector.java
@@ -1,77 +1,77 @@
-/*
- * 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.extensions.colibri;
-
-import org.jitsi.service.neomedia.*;
-
-/**
- * Implements a <tt>StreamConnector</tt> which allows sharing a specific
- * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s
- * for the purposes of the Jitsi VideoBridge.
- *
- * @author Lyubomir Marinov
- */
-public class ColibriStreamConnector
- extends StreamConnectorDelegate<StreamConnector>
-{
- /**
- * Initializes a new <tt>ColibriStreamConnector</tt> instance which is to
- * share a specific <tt>StreamConnector</tt> instance among multiple
- * <tt>TransportManager</tt>s for the purposes of the Jitsi VideoBridge.
- *
- * @param streamConnector the <tt>StreamConnector</tt> instance to be shared
- * by the new instance among multiple <tt>TransportManager</tt>s for the
- * purposes of the Jitsi VideoBridge
- */
- public ColibriStreamConnector(StreamConnector streamConnector)
- {
- super(streamConnector);
- }
-
- /**
- * {@inheritDoc}
- *
- * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the
- * closing of the <tt>StreamConnector</tt> wrapped by this instance because
- * the latter is shared and it is not clear whether no
- * <tt>TransportManager</tt> is using it.
- */
- @Override
- public void close()
- {
- /*
- * Do not close the shared StreamConnector because it is not clear
- * whether no TransportManager is using it.
- */
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes {@link #close()} on this instance when it is clear that no
- * <tt>TransportManager</tt> is using it in order to release the resources
- * allocated by this instance throughout its life time (that need explicit
- * disposal).
- */
- @Override
- protected void finalize()
- throws Throwable
- {
- try
- {
- /*
- * Close the shared StreamConnector because it is clear that no
- * TrasportManager is using it.
- */
- super.close();
- }
- finally
- {
- super.finalize();
- }
- }
-}
+/*
+ * 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.extensions.colibri;
+
+import org.jitsi.service.neomedia.*;
+
+/**
+ * Implements a <tt>StreamConnector</tt> which allows sharing a specific
+ * <tt>StreamConnector</tt> instance among multiple <tt>TransportManager</tt>s
+ * for the purposes of the Jitsi Videobridge.
+ *
+ * @author Lyubomir Marinov
+ */
+public class ColibriStreamConnector
+ extends StreamConnectorDelegate<StreamConnector>
+{
+ /**
+ * Initializes a new <tt>ColibriStreamConnector</tt> instance which is to
+ * share a specific <tt>StreamConnector</tt> instance among multiple
+ * <tt>TransportManager</tt>s for the purposes of the Jitsi Videobridge.
+ *
+ * @param streamConnector the <tt>StreamConnector</tt> instance to be shared
+ * by the new instance among multiple <tt>TransportManager</tt>s for the
+ * purposes of the Jitsi Videobridge
+ */
+ public ColibriStreamConnector(StreamConnector streamConnector)
+ {
+ super(streamConnector);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Overrides {@link StreamConnectorDelegate#close()} in order to prevent the
+ * closing of the <tt>StreamConnector</tt> wrapped by this instance because
+ * the latter is shared and it is not clear whether no
+ * <tt>TransportManager</tt> is using it.
+ */
+ @Override
+ public void close()
+ {
+ /*
+ * Do not close the shared StreamConnector because it is not clear
+ * whether no TransportManager is using it.
+ */
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes {@link #close()} on this instance when it is clear that no
+ * <tt>TransportManager</tt> is using it in order to release the resources
+ * allocated by this instance throughout its life time (that need explicit
+ * disposal).
+ */
+ @Override
+ protected void finalize()
+ throws Throwable
+ {
+ try
+ {
+ /*
+ * Close the shared StreamConnector because it is clear that no
+ * TrasportManager is using it.
+ */
+ super.close();
+ }
+ finally
+ {
+ super.finalize();
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/CallConference.java b/src/net/java/sip/communicator/service/protocol/CallConference.java
index bc0cc15..443aeb0 100644
--- a/src/net/java/sip/communicator/service/protocol/CallConference.java
+++ b/src/net/java/sip/communicator/service/protocol/CallConference.java
@@ -1,931 +1,931 @@
-/*
- * 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.service.protocol;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.event.*;
-
-import org.jitsi.util.event.*;
-
-/**
- * Represents the telephony conference-related state of a <tt>Call</tt>.
- * Multiple <tt>Call</tt> instances share a single <tt>CallConference</tt>
- * instance when the former are into a telephony conference i.e. the local
- * peer/user is the conference focus. <tt>CallConference</tt> is
- * protocol-agnostic and thus enables cross-protocol conferences. Since a
- * non-conference <tt>Call</tt> may be converted into a conference <tt>Call</tt>
- * at any time, every <tt>Call</tt> instance maintains a <tt>CallConference</tt>
- * instance regardless of whether the <tt>Call</tt> in question is participating
- * in a telephony conference.
- *
- * @author Lyubomir Marinov
- */
-public class CallConference
- extends PropertyChangeNotifier
-{
- /**
- * The name of the <tt>CallConference</tt> property which specifies the list
- * of <tt>Call</tt>s participating in a telephony conference. A change in
- * the value of the property is delivered in the form of a
- * <tt>PropertyChangeEvent</tt> which has its <tt>oldValue</tt> or
- * <tt>newValue</tt> set to the <tt>Call</tt> which has been removed or
- * added to the list of <tt>Call</tt>s participating in the telephony
- * conference.
- */
- public static final String CALLS = "calls";
-
- /**
- * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in the telephony conference-related state of a specific
- * <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> for which the number of <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in its associated
- * telephony conference-related state
- * @return the number of <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in the telephony conference-related state
- * of the specified <tt>Call</tt>
- */
- public static int getCallPeerCount(Call call)
- {
- CallConference conference = call.getConference();
-
- /*
- * A Call instance is supposed to always maintain a CallConference
- * instance. Anyway, if it turns out that it is not the case, we will
- * consider the Call as a representation of a telephony conference.
- */
- return
- (conference == null)
- ? call.getCallPeerCount()
- : conference.getCallPeerCount();
- }
-
- /**
- * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in the telephony conference in which a specific
- * <tt>Call</tt> is participating.
- *
- * @param call the <tt>Call</tt> which specifies the telephony conference
- * the <tt>CallPeer</tt>s of which are to be retrieved
- * @return a list of the <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in the telephony conference in which the
- * specified <tt>call</tt> is participating
- */
- public static List<CallPeer> getCallPeers(Call call)
- {
- CallConference conference = call.getConference();
- List<CallPeer> callPeers = new ArrayList<CallPeer>();
-
- if (conference == null)
- {
- Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
-
- while (callPeerIt.hasNext())
- callPeers.add(callPeerIt.next());
- }
- else
- conference.getCallPeers(callPeers);
- return callPeers;
- }
-
- /**
- * Gets the list of <tt>Call</tt>s participating in the telephony conference
- * in which a specific <tt>Call</tt> is participating.
- *
- * @param call the <tt>Call</tt> which participates in the telephony
- * conference the list of participating <tt>Call</tt>s of which is to be
- * returned
- * @return the list of <tt>Call</tt>s participating in the telephony
- * conference in which the specified <tt>call</tt> is participating
- */
- public static List<Call> getCalls(Call call)
- {
- CallConference conference = call.getConference();
- List<Call> calls;
-
- if (conference == null)
- calls = Collections.emptyList();
- else
- calls = conference.getCalls();
- return calls;
- }
-
- /**
- * Determines whether a <tt>CallConference</tt> is to report the local
- * peer/user as a conference focus judging by a specific list of
- * <tt>Call</tt>s.
- *
- * @param calls the list of <tt>Call</tt> which are to be judged whether
- * the local peer/user that they represent is to be considered as a
- * conference focus
- * @return <tt>true</tt> if the local peer/user represented by the specified
- * <tt>calls</tt> is judged to be a conference focus; otherwise,
- * <tt>false</tt>
- */
- private static boolean isConferenceFocus(List<Call> calls)
- {
- int callCount = calls.size();
- boolean conferenceFocus;
-
- if (callCount < 1)
- conferenceFocus = false;
- else if (callCount > 1)
- conferenceFocus = true;
- else
- conferenceFocus = (calls.get(0).getCallPeerCount() > 1);
- return conferenceFocus;
- }
-
- /**
- * The <tt>CallChangeListener</tt> which listens to changes in the
- * <tt>Call</tt>s participating in this telephony conference.
- */
- private final CallChangeListener callChangeListener
- = new CallChangeListener()
- {
- public void callPeerAdded(CallPeerEvent ev)
- {
- CallConference.this.onCallPeerEvent(ev);
- }
-
- public void callPeerRemoved(CallPeerEvent ev)
- {
- CallConference.this.onCallPeerEvent(ev);
- }
-
- public void callStateChanged(CallChangeEvent ev)
- {
- CallConference.this.callStateChanged(ev);
- }
- };
-
- /**
- * The list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
- * participating in this telephony conference via
- * {@link #addCallChangeListener(CallChangeListener)}.
- */
- private final List<CallChangeListener> callChangeListeners
- = new LinkedList<CallChangeListener>();
-
- /**
- * The <tt>CallPeerConferenceListener</tt> which listens to the
- * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
- * this telephony conference.
- */
- private final CallPeerConferenceListener callPeerConferenceListener
- = new CallPeerConferenceAdapter()
- {
- /**
- * {@inheritDoc}
- *
- * Invokes
- * {@link CallConference#onCallPeerConferenceEvent(
- * CallPeerConferenceEvent)}.
- */
- @Override
- protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
- {
- CallConference.this.onCallPeerConferenceEvent(ev);
- }
-
- /**
- * {@inheritDoc}
- *
- * Invokes
- * {@link CallConference#onCallPeerConferenceEvent(
- * CallPeerConferenceEvent)}.
- */
- @Override
- public void conferenceMemberErrorReceived(
- CallPeerConferenceEvent ev)
- {
- CallConference.this.onCallPeerConferenceEvent(ev);
- }
- };
-
- /**
- * The list of <tt>CallPeerConferenceListener</tt>s added to the
- * <tt>CallPeer</tt>s associated with the <tt>CallPeer</tt>s participating
- * in this telephony conference via
- * {@link #addCallPeerConferenceListener}.
- */
- private final List<CallPeerConferenceListener> callPeerConferenceListeners
- = new LinkedList<CallPeerConferenceListener>();
-
- /**
- * The synchronization root/<tt>Object</tt> which protects the access to
- * {@link #immutableCalls} and {@link #mutableCalls}.
- */
- private final Object callsSyncRoot = new Object();
-
- /**
- * The indicator which determines whether the local peer represented by this
- * instance and the <tt>Call</tt>s participating in it is acting as a
- * conference focus. The SIP protocol, for example, will add the
- * &quot;isfocus&quot; parameter to the Contact headers of its outgoing
- * signaling if <tt>true</tt>.
- */
- private boolean conferenceFocus = false;
-
- /**
- * The list of <tt>Call</tt>s participating in this telephony conference as
- * an immutable <tt>List</tt> which can be exposed out of this instance
- * without the need to make a copy. In other words, it is an unmodifiable
- * view of {@link #mutableCalls}.
- */
- private List<Call> immutableCalls;
-
- /**
- * The indicator which determines whether the telephony conference
- * represented by this instance is utilizing the Jitsi VideoBridge
- * server-side telephony conferencing technology.
- */
- private final boolean jitsiVideoBridge;
-
- /**
- * The list of <tt>Call</tt>s participating in this telephony conference as
- * a mutable <tt>List</tt> which should not be exposed out of this instance.
- */
- private List<Call> mutableCalls;
-
- /**
- * Initializes a new <tt>CallConference</tt> instance.
- */
- public CallConference()
- {
- this(false);
- }
-
- /**
- * Initializes a new <tt>CallConference</tt> instance which is to optionally
- * utilize the Jitsi VideoBridge server-side telephony conferencing
- * technology.
- *
- * @param jitsiVideoBridge <tt>true</tt> if the telephony conference
- * represented by the new instance is to utilize the Jitsi VideoBridge
- * server-side telephony conferencing technology; otherwise, <tt>false</tt>
- */
- public CallConference(boolean jitsiVideoBridge)
- {
- this.jitsiVideoBridge = jitsiVideoBridge;
-
- mutableCalls = new ArrayList<Call>();
- immutableCalls = Collections.unmodifiableList(mutableCalls);
- }
-
- /**
- * Adds a specific <tt>Call</tt> to the list of <tt>Call</tt>s participating
- * in this telephony conference.
- *
- * @param call the <tt>Call</tt> to add to the list of <tt>Call</tt>s
- * participating in this telephony conference
- * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
- * telephony conference changed as a result of the method call; otherwise,
- * <tt>false</tt>
- * @throws NullPointerException if <tt>call</tt> is <tt>null</tt>
- */
- boolean addCall(Call call)
- {
- if (call == null)
- throw new NullPointerException("call");
-
- synchronized (callsSyncRoot)
- {
- if (mutableCalls.contains(call))
- return false;
-
- /*
- * Implement the List of Calls participating in this telephony
- * conference as a copy-on-write storage in order to optimize the
- * getCalls method which is likely to be executed much more often
- * than the addCall and removeCall methods.
- */
- List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
-
- if (newMutableCalls.add(call))
- {
- mutableCalls = newMutableCalls;
- immutableCalls = Collections.unmodifiableList(mutableCalls);
- }
- else
- return false;
- }
-
- callAdded(call);
- return true;
- }
-
- /**
- * Adds a <tt>CallChangeListener</tt> to the <tt>Call</tt>s participating in
- * this telephony conference. The method is a convenience that takes on the
- * responsibility of tracking the <tt>Call</tt>s that get added/removed
- * to/from this telephony conference.
- *
- * @param listener the <tt>CallChangeListner</tt> to be added to the
- * <tt>Call</tt>s participating in this telephony conference
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addCallChangeListener(CallChangeListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
- else
- {
- synchronized (callChangeListeners)
- {
- if (!callChangeListeners.contains(listener))
- callChangeListeners.add(listener);
- }
- }
- }
-
- /**
- * Adds {@link #callPeerConferenceListener} to the <tt>CallPeer</tt>s
- * associated with a specific <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> to whose associated <tt>CallPeer</tt>s
- * <tt>callPeerConferenceListener</tt> is to be added
- */
- private void addCallPeerConferenceListener(Call call)
- {
- Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- callPeerIter.next().addCallPeerConferenceListener(
- callPeerConferenceListener);
- }
- }
-
- /**
- * Adds a <tt>CallPeerConferenceListener</tt> to the <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in this telephony
- * conference. The method is a convenience that takes on the responsibility
- * of tracking the <tt>Call</tt>s that get added/removed to/from this
- * telephony conference and the <tt>CallPeer</tt> that get added/removed
- * to/from these <tt>Call</tt>s.
- *
- * @param listener the <tt>CallPeerConferenceListener</tt> to be added to
- * the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating
- * in this telephony conference
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addCallPeerConferenceListener(
- CallPeerConferenceListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
- else
- {
- synchronized (callPeerConferenceListeners)
- {
- if (!callPeerConferenceListeners.contains(listener))
- callPeerConferenceListeners.add(listener);
- }
- }
- }
-
- /**
- * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
- * been added to the list of <tt>Call</tt>s participating in this telephony
- * conference.
- *
- * @param call the <tt>Call</tt> which has been added to the list of
- * <tt>Call</tt>s participating in this telephony conference
- */
- protected void callAdded(Call call)
- {
- call.addCallChangeListener(callChangeListener);
- addCallPeerConferenceListener(call);
-
- /*
- * Update the conferenceFocus state. Because the public
- * setConferenceFocus method allows forcing a specific value on the
- * state in question and because it does not sound right to have the
- * adding of a Call set conferenceFocus to false, only update it if the
- * new conferenceFocus value is true,
- */
- boolean conferenceFocus = isConferenceFocus(getCalls());
-
- if (conferenceFocus)
- setConferenceFocus(conferenceFocus);
-
- firePropertyChange(CALLS, null, call);
- }
-
- /**
- * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
- * been removed from the list of <tt>Call</tt>s participating in this
- * telephony conference.
- *
- * @param call the <tt>Call</tt> which has been removed from the list of
- * <tt>Call</tt>s participating in this telephony conference
- */
- protected void callRemoved(Call call)
- {
- call.removeCallChangeListener(callChangeListener);
- removeCallPeerConferenceListener(call);
-
- /*
- * Update the conferenceFocus state. Following the line of thinking
- * expressed in the callAdded method, only update it if the new
- * conferenceFocus value is false.
- */
- boolean conferenceFocus = isConferenceFocus(getCalls());
-
- if (!conferenceFocus)
- setConferenceFocus(conferenceFocus);
-
- firePropertyChange(CALLS, call, null);
- }
-
- /**
- * Notifies this telephony conference that the <tt>CallState</tt> of a
- * <tt>Call</tt> has changed.
- *
- * @param ev a <tt>CallChangeEvent</tt> which specifies the <tt>Call</tt>
- * which had its <tt>CallState</tt> changed and the old and new
- * <tt>CallState</tt>s of that <tt>Call</tt>
- */
- private void callStateChanged(CallChangeEvent ev)
- {
- Call call = ev.getSourceCall();
-
- if (containsCall(call))
- {
- try
- {
- // Forward the CallChangeEvent to the callChangeListeners.
- for (CallChangeListener l : getCallChangeListeners())
- l.callStateChanged(ev);
- }
- finally
- {
- if (CallChangeEvent.CALL_STATE_CHANGE
- .equals(ev.getPropertyName())
- && CallState.CALL_ENDED.equals(ev.getNewValue()))
- {
- /*
- * Should not be vital because Call will remove itself.
- * Anyway, do it for the sake of completeness.
- */
- removeCall(call);
- }
- }
- }
- }
-
- /**
- * Notifies this <tt>CallConference</tt> that the value of its
- * <tt>conferenceFocus</tt> property has changed from a specific old value
- * to a specific new value.
- *
- * @param oldValue the value of the <tt>conferenceFocus</tt> property of
- * this instance before the change
- * @param newValue the value of the <tt>conferenceFocus</tt> property of
- * this instance after the change
- */
- protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
- {
- firePropertyChange(Call.CONFERENCE_FOCUS, oldValue, newValue);
- }
-
- /**
- * Determines whether a specific <tt>Call</tt> is participating in this
- * telephony conference.
- *
- * @param call the <tt>Call</tt> which is to be checked whether it is
- * participating in this telephony conference
- * @return <tt>true</tt> if the specified <tt>call</tt> is participating in
- * this telephony conference
- */
- public boolean containsCall(Call call)
- {
- synchronized (callsSyncRoot)
- {
- return mutableCalls.contains(call);
- }
- }
-
- /**
- * Gets the list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
- * participating in this telephony conference via
- * {@link #addCallChangeListener(CallChangeListener)}.
- *
- * @return the list of <tt>CallChangeListener</tt>s added to the
- * <tt>Call</tt>s participating in this telephony conference via
- * {@link #addCallChangeListener(CallChangeListener)}
- */
- private CallChangeListener[] getCallChangeListeners()
- {
- synchronized (callChangeListeners)
- {
- return
- callChangeListeners.toArray(
- new CallChangeListener[callChangeListeners.size()]);
- }
- }
-
- /**
- * Gets the number of <tt>Call</tt>s that are participating in this
- * telephony conference.
- *
- * @return the number of <tt>Call</tt>s that are participating in this
- * telephony conference
- */
- public int getCallCount()
- {
- synchronized (callsSyncRoot)
- {
- return mutableCalls.size();
- }
- }
-
- /**
- * Gets the list of <tt>CallPeerConferenceListener</tt>s added to the
- * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
- * this telephony conference via
- * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}.
- *
- * @return the list of <tt>CallPeerConferenceListener</tt>s added to the
- * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
- * this telephony conference via
- * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}
- */
- private CallPeerConferenceListener[] getCallPeerConferenceListeners()
- {
- synchronized (callPeerConferenceListeners)
- {
- return
- callPeerConferenceListeners.toArray(
- new CallPeerConferenceListener[
- callPeerConferenceListeners.size()]);
- }
- }
-
- /**
- * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @return the number of <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in this telephony conference
- */
- public int getCallPeerCount()
- {
- int callPeerCount = 0;
-
- for (Call call : getCalls())
- callPeerCount += call.getCallPeerCount();
- return callPeerCount;
- }
-
- /**
- * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @return a list of the <tt>CallPeer</tt>s associated with the
- * <tt>Call</tt>s participating in this telephony conference
- */
- public List<CallPeer> getCallPeers()
- {
- List<CallPeer> callPeers = new ArrayList<CallPeer>();
-
- getCallPeers(callPeers);
- return callPeers;
- }
-
- /**
- * Adds the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference into a specific <tt>List</tt>.
- *
- * @param callPeers a <tt>List</tt> into which the <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in this telephony
- * conference are to be added
- */
- protected void getCallPeers(List<CallPeer> callPeers)
- {
- for (Call call : getCalls())
- {
- Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
-
- while (callPeerIt.hasNext())
- callPeers.add(callPeerIt.next());
- }
- }
-
- /**
- * Gets the list of <tt>Call</tt> participating in this telephony
- * conference.
- *
- * @return the list of <tt>Call</tt>s participating in this telephony
- * conference. An empty array of <tt>Call</tt> element type is returned if
- * there are no <tt>Call</tt>s in this telephony conference-related state.
- */
- public List<Call> getCalls()
- {
- synchronized (callsSyncRoot)
- {
- return immutableCalls;
- }
- }
-
- /**
- * Determines whether the local peer/user associated with this instance and
- * represented by the <tt>Call</tt>s participating into it is acting as a
- * conference focus.
- *
- * @return <tt>true</tt> if the local peer/user associated by this instance
- * is acting as a conference focus; otherwise, <tt>false</tt>
- */
- public boolean isConferenceFocus()
- {
- return conferenceFocus;
- }
-
- /**
- * Determines whether the current state of this instance suggests that the
- * telephony conference it represents has ended. Iterates over the
- * <tt>Call</tt>s participating in this telephony conference and looks for a
- * <tt>Call</tt> which is not in the {@link CallState#CALL_ENDED} state.
- *
- * @return <tt>true</tt> if the current state of this instance suggests that
- * the telephony conference it represents has ended; otherwise,
- * <tt>false</tt>
- */
- public boolean isEnded()
- {
- for (Call call : getCalls())
- {
- if (!CallState.CALL_ENDED.equals(call.getCallState()))
- return false;
- }
- return true;
- }
-
- /**
- * Determines whether the telephony conference represented by this instance
- * is utilizing the Jitsi VideoBridge server-side telephony conferencing
- * technology.
- *
- * @return <tt>true</tt> if the telephony conference represented by this
- * instance is utilizing the Jitsi VideoBridge server-side telephony
- * conferencing technology
- */
- public boolean isJitsiVideoBridge()
- {
- return jitsiVideoBridge;
- }
-
- /**
- * Notifies this telephony conference that a
- * <tt>CallPeerConferenceEvent</tt> was fired by a <tt>CallPeer</tt>
- * associated with a <tt>Call</tt> participating in this telephony
- * conference. Forwards the specified <tt>CallPeerConferenceEvent</tt> to
- * {@link #callPeerConferenceListeners}.
- *
- * @param ev the <tt>CallPeerConferenceEvent</tt> which was fired
- */
- private void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
- {
- int eventID = ev.getEventID();
-
- for (CallPeerConferenceListener l : getCallPeerConferenceListeners())
- {
- switch (eventID)
- {
- case CallPeerConferenceEvent.CONFERENCE_FOCUS_CHANGED:
- l.conferenceFocusChanged(ev);
- break;
- case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED:
- l.conferenceMemberAdded(ev);
- break;
- case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED:
- l.conferenceMemberRemoved(ev);
- break;
- case CallPeerConferenceEvent.CONFERENCE_MEMBER_ERROR_RECEIVED:
- l.conferenceMemberErrorReceived(ev);
- break;
- default:
- throw new UnsupportedOperationException(
- "Unsupported CallPeerConferenceEvent eventID.");
- }
- }
- }
-
- /**
- * Notifies this telephony conference about a specific
- * <tt>CallPeerEvent</tt> i.e. that a <tt>CallPeer</tt> was either added to
- * or removed from a <tt>Call</tt>.
- *
- * @param ev a <tt>CallPeerEvent</tt> which specifies the <tt>CallPeer</tt>
- * which was added or removed and the <tt>Call</tt> to which it was added or
- * from which is was removed
- */
- private void onCallPeerEvent(CallPeerEvent ev)
- {
- Call call = ev.getSourceCall();
-
- if (containsCall(call))
- {
- /*
- * Update the conferenceFocus state. Following the line of thinking
- * expressed in the callAdded and callRemoved methods, only update
- * it if the new conferenceFocus value is in accord with the
- * expectations.
- */
- int eventID = ev.getEventID();
- boolean conferenceFocus = isConferenceFocus(getCalls());
-
- switch (eventID)
- {
- case CallPeerEvent.CALL_PEER_ADDED:
- if (conferenceFocus)
- setConferenceFocus(conferenceFocus);
- break;
- case CallPeerEvent.CALL_PEER_REMOVED:
- if (!conferenceFocus)
- setConferenceFocus(conferenceFocus);
- break;
- default:
- /*
- * We're interested in the adding and removing of CallPeers
- * only.
- */
- break;
- }
-
- try
- {
- // Forward the CallPeerEvent to the callChangeListeners.
- for (CallChangeListener l : getCallChangeListeners())
- {
- switch (eventID)
- {
- case CallPeerEvent.CALL_PEER_ADDED:
- l.callPeerAdded(ev);
- break;
- case CallPeerEvent.CALL_PEER_REMOVED:
- l.callPeerRemoved(ev);
- break;
- default:
- break;
- }
- }
- }
- finally
- {
- /*
- * Add/remove the callPeerConferenceListener to/from the source
- * CallPeer (for the purposes of the
- * addCallPeerConferenceListener method of this CallConference).
- */
- CallPeer callPeer = ev.getSourceCallPeer();
-
- switch (eventID)
- {
- case CallPeerEvent.CALL_PEER_ADDED:
- callPeer.addCallPeerConferenceListener(
- callPeerConferenceListener);
- break;
- case CallPeerEvent.CALL_PEER_REMOVED:
- callPeer.removeCallPeerConferenceListener(
- callPeerConferenceListener);
- break;
- default:
- break;
- }
- }
- }
- }
-
- /**
- * Removes a specific <tt>Call</tt> from the list of <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @param call the <tt>Call</tt> to remove from the list of <tt>Call</tt>s
- * participating in this telephony conference
- * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
- * telephony conference changed as a result of the method call; otherwise,
- * <tt>false</tt>
- */
- boolean removeCall(Call call)
- {
- if (call == null)
- return false;
-
- synchronized (callsSyncRoot)
- {
- if (!mutableCalls.contains(call))
- return false;
-
- /*
- * Implement the List of Calls participating in this telephony
- * conference as a copy-on-write storage in order to optimize the
- * getCalls method which is likely to be executed much more often
- * than the addCall and removeCall methods.
- */
- List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
-
- if (newMutableCalls.remove(call))
- {
- mutableCalls = newMutableCalls;
- immutableCalls = Collections.unmodifiableList(mutableCalls);
- }
- else
- return false;
- }
-
- callRemoved(call);
- return true;
- }
-
- /**
- * Removes a <tt>CallChangeListener</tt> from the <tt>Call</tt>s
- * participating in this telephony conference.
- *
- * @param listener the <tt>CallChangeListener</tt> to be removed from the
- * <tt>Call</tt>s participating in this telephony conference
- * @see #addCallChangeListener(CallChangeListener)
- */
- public void removeCallChangeListener(CallChangeListener listener)
- {
- if (listener != null)
- {
- synchronized (callChangeListeners)
- {
- callChangeListeners.remove(listener);
- }
- }
- }
-
- /**
- * Removes {@link #callPeerConferenceListener} from the <tt>CallPeer</tt>s
- * associated with a specific <tt>Call</tt>.
- *
- * @param call the <tt>Call</tt> from whose associated <tt>CallPeer</tt>s
- * <tt>callPeerConferenceListener</tt> is to be removed
- */
- private void removeCallPeerConferenceListener(Call call)
- {
- Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
-
- while (callPeerIter.hasNext())
- {
- callPeerIter.next().removeCallPeerConferenceListener(
- callPeerConferenceListener);
- }
- }
-
- /**
- * Removes a <tt>CallPeerConferenceListener</tt> from the <tt>CallPeer</tt>s
- * associated with the <tt>Call</tt>s participating in this telephony
- * conference.
- *
- * @param listener the <tt>CallPeerConferenceListener</tt> to be removed
- * from the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
- * participating in this telephony conference
- * @see #addCallPeerConferenceListener(CallPeerConferenceListener)
- */
- public void removeCallPeerConferenceListener(
- CallPeerConferenceListener listener)
- {
- if (listener != null)
- {
- synchronized (callPeerConferenceListeners)
- {
- callPeerConferenceListeners.remove(listener);
- }
- }
- }
-
- /**
- * Sets the indicator which determines whether the local peer represented by
- * this instance and the <tt>Call</tt>s participating in it is acting as a
- * conference focus (and thus may, for example, need to send the
- * corresponding parameters in its outgoing signaling).
- *
- * @param conferenceFocus <tt>true</tt> if the local peer represented by
- * this instance and the <tt>Call</tt>s participating in it is to act as a
- * conference focus; otherwise, <tt>false</tt>
- */
- public void setConferenceFocus(boolean conferenceFocus)
- {
- if (this.conferenceFocus != conferenceFocus)
- {
- boolean oldValue = isConferenceFocus();
-
- this.conferenceFocus = conferenceFocus;
-
- boolean newValue = isConferenceFocus();
-
- if (oldValue != newValue)
- conferenceFocusChanged(oldValue, newValue);
- }
- }
-}
+/*
+ * 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.service.protocol;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.event.*;
+
+import org.jitsi.util.event.*;
+
+/**
+ * Represents the telephony conference-related state of a <tt>Call</tt>.
+ * Multiple <tt>Call</tt> instances share a single <tt>CallConference</tt>
+ * instance when the former are into a telephony conference i.e. the local
+ * peer/user is the conference focus. <tt>CallConference</tt> is
+ * protocol-agnostic and thus enables cross-protocol conferences. Since a
+ * non-conference <tt>Call</tt> may be converted into a conference <tt>Call</tt>
+ * at any time, every <tt>Call</tt> instance maintains a <tt>CallConference</tt>
+ * instance regardless of whether the <tt>Call</tt> in question is participating
+ * in a telephony conference.
+ *
+ * @author Lyubomir Marinov
+ */
+public class CallConference
+ extends PropertyChangeNotifier
+{
+ /**
+ * The name of the <tt>CallConference</tt> property which specifies the list
+ * of <tt>Call</tt>s participating in a telephony conference. A change in
+ * the value of the property is delivered in the form of a
+ * <tt>PropertyChangeEvent</tt> which has its <tt>oldValue</tt> or
+ * <tt>newValue</tt> set to the <tt>Call</tt> which has been removed or
+ * added to the list of <tt>Call</tt>s participating in the telephony
+ * conference.
+ */
+ public static final String CALLS = "calls";
+
+ /**
+ * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in the telephony conference-related state of a specific
+ * <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> for which the number of <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in its associated
+ * telephony conference-related state
+ * @return the number of <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in the telephony conference-related state
+ * of the specified <tt>Call</tt>
+ */
+ public static int getCallPeerCount(Call call)
+ {
+ CallConference conference = call.getConference();
+
+ /*
+ * A Call instance is supposed to always maintain a CallConference
+ * instance. Anyway, if it turns out that it is not the case, we will
+ * consider the Call as a representation of a telephony conference.
+ */
+ return
+ (conference == null)
+ ? call.getCallPeerCount()
+ : conference.getCallPeerCount();
+ }
+
+ /**
+ * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in the telephony conference in which a specific
+ * <tt>Call</tt> is participating.
+ *
+ * @param call the <tt>Call</tt> which specifies the telephony conference
+ * the <tt>CallPeer</tt>s of which are to be retrieved
+ * @return a list of the <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in the telephony conference in which the
+ * specified <tt>call</tt> is participating
+ */
+ public static List<CallPeer> getCallPeers(Call call)
+ {
+ CallConference conference = call.getConference();
+ List<CallPeer> callPeers = new ArrayList<CallPeer>();
+
+ if (conference == null)
+ {
+ Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
+
+ while (callPeerIt.hasNext())
+ callPeers.add(callPeerIt.next());
+ }
+ else
+ conference.getCallPeers(callPeers);
+ return callPeers;
+ }
+
+ /**
+ * Gets the list of <tt>Call</tt>s participating in the telephony conference
+ * in which a specific <tt>Call</tt> is participating.
+ *
+ * @param call the <tt>Call</tt> which participates in the telephony
+ * conference the list of participating <tt>Call</tt>s of which is to be
+ * returned
+ * @return the list of <tt>Call</tt>s participating in the telephony
+ * conference in which the specified <tt>call</tt> is participating
+ */
+ public static List<Call> getCalls(Call call)
+ {
+ CallConference conference = call.getConference();
+ List<Call> calls;
+
+ if (conference == null)
+ calls = Collections.emptyList();
+ else
+ calls = conference.getCalls();
+ return calls;
+ }
+
+ /**
+ * Determines whether a <tt>CallConference</tt> is to report the local
+ * peer/user as a conference focus judging by a specific list of
+ * <tt>Call</tt>s.
+ *
+ * @param calls the list of <tt>Call</tt> which are to be judged whether
+ * the local peer/user that they represent is to be considered as a
+ * conference focus
+ * @return <tt>true</tt> if the local peer/user represented by the specified
+ * <tt>calls</tt> is judged to be a conference focus; otherwise,
+ * <tt>false</tt>
+ */
+ private static boolean isConferenceFocus(List<Call> calls)
+ {
+ int callCount = calls.size();
+ boolean conferenceFocus;
+
+ if (callCount < 1)
+ conferenceFocus = false;
+ else if (callCount > 1)
+ conferenceFocus = true;
+ else
+ conferenceFocus = (calls.get(0).getCallPeerCount() > 1);
+ return conferenceFocus;
+ }
+
+ /**
+ * The <tt>CallChangeListener</tt> which listens to changes in the
+ * <tt>Call</tt>s participating in this telephony conference.
+ */
+ private final CallChangeListener callChangeListener
+ = new CallChangeListener()
+ {
+ public void callPeerAdded(CallPeerEvent ev)
+ {
+ CallConference.this.onCallPeerEvent(ev);
+ }
+
+ public void callPeerRemoved(CallPeerEvent ev)
+ {
+ CallConference.this.onCallPeerEvent(ev);
+ }
+
+ public void callStateChanged(CallChangeEvent ev)
+ {
+ CallConference.this.callStateChanged(ev);
+ }
+ };
+
+ /**
+ * The list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
+ * participating in this telephony conference via
+ * {@link #addCallChangeListener(CallChangeListener)}.
+ */
+ private final List<CallChangeListener> callChangeListeners
+ = new LinkedList<CallChangeListener>();
+
+ /**
+ * The <tt>CallPeerConferenceListener</tt> which listens to the
+ * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
+ * this telephony conference.
+ */
+ private final CallPeerConferenceListener callPeerConferenceListener
+ = new CallPeerConferenceAdapter()
+ {
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes
+ * {@link CallConference#onCallPeerConferenceEvent(
+ * CallPeerConferenceEvent)}.
+ */
+ @Override
+ protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
+ {
+ CallConference.this.onCallPeerConferenceEvent(ev);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Invokes
+ * {@link CallConference#onCallPeerConferenceEvent(
+ * CallPeerConferenceEvent)}.
+ */
+ @Override
+ public void conferenceMemberErrorReceived(
+ CallPeerConferenceEvent ev)
+ {
+ CallConference.this.onCallPeerConferenceEvent(ev);
+ }
+ };
+
+ /**
+ * The list of <tt>CallPeerConferenceListener</tt>s added to the
+ * <tt>CallPeer</tt>s associated with the <tt>CallPeer</tt>s participating
+ * in this telephony conference via
+ * {@link #addCallPeerConferenceListener}.
+ */
+ private final List<CallPeerConferenceListener> callPeerConferenceListeners
+ = new LinkedList<CallPeerConferenceListener>();
+
+ /**
+ * The synchronization root/<tt>Object</tt> which protects the access to
+ * {@link #immutableCalls} and {@link #mutableCalls}.
+ */
+ private final Object callsSyncRoot = new Object();
+
+ /**
+ * The indicator which determines whether the local peer represented by this
+ * instance and the <tt>Call</tt>s participating in it is acting as a
+ * conference focus. The SIP protocol, for example, will add the
+ * &quot;isfocus&quot; parameter to the Contact headers of its outgoing
+ * signaling if <tt>true</tt>.
+ */
+ private boolean conferenceFocus = false;
+
+ /**
+ * The list of <tt>Call</tt>s participating in this telephony conference as
+ * an immutable <tt>List</tt> which can be exposed out of this instance
+ * without the need to make a copy. In other words, it is an unmodifiable
+ * view of {@link #mutableCalls}.
+ */
+ private List<Call> immutableCalls;
+
+ /**
+ * The indicator which determines whether the telephony conference
+ * represented by this instance is utilizing the Jitsi Videobridge
+ * server-side telephony conferencing technology.
+ */
+ private final boolean jitsiVideobridge;
+
+ /**
+ * The list of <tt>Call</tt>s participating in this telephony conference as
+ * a mutable <tt>List</tt> which should not be exposed out of this instance.
+ */
+ private List<Call> mutableCalls;
+
+ /**
+ * Initializes a new <tt>CallConference</tt> instance.
+ */
+ public CallConference()
+ {
+ this(false);
+ }
+
+ /**
+ * Initializes a new <tt>CallConference</tt> instance which is to optionally
+ * utilize the Jitsi Videobridge server-side telephony conferencing
+ * technology.
+ *
+ * @param jitsiVideobridge <tt>true</tt> if the telephony conference
+ * represented by the new instance is to utilize the Jitsi Videobridge
+ * server-side telephony conferencing technology; otherwise, <tt>false</tt>
+ */
+ public CallConference(boolean jitsiVideobridge)
+ {
+ this.jitsiVideobridge = jitsiVideobridge;
+
+ mutableCalls = new ArrayList<Call>();
+ immutableCalls = Collections.unmodifiableList(mutableCalls);
+ }
+
+ /**
+ * Adds a specific <tt>Call</tt> to the list of <tt>Call</tt>s participating
+ * in this telephony conference.
+ *
+ * @param call the <tt>Call</tt> to add to the list of <tt>Call</tt>s
+ * participating in this telephony conference
+ * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
+ * telephony conference changed as a result of the method call; otherwise,
+ * <tt>false</tt>
+ * @throws NullPointerException if <tt>call</tt> is <tt>null</tt>
+ */
+ boolean addCall(Call call)
+ {
+ if (call == null)
+ throw new NullPointerException("call");
+
+ synchronized (callsSyncRoot)
+ {
+ if (mutableCalls.contains(call))
+ return false;
+
+ /*
+ * Implement the List of Calls participating in this telephony
+ * conference as a copy-on-write storage in order to optimize the
+ * getCalls method which is likely to be executed much more often
+ * than the addCall and removeCall methods.
+ */
+ List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
+
+ if (newMutableCalls.add(call))
+ {
+ mutableCalls = newMutableCalls;
+ immutableCalls = Collections.unmodifiableList(mutableCalls);
+ }
+ else
+ return false;
+ }
+
+ callAdded(call);
+ return true;
+ }
+
+ /**
+ * Adds a <tt>CallChangeListener</tt> to the <tt>Call</tt>s participating in
+ * this telephony conference. The method is a convenience that takes on the
+ * responsibility of tracking the <tt>Call</tt>s that get added/removed
+ * to/from this telephony conference.
+ *
+ * @param listener the <tt>CallChangeListner</tt> to be added to the
+ * <tt>Call</tt>s participating in this telephony conference
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addCallChangeListener(CallChangeListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ else
+ {
+ synchronized (callChangeListeners)
+ {
+ if (!callChangeListeners.contains(listener))
+ callChangeListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Adds {@link #callPeerConferenceListener} to the <tt>CallPeer</tt>s
+ * associated with a specific <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> to whose associated <tt>CallPeer</tt>s
+ * <tt>callPeerConferenceListener</tt> is to be added
+ */
+ private void addCallPeerConferenceListener(Call call)
+ {
+ Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ callPeerIter.next().addCallPeerConferenceListener(
+ callPeerConferenceListener);
+ }
+ }
+
+ /**
+ * Adds a <tt>CallPeerConferenceListener</tt> to the <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in this telephony
+ * conference. The method is a convenience that takes on the responsibility
+ * of tracking the <tt>Call</tt>s that get added/removed to/from this
+ * telephony conference and the <tt>CallPeer</tt> that get added/removed
+ * to/from these <tt>Call</tt>s.
+ *
+ * @param listener the <tt>CallPeerConferenceListener</tt> to be added to
+ * the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating
+ * in this telephony conference
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addCallPeerConferenceListener(
+ CallPeerConferenceListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ else
+ {
+ synchronized (callPeerConferenceListeners)
+ {
+ if (!callPeerConferenceListeners.contains(listener))
+ callPeerConferenceListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
+ * been added to the list of <tt>Call</tt>s participating in this telephony
+ * conference.
+ *
+ * @param call the <tt>Call</tt> which has been added to the list of
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ protected void callAdded(Call call)
+ {
+ call.addCallChangeListener(callChangeListener);
+ addCallPeerConferenceListener(call);
+
+ /*
+ * Update the conferenceFocus state. Because the public
+ * setConferenceFocus method allows forcing a specific value on the
+ * state in question and because it does not sound right to have the
+ * adding of a Call set conferenceFocus to false, only update it if the
+ * new conferenceFocus value is true,
+ */
+ boolean conferenceFocus = isConferenceFocus(getCalls());
+
+ if (conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+
+ firePropertyChange(CALLS, null, call);
+ }
+
+ /**
+ * Notifies this <tt>CallConference</tt> that a specific <tt>Call</tt> has
+ * been removed from the list of <tt>Call</tt>s participating in this
+ * telephony conference.
+ *
+ * @param call the <tt>Call</tt> which has been removed from the list of
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ protected void callRemoved(Call call)
+ {
+ call.removeCallChangeListener(callChangeListener);
+ removeCallPeerConferenceListener(call);
+
+ /*
+ * Update the conferenceFocus state. Following the line of thinking
+ * expressed in the callAdded method, only update it if the new
+ * conferenceFocus value is false.
+ */
+ boolean conferenceFocus = isConferenceFocus(getCalls());
+
+ if (!conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+
+ firePropertyChange(CALLS, call, null);
+ }
+
+ /**
+ * Notifies this telephony conference that the <tt>CallState</tt> of a
+ * <tt>Call</tt> has changed.
+ *
+ * @param ev a <tt>CallChangeEvent</tt> which specifies the <tt>Call</tt>
+ * which had its <tt>CallState</tt> changed and the old and new
+ * <tt>CallState</tt>s of that <tt>Call</tt>
+ */
+ private void callStateChanged(CallChangeEvent ev)
+ {
+ Call call = ev.getSourceCall();
+
+ if (containsCall(call))
+ {
+ try
+ {
+ // Forward the CallChangeEvent to the callChangeListeners.
+ for (CallChangeListener l : getCallChangeListeners())
+ l.callStateChanged(ev);
+ }
+ finally
+ {
+ if (CallChangeEvent.CALL_STATE_CHANGE
+ .equals(ev.getPropertyName())
+ && CallState.CALL_ENDED.equals(ev.getNewValue()))
+ {
+ /*
+ * Should not be vital because Call will remove itself.
+ * Anyway, do it for the sake of completeness.
+ */
+ removeCall(call);
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies this <tt>CallConference</tt> that the value of its
+ * <tt>conferenceFocus</tt> property has changed from a specific old value
+ * to a specific new value.
+ *
+ * @param oldValue the value of the <tt>conferenceFocus</tt> property of
+ * this instance before the change
+ * @param newValue the value of the <tt>conferenceFocus</tt> property of
+ * this instance after the change
+ */
+ protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
+ {
+ firePropertyChange(Call.CONFERENCE_FOCUS, oldValue, newValue);
+ }
+
+ /**
+ * Determines whether a specific <tt>Call</tt> is participating in this
+ * telephony conference.
+ *
+ * @param call the <tt>Call</tt> which is to be checked whether it is
+ * participating in this telephony conference
+ * @return <tt>true</tt> if the specified <tt>call</tt> is participating in
+ * this telephony conference
+ */
+ public boolean containsCall(Call call)
+ {
+ synchronized (callsSyncRoot)
+ {
+ return mutableCalls.contains(call);
+ }
+ }
+
+ /**
+ * Gets the list of <tt>CallChangeListener</tt>s added to the <tt>Call</tt>s
+ * participating in this telephony conference via
+ * {@link #addCallChangeListener(CallChangeListener)}.
+ *
+ * @return the list of <tt>CallChangeListener</tt>s added to the
+ * <tt>Call</tt>s participating in this telephony conference via
+ * {@link #addCallChangeListener(CallChangeListener)}
+ */
+ private CallChangeListener[] getCallChangeListeners()
+ {
+ synchronized (callChangeListeners)
+ {
+ return
+ callChangeListeners.toArray(
+ new CallChangeListener[callChangeListeners.size()]);
+ }
+ }
+
+ /**
+ * Gets the number of <tt>Call</tt>s that are participating in this
+ * telephony conference.
+ *
+ * @return the number of <tt>Call</tt>s that are participating in this
+ * telephony conference
+ */
+ public int getCallCount()
+ {
+ synchronized (callsSyncRoot)
+ {
+ return mutableCalls.size();
+ }
+ }
+
+ /**
+ * Gets the list of <tt>CallPeerConferenceListener</tt>s added to the
+ * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
+ * this telephony conference via
+ * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}.
+ *
+ * @return the list of <tt>CallPeerConferenceListener</tt>s added to the
+ * <tt>CallPeer</tt>s associated with the <tt>Call</tt>s participating in
+ * this telephony conference via
+ * {@link #addCallPeerConferenceListener(CallPeerConferenceListener)}
+ */
+ private CallPeerConferenceListener[] getCallPeerConferenceListeners()
+ {
+ synchronized (callPeerConferenceListeners)
+ {
+ return
+ callPeerConferenceListeners.toArray(
+ new CallPeerConferenceListener[
+ callPeerConferenceListeners.size()]);
+ }
+ }
+
+ /**
+ * Gets the number of <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @return the number of <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ public int getCallPeerCount()
+ {
+ int callPeerCount = 0;
+
+ for (Call call : getCalls())
+ callPeerCount += call.getCallPeerCount();
+ return callPeerCount;
+ }
+
+ /**
+ * Gets a list of the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @return a list of the <tt>CallPeer</tt>s associated with the
+ * <tt>Call</tt>s participating in this telephony conference
+ */
+ public List<CallPeer> getCallPeers()
+ {
+ List<CallPeer> callPeers = new ArrayList<CallPeer>();
+
+ getCallPeers(callPeers);
+ return callPeers;
+ }
+
+ /**
+ * Adds the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference into a specific <tt>List</tt>.
+ *
+ * @param callPeers a <tt>List</tt> into which the <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in this telephony
+ * conference are to be added
+ */
+ protected void getCallPeers(List<CallPeer> callPeers)
+ {
+ for (Call call : getCalls())
+ {
+ Iterator<? extends CallPeer> callPeerIt = call.getCallPeers();
+
+ while (callPeerIt.hasNext())
+ callPeers.add(callPeerIt.next());
+ }
+ }
+
+ /**
+ * Gets the list of <tt>Call</tt> participating in this telephony
+ * conference.
+ *
+ * @return the list of <tt>Call</tt>s participating in this telephony
+ * conference. An empty array of <tt>Call</tt> element type is returned if
+ * there are no <tt>Call</tt>s in this telephony conference-related state.
+ */
+ public List<Call> getCalls()
+ {
+ synchronized (callsSyncRoot)
+ {
+ return immutableCalls;
+ }
+ }
+
+ /**
+ * Determines whether the local peer/user associated with this instance and
+ * represented by the <tt>Call</tt>s participating into it is acting as a
+ * conference focus.
+ *
+ * @return <tt>true</tt> if the local peer/user associated by this instance
+ * is acting as a conference focus; otherwise, <tt>false</tt>
+ */
+ public boolean isConferenceFocus()
+ {
+ return conferenceFocus;
+ }
+
+ /**
+ * Determines whether the current state of this instance suggests that the
+ * telephony conference it represents has ended. Iterates over the
+ * <tt>Call</tt>s participating in this telephony conference and looks for a
+ * <tt>Call</tt> which is not in the {@link CallState#CALL_ENDED} state.
+ *
+ * @return <tt>true</tt> if the current state of this instance suggests that
+ * the telephony conference it represents has ended; otherwise,
+ * <tt>false</tt>
+ */
+ public boolean isEnded()
+ {
+ for (Call call : getCalls())
+ {
+ if (!CallState.CALL_ENDED.equals(call.getCallState()))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determines whether the telephony conference represented by this instance
+ * is utilizing the Jitsi Videobridge server-side telephony conferencing
+ * technology.
+ *
+ * @return <tt>true</tt> if the telephony conference represented by this
+ * instance is utilizing the Jitsi Videobridge server-side telephony
+ * conferencing technology
+ */
+ public boolean isJitsiVideobridge()
+ {
+ return jitsiVideobridge;
+ }
+
+ /**
+ * Notifies this telephony conference that a
+ * <tt>CallPeerConferenceEvent</tt> was fired by a <tt>CallPeer</tt>
+ * associated with a <tt>Call</tt> participating in this telephony
+ * conference. Forwards the specified <tt>CallPeerConferenceEvent</tt> to
+ * {@link #callPeerConferenceListeners}.
+ *
+ * @param ev the <tt>CallPeerConferenceEvent</tt> which was fired
+ */
+ private void onCallPeerConferenceEvent(CallPeerConferenceEvent ev)
+ {
+ int eventID = ev.getEventID();
+
+ for (CallPeerConferenceListener l : getCallPeerConferenceListeners())
+ {
+ switch (eventID)
+ {
+ case CallPeerConferenceEvent.CONFERENCE_FOCUS_CHANGED:
+ l.conferenceFocusChanged(ev);
+ break;
+ case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED:
+ l.conferenceMemberAdded(ev);
+ break;
+ case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED:
+ l.conferenceMemberRemoved(ev);
+ break;
+ case CallPeerConferenceEvent.CONFERENCE_MEMBER_ERROR_RECEIVED:
+ l.conferenceMemberErrorReceived(ev);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported CallPeerConferenceEvent eventID.");
+ }
+ }
+ }
+
+ /**
+ * Notifies this telephony conference about a specific
+ * <tt>CallPeerEvent</tt> i.e. that a <tt>CallPeer</tt> was either added to
+ * or removed from a <tt>Call</tt>.
+ *
+ * @param ev a <tt>CallPeerEvent</tt> which specifies the <tt>CallPeer</tt>
+ * which was added or removed and the <tt>Call</tt> to which it was added or
+ * from which is was removed
+ */
+ private void onCallPeerEvent(CallPeerEvent ev)
+ {
+ Call call = ev.getSourceCall();
+
+ if (containsCall(call))
+ {
+ /*
+ * Update the conferenceFocus state. Following the line of thinking
+ * expressed in the callAdded and callRemoved methods, only update
+ * it if the new conferenceFocus value is in accord with the
+ * expectations.
+ */
+ int eventID = ev.getEventID();
+ boolean conferenceFocus = isConferenceFocus(getCalls());
+
+ switch (eventID)
+ {
+ case CallPeerEvent.CALL_PEER_ADDED:
+ if (conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+ break;
+ case CallPeerEvent.CALL_PEER_REMOVED:
+ if (!conferenceFocus)
+ setConferenceFocus(conferenceFocus);
+ break;
+ default:
+ /*
+ * We're interested in the adding and removing of CallPeers
+ * only.
+ */
+ break;
+ }
+
+ try
+ {
+ // Forward the CallPeerEvent to the callChangeListeners.
+ for (CallChangeListener l : getCallChangeListeners())
+ {
+ switch (eventID)
+ {
+ case CallPeerEvent.CALL_PEER_ADDED:
+ l.callPeerAdded(ev);
+ break;
+ case CallPeerEvent.CALL_PEER_REMOVED:
+ l.callPeerRemoved(ev);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ finally
+ {
+ /*
+ * Add/remove the callPeerConferenceListener to/from the source
+ * CallPeer (for the purposes of the
+ * addCallPeerConferenceListener method of this CallConference).
+ */
+ CallPeer callPeer = ev.getSourceCallPeer();
+
+ switch (eventID)
+ {
+ case CallPeerEvent.CALL_PEER_ADDED:
+ callPeer.addCallPeerConferenceListener(
+ callPeerConferenceListener);
+ break;
+ case CallPeerEvent.CALL_PEER_REMOVED:
+ callPeer.removeCallPeerConferenceListener(
+ callPeerConferenceListener);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>Call</tt> from the list of <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @param call the <tt>Call</tt> to remove from the list of <tt>Call</tt>s
+ * participating in this telephony conference
+ * @return <tt>true</tt> if the list of <tt>Call</tt>s participating in this
+ * telephony conference changed as a result of the method call; otherwise,
+ * <tt>false</tt>
+ */
+ boolean removeCall(Call call)
+ {
+ if (call == null)
+ return false;
+
+ synchronized (callsSyncRoot)
+ {
+ if (!mutableCalls.contains(call))
+ return false;
+
+ /*
+ * Implement the List of Calls participating in this telephony
+ * conference as a copy-on-write storage in order to optimize the
+ * getCalls method which is likely to be executed much more often
+ * than the addCall and removeCall methods.
+ */
+ List<Call> newMutableCalls = new ArrayList<Call>(mutableCalls);
+
+ if (newMutableCalls.remove(call))
+ {
+ mutableCalls = newMutableCalls;
+ immutableCalls = Collections.unmodifiableList(mutableCalls);
+ }
+ else
+ return false;
+ }
+
+ callRemoved(call);
+ return true;
+ }
+
+ /**
+ * Removes a <tt>CallChangeListener</tt> from the <tt>Call</tt>s
+ * participating in this telephony conference.
+ *
+ * @param listener the <tt>CallChangeListener</tt> to be removed from the
+ * <tt>Call</tt>s participating in this telephony conference
+ * @see #addCallChangeListener(CallChangeListener)
+ */
+ public void removeCallChangeListener(CallChangeListener listener)
+ {
+ if (listener != null)
+ {
+ synchronized (callChangeListeners)
+ {
+ callChangeListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes {@link #callPeerConferenceListener} from the <tt>CallPeer</tt>s
+ * associated with a specific <tt>Call</tt>.
+ *
+ * @param call the <tt>Call</tt> from whose associated <tt>CallPeer</tt>s
+ * <tt>callPeerConferenceListener</tt> is to be removed
+ */
+ private void removeCallPeerConferenceListener(Call call)
+ {
+ Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
+
+ while (callPeerIter.hasNext())
+ {
+ callPeerIter.next().removeCallPeerConferenceListener(
+ callPeerConferenceListener);
+ }
+ }
+
+ /**
+ * Removes a <tt>CallPeerConferenceListener</tt> from the <tt>CallPeer</tt>s
+ * associated with the <tt>Call</tt>s participating in this telephony
+ * conference.
+ *
+ * @param listener the <tt>CallPeerConferenceListener</tt> to be removed
+ * from the <tt>CallPeer</tt>s associated with the <tt>Call</tt>s
+ * participating in this telephony conference
+ * @see #addCallPeerConferenceListener(CallPeerConferenceListener)
+ */
+ public void removeCallPeerConferenceListener(
+ CallPeerConferenceListener listener)
+ {
+ if (listener != null)
+ {
+ synchronized (callPeerConferenceListeners)
+ {
+ callPeerConferenceListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Sets the indicator which determines whether the local peer represented by
+ * this instance and the <tt>Call</tt>s participating in it is acting as a
+ * conference focus (and thus may, for example, need to send the
+ * corresponding parameters in its outgoing signaling).
+ *
+ * @param conferenceFocus <tt>true</tt> if the local peer represented by
+ * this instance and the <tt>Call</tt>s participating in it is to act as a
+ * conference focus; otherwise, <tt>false</tt>
+ */
+ public void setConferenceFocus(boolean conferenceFocus)
+ {
+ if (this.conferenceFocus != conferenceFocus)
+ {
+ boolean oldValue = isConferenceFocus();
+
+ this.conferenceFocus = conferenceFocus;
+
+ boolean newValue = isConferenceFocus();
+
+ if (oldValue != newValue)
+ conferenceFocusChanged(oldValue, newValue);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java
index 010dc16..0530b35 100644
--- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java
+++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java
@@ -1,1259 +1,1259 @@
-/*
- * 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.service.protocol;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-import net.java.sip.communicator.service.credentialsstorage.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.configuration.*;
-import org.osgi.framework.*;
-
-/**
- * The ProtocolProviderFactory is what actually creates instances of a
- * ProtocolProviderService implementation. A provider factory would register,
- * persistently store, and remove when necessary, ProtocolProviders. The way
- * things are in the SIP Communicator, a user account is represented (in a 1:1
- * relationship) by an AccountID and a ProtocolProvider. In other words - one
- * would have as many protocol providers installed in a given moment as they
- * would user account registered through the various services.
- *
- * @author Emil Ivov
- * @author Lubomir Marinov
- */
-public abstract class ProtocolProviderFactory
-{
- /**
- * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class
- * and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(ProtocolProviderFactory.class);
-
- /**
- * Then name of a property which represents a password.
- */
- public static final String PASSWORD = "PASSWORD";
-
- /**
- * The name of a property representing the name of the protocol for an
- * ProtocolProviderFactory.
- */
- public static final String PROTOCOL = "PROTOCOL_NAME";
-
- /**
- * The name of a property representing the path to protocol icons.
- */
- public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH";
-
- /**
- * The name of a property representing the path to the account icon to
- * be used in the user interface, when the protocol provider service is not
- * available.
- */
- public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH";
-
- /**
- * The name of a property which represents the AccountID of a
- * ProtocolProvider and that, together with a password is used to login
- * on the protocol network..
- */
- public static final String USER_ID = "USER_ID";
-
- /**
- * The name that should be displayed to others when we are calling or
- * writing them.
- */
- public static final String DISPLAY_NAME = "DISPLAY_NAME";
-
- /**
- * The name that should be displayed to the user on call via and chat via
- * lists.
- */
- public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME";
-
- /**
- * The name of the property under which we store protocol AccountID-s.
- */
- public static final String ACCOUNT_UID = "ACCOUNT_UID";
-
- /**
- * The name of the property under which we store protocol the address of
- * a protocol centric entity (any protocol server).
- */
- public static final String SERVER_ADDRESS = "SERVER_ADDRESS";
-
- /**
- * The name of the property under which we store the number of the port
- * where the server stored against the SERVER_ADDRESS property is expecting
- * connections to be made via this protocol.
- */
- public static final String SERVER_PORT = "SERVER_PORT";
-
- /**
- * The name of the property under which we store the name of the transport
- * protocol that needs to be used to access the server.
- */
- public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT";
-
- /**
- * The name of the property under which we store protocol the address of
- * a protocol proxy.
- */
- public static final String PROXY_ADDRESS = "PROXY_ADDRESS";
-
- /**
- * The name of the property under which we store the number of the port
- * where the proxy stored against the PROXY_ADDRESS property is expecting
- * connections to be made via this protocol.
- */
- public static final String PROXY_PORT = "PROXY_PORT";
-
- /**
- * The name of the property which defines whether proxy is auto configured
- * by the protocol by using known methods such as specific DNS queries.
- */
- public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";
-
- /**
- * The property indicating the preferred UDP and TCP
- * port to bind to for clear communications.
- */
- public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME
- = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";
-
- /**
- * The property indicating the preferred TLS (TCP)
- * port to bind to for secure communications.
- */
- public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME
- = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT";
-
- /**
- * The name of the property under which we store the the type of the proxy
- * stored against the PROXY_ADDRESS property. Exact type values depend on
- * protocols and among them are socks4, socks5, http and possibly others.
- */
- public static final String PROXY_TYPE = "PROXY_TYPE";
-
- /**
- * The name of the property under which we store the the username for the
- * proxy stored against the PROXY_ADDRESS property.
- */
- public static final String PROXY_USERNAME = "PROXY_USERNAME";
-
- /**
- * The name of the property under which we store the the authorization name
- * for the proxy stored against the PROXY_ADDRESS property.
- */
- public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";
-
- /**
- * The name of the property under which we store the password for the proxy
- * stored against the PROXY_ADDRESS property.
- */
- public static final String PROXY_PASSWORD = "PROXY_PASSWORD";
-
- /**
- * The name of the property under which we store the name of the transport
- * protocol that needs to be used to access the proxy.
- */
- public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT";
-
- /**
- * The name of the property that indicates whether loose routing should be
- * forced for all traffic in an account, rather than routing through an
- * outbound proxy which is the default for Jitsi.
- */
- public static final String FORCE_PROXY_BYPASS = "FORCE_PROXY_BYPASS";
-
- /**
- * The name of the property under which we store the user preference for a
- * transport protocol to use (i.e. tcp or udp).
- */
- public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT";
-
- /**
- * The name of the property under which we store whether we generate
- * resource values or we just use the stored one.
- */
- public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";
-
- /**
- * The name of the property under which we store resources such as the
- * jabber resource property.
- */
- public static final String RESOURCE = "RESOURCE";
-
- /**
- * The name of the property under which we store resource priority.
- */
- public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY";
-
- /**
- * The name of the property which defines that the call is encrypted by
- * default
- */
- public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";
-
- /**
- * The name of the property that indicates the encryption protocols for this
- * account.
- */
- public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";
-
- /**
- * The name of the property that indicates the status (enabed or disabled)
- * encryption protocols for this account.
- */
- public static final String ENCRYPTION_PROTOCOL_STATUS
- = "ENCRYPTION_PROTOCOL_STATUS";
-
- /**
- * The name of the property which defines if to include the ZRTP attribute
- * to SIP/SDP
- */
- public static final String DEFAULT_SIPZRTP_ATTRIBUTE =
- "DEFAULT_SIPZRTP_ATTRIBUTE";
-
- /**
- * The name of the property which defines the ID of the client TLS
- * certificate configuration entry.
- */
- public static final String CLIENT_TLS_CERTIFICATE =
- "CLIENT_TLS_CERTIFICATE";
-
- /**
- * The name of the property under which we store the boolean value
- * indicating if the user name should be automatically changed if the
- * specified name already exists. This property is meant to be used by IRC
- * implementations.
- */
- public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME";
-
- /**
- * The name of the property under which we store the boolean value
- * indicating if a password is required. Initially this property is meant to
- * be used by IRC implementations.
- */
- public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED";
-
- /**
- * The name of the property under which we store if the presence is enabled.
- */
- public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED";
-
- /**
- * The name of the property under which we store if the p2p mode for SIMPLE
- * should be forced.
- */
- public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE";
-
- /**
- * The name of the property under which we store the offline contact polling
- * period for SIMPLE.
- */
- public static final String POLLING_PERIOD = "POLLING_PERIOD";
-
- /**
- * The name of the property under which we store the chosen default
- * subscription expiration value for SIMPLE.
- */
- public static final String SUBSCRIPTION_EXPIRATION
- = "SUBSCRIPTION_EXPIRATION";
-
- /**
- * Indicates if the server address has been validated.
- */
- public static final String SERVER_ADDRESS_VALIDATED
- = "SERVER_ADDRESS_VALIDATED";
-
- /**
- * Indicates if the server settings are over
- */
- public static final String IS_SERVER_OVERRIDDEN
- = "IS_SERVER_OVERRIDDEN";
- /**
- * Indicates if the proxy address has been validated.
- */
- public static final String PROXY_ADDRESS_VALIDATED
- = "PROXY_ADDRESS_VALIDATED";
-
- /**
- * Indicates the search strategy chosen for the DICT protocole.
- */
- public static final String STRATEGY = "STRATEGY";
-
- /**
- * Indicates a protocol that would not be shown in the user interface as an
- * account.
- */
- public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN";
-
- /**
- * Indicates if the given account is the preferred account.
- */
- public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";
-
- /**
- * The name of the property that would indicate if a given account is
- * currently enabled or disabled.
- */
- public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";
-
- /**
- * Indicates if ICE should be used.
- */
- public static final String IS_USE_ICE = "ICE_ENABLED";
-
- /**
- * Indicates if Google ICE should be used.
- */
- public static final String IS_USE_GOOGLE_ICE = "GTALK_ICE_ENABLED";
-
- /**
- * Indicates if STUN server should be automatically discovered.
- */
- public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";
-
- /**
- * Indicates if default STUN server would be used if no other STUN/TURN
- * server are available.
- */
- public static final String USE_DEFAULT_STUN_SERVER
- = "USE_DEFAULT_STUN_SERVER";
-
- /**
- * The name of the boolean account property which indicates whether Jitsi
- * VideoBridge is to be used, if available and supported, for conference
- * calls.
- */
- public static final String USE_JITSI_VIDEO_BRIDGE
- = "USE_JITSI_VIDEO_BRIDGE";
-
- /**
- * The property name prefix for all stun server properties. We generally use
- * this prefix in conjunction with an index which is how we store multiple
- * servers.
- */
- public static final String STUN_PREFIX = "STUN";
-
- /**
- * The base property name for address of additional STUN servers specified.
- */
- public static final String STUN_ADDRESS = "ADDRESS";
-
- /**
- * The base property name for port of additional STUN servers specified.
- */
- public static final String STUN_PORT = "PORT";
-
- /**
- * The base property name for username of additional STUN servers specified.
- */
- public static final String STUN_USERNAME = "USERNAME";
-
- /**
- * The base property name for password of additional STUN servers specified.
- */
- public static final String STUN_PASSWORD = "PASSWORD";
-
- /**
- * The base property name for the turn supported property of additional
- * STUN servers specified.
- */
- public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";
-
- /**
- * Indicates if JingleNodes should be used with ICE.
- */
- public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";
-
- /**
- * Indicates if JingleNodes should be used with ICE.
- */
- public static final String AUTO_DISCOVER_JINGLE_NODES
- = "AUTO_DISCOVER_JINGLE_NODES";
-
- /**
- * Indicates if JingleNodes should use buddies to search for nodes.
- */
- public static final String JINGLE_NODES_SEARCH_BUDDIES
- = "JINGLE_NODES_SEARCH_BUDDIES";
-
- /**
- * Indicates if UPnP should be used with ICE.
- */
- public static final String IS_USE_UPNP = "UPNP_ENABLED";
-
- /**
- * Indicates if we allow non-TLS connection.
- */
- public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";
-
- /**
- * Enable notifications for new voicemail messages.
- */
- public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";
-
- /**
- * Address used to reach voicemail box, by services able to
- * subscribe for voicemail new messages notifications.
- */
- public static final String VOICEMAIL_URI = "VOICEMAIL_URI";
-
- /**
- * Address used to call to hear your messages stored on the server
- * for your voicemail.
- */
- public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";
-
- /**
- * Indicates if calling is disabled for a certain account.
- */
- public static final String IS_CALLING_DISABLED_FOR_ACCOUNT
- = "CALLING_DISABLED";
-
- /**
- * Indicates if desktop streaming/sharing is disabled for a certain account.
- */
- public static final String IS_DESKTOP_STREAMING_DISABLED
- = "DESKTOP_STREAMING_DISABLED";
-
- /**
- * The sms default server address.
- */
- public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";
-
- /**
- * Keep-alive method used by the protocol.
- */
- public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";
-
- /**
- * The interval for keep-alives if any.
- */
- public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
-
- /**
- * The name of the property holding DTMF method.
- */
- public static final String DTMF_METHOD = "DTMF_METHOD";
-
- /**
- * The minimal DTMF tone duration.
- */
- public static final String DTMF_MINIMAL_TONE_DURATION
- = "DTMF_MINIMAL_TONE_DURATION";
-
- /**
- * Paranoia mode when turned on requires all calls to be secure and
- * indicated as such.
- */
- public static final String MODE_PARANOIA = "MODE_PARANOIA";
-
- /**
- * The name of the "override encodings" property
- */
- public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";
-
- /**
- * The prefix used to store account encoding properties
- */
- public static final String ENCODING_PROP_PREFIX = "Encodings";
-
- /**
- * An account property to provide a connected account to check for
- * its status. Used when the current provider need to reject calls
- * but is missing presence operation set and need to check other
- * provider for status.
- */
- public static final String CUSAX_PROVIDER_ACCOUNT_PROP
- = "cusax.XMPP_ACCOUNT_ID";
-
- /**
- * The <code>BundleContext</code> containing (or to contain) the service
- * registration of this factory.
- */
- private final BundleContext bundleContext;
-
- /**
- * The name of the protocol this factory registers its
- * <code>ProtocolProviderService</code>s with and to be placed in the
- * properties of the accounts created by this factory.
- */
- private final String protocolName;
-
- /**
- * The table that we store our accounts in.
- * <p>
- * TODO Synchronize the access to the field which may in turn be better
- * achieved by also hiding it from protected into private access.
- * </p>
- */
- protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts
- = new Hashtable<AccountID, ServiceRegistration>();
-
- /**
- * The name of the property that indicates the AVP type.
- * <ul>
- * <li>{@link #SAVP_OFF}</li>
- * <li>{@link #SAVP_MANDATORY}</li>
- * <li>{@link #SAVP_OPTIONAL}</li>
- * </ul>
- */
- public static final String SAVP_OPTION = "SAVP_OPTION";
-
- /**
- * Always use RTP/AVP
- */
- public static final int SAVP_OFF = 0;
-
- /**
- * Always use RTP/SAVP
- */
- public static final int SAVP_MANDATORY = 1;
-
- /**
- * Sends two media description, with RTP/SAVP being first.
- */
- public static final int SAVP_OPTIONAL = 2;
-
- /**
- * The name of the property that defines the enabled SDES cipher suites.
- * Enabled suites are listed as CSV by their RFC name.
- */
- public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";
-
- /**
- * Creates a new <tt>ProtocolProviderFactory</tt>.
- *
- * @param bundleContext the bundle context reference of the service
- * @param protocolName the name of the protocol
- */
- protected ProtocolProviderFactory(BundleContext bundleContext,
- String protocolName)
- {
- this.bundleContext = bundleContext;
- this.protocolName = protocolName;
- }
-
- /**
- * Gets the <code>BundleContext</code> containing (or to contain) the
- * service registration of this factory.
- *
- * @return the <code>BundleContext</code> containing (or to contain) the
- * service registration of this factory
- */
- public BundleContext getBundleContext()
- {
- return bundleContext;
- }
-
- /**
- * Initializes and creates an account corresponding to the specified
- * accountProperties and registers the resulting ProtocolProvider in the
- * <tt>context</tt> BundleContext parameter. Note that account
- * registration is persistent and accounts that are registered during
- * a particular sip-communicator session would be automatically reloaded
- * during all following sessions until they are removed through the
- * removeAccount method.
- *
- * @param userID the user identifier uniquely representing the newly
- * created account within the protocol namespace.
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- * @return the AccountID of the newly created account.
- * @throws java.lang.IllegalArgumentException if userID does not correspond
- * to an identifier in the context of the underlying protocol or if
- * accountProperties does not contain a complete set of account installation
- * properties.
- * @throws java.lang.IllegalStateException if the account has already been
- * installed.
- * @throws java.lang.NullPointerException if any of the arguments is null.
- */
- public abstract AccountID installAccount(String userID,
- Map<String, String> accountProperties)
- throws IllegalArgumentException,
- IllegalStateException,
- NullPointerException;
-
-
- /**
- * Modifies the account corresponding to the specified accountID. This
- * method is meant to be used to change properties of already existing
- * accounts. Note that if the given accountID doesn't correspond to any
- * registered account this method would do nothing.
- *
- * @param protocolProvider the protocol provider service corresponding to
- * the modified account.
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- *
- * @throws java.lang.NullPointerException if any of the arguments is null.
- */
- public abstract void modifyAccount(
- ProtocolProviderService protocolProvider,
- Map<String, String> accountProperties)
- throws NullPointerException;
-
- /**
- * Returns a copy of the list containing the <tt>AccountID</tt>s of all
- * accounts currently registered in this protocol provider.
- * @return a copy of the list containing the <tt>AccountID</tt>s of all
- * accounts currently registered in this protocol provider.
- */
- public ArrayList<AccountID> getRegisteredAccounts()
- {
- synchronized (registeredAccounts)
- {
- return new ArrayList<AccountID>(registeredAccounts.keySet());
- }
- }
-
- /**
- * Returns the ServiceReference for the protocol provider corresponding to
- * the specified accountID or null if the accountID is unknown.
- * @param accountID the accountID of the protocol provider we'd like to get
- * @return a ServiceReference object to the protocol provider with the
- * specified account id and null if the account id is unknown to the
- * provider factory.
- */
- public ServiceReference getProviderForAccount(AccountID accountID)
- {
- ServiceRegistration registration;
-
- synchronized (registeredAccounts)
- {
- registration =
- registeredAccounts.get(accountID);
- }
-
- try
- {
- return (registration == null) ? null : registration.getReference();
- }
- catch (IllegalStateException ise)
- {
- synchronized (registeredAccounts)
- {
- registeredAccounts.remove(accountID);
- }
- }
-
- return null;
- }
-
- /**
- * Removes the specified account from the list of accounts that this
- * provider factory is handling. If the specified accountID is unknown to
- * the ProtocolProviderFactory, the call has no effect and false is
- * returned. This method is persistent in nature and once called the account
- * corresponding to the specified ID will not be loaded during future runs
- * of the project.
- *
- * @param accountID the ID of the account to remove.
- * @return true if an account with the specified ID existed and was removed
- * and false otherwise.
- */
- public boolean uninstallAccount(AccountID accountID)
- {
- // Unregister the protocol provider.
- ServiceReference serRef = getProviderForAccount(accountID);
-
- boolean wasAccountExisting = false;
-
- // If the protocol provider service is registered, first unregister the
- // service.
- if (serRef != null)
- {
- BundleContext bundleContext = getBundleContext();
- ProtocolProviderService protocolProvider =
- (ProtocolProviderService) bundleContext.getService(serRef);
-
- try
- {
- protocolProvider.unregister();
- }
- catch (OperationFailedException ex)
- {
- logger
- .error("Failed to unregister protocol provider for account: "
- + accountID + " caused by: " + ex);
- }
- }
-
- ServiceRegistration registration;
-
- synchronized (registeredAccounts)
- {
- registration = registeredAccounts.remove(accountID);
- }
-
- // first remove the stored account so when PP is unregistered we can
- // distinguish between deleted or just disabled account
- wasAccountExisting = removeStoredAccount(accountID);
-
- if (registration != null)
- {
- // Kill the service.
- registration.unregister();
- }
-
- return wasAccountExisting;
- }
-
- /**
- * The method stores the specified account in the configuration service
- * under the package name of the source factory. The restore and remove
- * account methods are to be used to obtain access to and control the stored
- * accounts.
- * <p>
- * In order to store all account properties, the method would create an
- * entry in the configuration service corresponding (beginning with) the
- * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
- * (e.g. the current miliseconds.)
- * </p>
- *
- * @param accountID the AccountID corresponding to the account that we would
- * like to store.
- */
- protected void storeAccount(AccountID accountID)
- {
- this.storeAccount(accountID, true);
- }
-
- /**
- * The method stores the specified account in the configuration service
- * under the package name of the source factory. The restore and remove
- * account methods are to be used to obtain access to and control the stored
- * accounts.
- * <p>
- * In order to store all account properties, the method would create an
- * entry in the configuration service corresponding (beginning with) the
- * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
- * (e.g. the current miliseconds.)
- * </p>
- *
- * @param accountID the AccountID corresponding to the account that we would
- * like to store.
- * @param isModification if <tt>false</tt> there must be no such already
- * loaded account, it <tt>true</tt> ist modification of an existing account.
- * Usually we use this method with <tt>false</tt> in method installAccount
- * and with <tt>true</tt> or the overridden method in method
- * modifyAccount.
- */
- protected void storeAccount(AccountID accountID, boolean isModification)
- {
- if(!isModification
- && getAccountManager().getStoredAccounts().contains(accountID))
- {
- throw new IllegalStateException(
- "An account for id " + accountID.getUserID()
- + " was already loaded!");
- }
-
- try
- {
- getAccountManager().storeAccount(this, accountID);
- }
- catch (OperationFailedException ofex)
- {
- throw new UndeclaredThrowableException(ofex);
- }
- }
-
- /**
- * Saves the password for the specified account after scrambling it a bit so
- * that it is not visible from first sight. (The method remains highly
- * insecure).
- *
- * @param accountID the AccountID for the account whose password we're
- * storing
- * @param password the password itself
- *
- * @throws IllegalArgumentException if no account corresponding to
- * <code>accountID</code> has been previously stored
- */
- public void storePassword(AccountID accountID, String password)
- throws IllegalArgumentException
- {
- try
- {
- storePassword(getBundleContext(), accountID, password);
- }
- catch (OperationFailedException ofex)
- {
- throw new UndeclaredThrowableException(ofex);
- }
- }
-
- /**
- * Saves the password for the specified account after scrambling it a bit
- * so that it is not visible from first sight (Method remains highly
- * insecure).
- * <p>
- * TODO Delegate the implementation to {@link AccountManager} because it
- * knows the format in which the password (among the other account
- * properties) is to be saved.
- * </p>
- *
- * @param bundleContext a currently valid bundle context.
- * @param accountID the <tt>AccountID</tt> of the account whose password is
- * to be stored
- * @param password the password to be stored
- *
- * @throws IllegalArgumentException if no account corresponding to
- * <tt>accountID</tt> has been previously stored.
- * @throws OperationFailedException if anything goes wrong while storing the
- * specified <tt>password</tt>
- */
- protected void storePassword(BundleContext bundleContext,
- AccountID accountID,
- String password)
- throws IllegalArgumentException,
- OperationFailedException
- {
- String accountPrefix
- = findAccountPrefix(
- bundleContext,
- accountID,
- getFactoryImplPackageName());
-
- if (accountPrefix == null)
- {
- throw
- new IllegalArgumentException(
- "No previous records found for account ID: "
- + accountID.getAccountUniqueID()
- + " in package"
- + getFactoryImplPackageName());
- }
-
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- if (!credentialsStorage.storePassword(accountPrefix, password))
- {
- throw
- new OperationFailedException(
- "CredentialsStorageService failed to storePassword",
- OperationFailedException.GENERAL_ERROR);
- }
-
- // Update password property also in the AccountID
- // to prevent it from being removed during account reload
- // in some cases.
- accountID.setPassword(password);
-
- }
-
- /**
- * Returns the password last saved for the specified account.
- *
- * @param accountID the AccountID for the account whose password we're
- * looking for
- *
- * @return a String containing the password for the specified accountID
- */
- public String loadPassword(AccountID accountID)
- {
- return loadPassword(getBundleContext(), accountID);
- }
-
- /**
- * Returns the password last saved for the specified account.
- * <p>
- * TODO Delegate the implementation to {@link AccountManager} because it
- * knows the format in which the password (among the other account
- * properties) was saved.
- * </p>
- *
- * @param bundleContext a currently valid bundle context.
- * @param accountID the AccountID for the account whose password we're
- * looking for..
- *
- * @return a String containing the password for the specified accountID.
- */
- protected String loadPassword(BundleContext bundleContext,
- AccountID accountID)
- {
- String accountPrefix = findAccountPrefix(
- bundleContext, accountID, getFactoryImplPackageName());
-
- if (accountPrefix == null)
- return null;
-
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- return credentialsStorage.loadPassword(accountPrefix);
- }
-
- /**
- * Initializes and creates an account corresponding to the specified
- * accountProperties and registers the resulting ProtocolProvider in the
- * <tt>context</tt> BundleContext parameter. This method has a persistent
- * effect. Once created the resulting account will remain installed until
- * removed through the uninstallAccount method.
- *
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- * @return the AccountID of the newly loaded account
- */
- public AccountID loadAccount(Map<String, String> accountProperties)
- {
- AccountID accountID = createAccount(accountProperties);
-
- loadAccount(accountID);
-
- return accountID;
- }
-
- /**
- * Creates a protocol provider for the given <tt>accountID</tt> and
- * registers it in the bundle context. This method has a persistent
- * effect. Once created the resulting account will remain installed until
- * removed through the uninstallAccount method.
- *
- * @param accountID the account identifier
- * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is
- * successfully loaded, otherwise returns <tt>false</tt>
- */
- public boolean loadAccount(AccountID accountID)
- {
- // Need to obtain the original user id property, instead of calling
- // accountID.getUserID(), because this method could return a modified
- // version of the user id property.
- String userID = accountID
- .getAccountPropertyString(ProtocolProviderFactory.USER_ID);
-
- ProtocolProviderService service = createService(userID, accountID);
-
- Dictionary<String, String> properties = new Hashtable<String, String>();
- properties.put(PROTOCOL, protocolName);
- properties.put(USER_ID, userID);
-
- ServiceRegistration serviceRegistration =
- bundleContext.registerService(ProtocolProviderService.class
- .getName(), service, properties);
-
- if (serviceRegistration == null)
- return false;
- else
- {
- synchronized (registeredAccounts)
- {
- registeredAccounts.put(accountID, serviceRegistration);
- }
- return true;
- }
- }
-
- /**
- * Unloads the account corresponding to the given <tt>accountID</tt>.
- * Unregisters the corresponding protocol provider, but keeps the account in
- * contrast to the uninstallAccount method.
- *
- * @param accountID the account identifier
- * @return true if an account with the specified ID existed and was unloaded
- * and false otherwise.
- */
- public boolean unloadAccount(AccountID accountID)
- {
- // Unregister the protocol provider.
- ServiceReference serRef = getProviderForAccount(accountID);
-
- if (serRef == null)
- {
- return false;
- }
-
- BundleContext bundleContext = getBundleContext();
- ProtocolProviderService protocolProvider =
- (ProtocolProviderService) bundleContext.getService(serRef);
-
- try
- {
- protocolProvider.unregister();
- }
- catch (OperationFailedException ex)
- {
- logger
- .error("Failed to unregister protocol provider for account : "
- + accountID + " caused by: " + ex);
- }
-
- ServiceRegistration registration;
-
- synchronized (registeredAccounts)
- {
- registration = registeredAccounts.remove(accountID);
- }
- if (registration == null)
- {
- return false;
- }
-
- // Kill the service.
- registration.unregister();
-
- return true;
- }
-
- /**
- * Initializes and creates an account corresponding to the specified
- * accountProperties.
- *
- * @param accountProperties a set of protocol (or implementation) specific
- * properties defining the new account.
- * @return the AccountID of the newly created account
- */
- public AccountID createAccount(Map<String, String> accountProperties)
- {
- BundleContext bundleContext = getBundleContext();
- if (bundleContext == null)
- throw new NullPointerException(
- "The specified BundleContext was null");
-
- if (accountProperties == null)
- throw new NullPointerException(
- "The specified property map was null");
-
- String userID = accountProperties.get(USER_ID);
- if (userID == null)
- throw new NullPointerException(
- "The account properties contained no user id.");
-
- String protocolName = getProtocolName();
- if (!accountProperties.containsKey(PROTOCOL))
- accountProperties.put(PROTOCOL, protocolName);
-
- return createAccountID(userID, accountProperties);
- }
-
- /**
- * Creates a new <code>AccountID</code> instance with a specific user ID to
- * represent a given set of account properties.
- * <p>
- * The method is a pure factory allowing implementers to specify the runtime
- * type of the created <code>AccountID</code> and customize the instance.
- * The returned <code>AccountID</code> will later be associated with a
- * <code>ProtocolProviderService</code> by the caller (e.g. using
- * {@link #createService(String, AccountID)}).
- * </p>
- *
- * @param userID the user ID of the new instance
- * @param accountProperties the set of properties to be represented by the
- * new instance
- * @return a new <code>AccountID</code> instance with the specified user ID
- * representing the given set of account properties
- */
- protected abstract AccountID createAccountID(
- String userID, Map<String, String> accountProperties);
-
- /**
- * Gets the name of the protocol this factory registers its
- * <code>ProtocolProviderService</code>s with and to be placed in the
- * properties of the accounts created by this factory.
- *
- * @return the name of the protocol this factory registers its
- * <code>ProtocolProviderService</code>s with and to be placed in
- * the properties of the accounts created by this factory
- */
- public String getProtocolName()
- {
- return protocolName;
- }
-
- /**
- * Initializes a new <code>ProtocolProviderService</code> instance with a
- * specific user ID to represent a specific <code>AccountID</code>.
- * <p>
- * The method is a pure factory allowing implementers to specify the runtime
- * type of the created <code>ProtocolProviderService</code> and customize
- * the instance. The caller will later register the returned service with
- * the <code>BundleContext</code> of this factory.
- * </p>
- *
- * @param userID the user ID to initialize the new instance with
- * @param accountID the <code>AccountID</code> to be represented by the new
- * instance
- * @return a new <code>ProtocolProviderService</code> instance with the
- * specific user ID representing the specified
- * <code>AccountID</code>
- */
- protected abstract ProtocolProviderService createService(String userID,
- AccountID accountID);
-
- /**
- * Removes the account with <tt>accountID</tt> from the set of accounts
- * that are persistently stored inside the configuration service.
- *
- * @param accountID the AccountID of the account to remove.
- *
- * @return true if an account has been removed and false otherwise.
- */
- protected boolean removeStoredAccount(AccountID accountID)
- {
- return getAccountManager().removeStoredAccount(this, accountID);
- }
-
- /**
- * Returns the prefix for all persistently stored properties of the account
- * with the specified id.
- * @param bundleContext a currently valid bundle context.
- * @param accountID the AccountID of the account whose properties we're
- * looking for.
- * @param sourcePackageName a String containing the package name of the
- * concrete factory class that extends us.
- * @return a String indicating the ConfigurationService property name
- * prefix under which all account properties are stored or null if no
- * account corresponding to the specified id was found.
- */
- public static String findAccountPrefix(BundleContext bundleContext,
- AccountID accountID,
- String sourcePackageName)
- {
- ServiceReference confReference
- = bundleContext.getServiceReference(
- ConfigurationService.class.getName());
- ConfigurationService configurationService
- = (ConfigurationService) bundleContext.getService(confReference);
-
- //first retrieve all accounts that we've registered
- List<String> storedAccounts =
- configurationService.getPropertyNamesByPrefix(sourcePackageName,
- true);
-
- //find an account with the corresponding id.
- for (String accountRootPropertyName : storedAccounts)
- {
- //unregister the account in the configuration service.
- //all the properties must have been registered in the following
- //hierarchy:
- //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
- String accountUID = configurationService.getString(
- accountRootPropertyName //node id
- + "." + ACCOUNT_UID); // propname
-
- if (accountID.getAccountUniqueID().equals(accountUID))
- {
- return accountRootPropertyName;
- }
- }
- return null;
- }
-
- /**
- * Returns the name of the package that we're currently running in (i.e.
- * the name of the package containing the proto factory that extends us).
- *
- * @return a String containing the package name of the concrete factory
- * class that extends us.
- */
- private String getFactoryImplPackageName()
- {
- String className = getClass().getName();
-
- return className.substring(0, className.lastIndexOf('.'));
- }
-
- /**
- * Prepares the factory for bundle shutdown.
- */
- public void stop()
- {
- if (logger.isTraceEnabled())
- logger.trace("Preparing to stop all protocol providers of" + this);
-
- synchronized (registeredAccounts)
- {
- for (Enumeration<ServiceRegistration> registrations =
- registeredAccounts.elements(); registrations.hasMoreElements();)
- {
- ServiceRegistration reg = registrations.nextElement();
-
- stop(reg);
-
- reg.unregister();
- }
-
- registeredAccounts.clear();
- }
- }
-
- /**
- * Shuts down the <code>ProtocolProviderService</code> representing an
- * account registered with this factory.
- *
- * @param registeredAccount the <code>ServiceRegistration</code> of the
- * <code>ProtocolProviderService</code> representing an account
- * registered with this factory
- */
- protected void stop(ServiceRegistration registeredAccount)
- {
- ProtocolProviderService protocolProviderService =
- (ProtocolProviderService) getBundleContext().getService(
- registeredAccount.getReference());
-
- protocolProviderService.shutdown();
- }
-
- /**
- * Get the <tt>AccountManager</tt> of the protocol.
- *
- * @return <tt>AccountManager</tt> of the protocol
- */
- private AccountManager getAccountManager()
- {
- BundleContext bundleContext = getBundleContext();
- ServiceReference serviceReference =
- bundleContext.getServiceReference(AccountManager.class.getName());
-
- return (AccountManager) bundleContext.getService(serviceReference);
- }
-
-
- /**
- * Finds registered <tt>ProtocolProviderFactory</tt> for given
- * <tt>protocolName</tt>.
- * @param bundleContext the OSGI bundle context that will be used.
- * @param protocolName the protocol name.
- * @return Registered <tt>ProtocolProviderFactory</tt> for given protocol
- * name or <tt>null</tt> if no provider was found.
- */
- static public ProtocolProviderFactory getProtocolProviderFactory(
- BundleContext bundleContext,
- String protocolName)
- {
- ServiceReference[] serRefs = null;
-
- String osgiFilter =
- "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
-
- try
- {
- serRefs =
- bundleContext.getServiceReferences(
- ProtocolProviderFactory.class.getName(), osgiFilter);
- }
- catch (InvalidSyntaxException ex)
- {
- logger.error(ex);
- return null;
- }
-
- return (ProtocolProviderFactory) bundleContext.getService(serRefs[0]);
- }
-}
+/*
+ * 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.service.protocol;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import net.java.sip.communicator.service.credentialsstorage.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.configuration.*;
+import org.osgi.framework.*;
+
+/**
+ * The ProtocolProviderFactory is what actually creates instances of a
+ * ProtocolProviderService implementation. A provider factory would register,
+ * persistently store, and remove when necessary, ProtocolProviders. The way
+ * things are in the SIP Communicator, a user account is represented (in a 1:1
+ * relationship) by an AccountID and a ProtocolProvider. In other words - one
+ * would have as many protocol providers installed in a given moment as they
+ * would user account registered through the various services.
+ *
+ * @author Emil Ivov
+ * @author Lubomir Marinov
+ */
+public abstract class ProtocolProviderFactory
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class
+ * and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(ProtocolProviderFactory.class);
+
+ /**
+ * Then name of a property which represents a password.
+ */
+ public static final String PASSWORD = "PASSWORD";
+
+ /**
+ * The name of a property representing the name of the protocol for an
+ * ProtocolProviderFactory.
+ */
+ public static final String PROTOCOL = "PROTOCOL_NAME";
+
+ /**
+ * The name of a property representing the path to protocol icons.
+ */
+ public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH";
+
+ /**
+ * The name of a property representing the path to the account icon to
+ * be used in the user interface, when the protocol provider service is not
+ * available.
+ */
+ public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH";
+
+ /**
+ * The name of a property which represents the AccountID of a
+ * ProtocolProvider and that, together with a password is used to login
+ * on the protocol network..
+ */
+ public static final String USER_ID = "USER_ID";
+
+ /**
+ * The name that should be displayed to others when we are calling or
+ * writing them.
+ */
+ public static final String DISPLAY_NAME = "DISPLAY_NAME";
+
+ /**
+ * The name that should be displayed to the user on call via and chat via
+ * lists.
+ */
+ public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME";
+
+ /**
+ * The name of the property under which we store protocol AccountID-s.
+ */
+ public static final String ACCOUNT_UID = "ACCOUNT_UID";
+
+ /**
+ * The name of the property under which we store protocol the address of
+ * a protocol centric entity (any protocol server).
+ */
+ public static final String SERVER_ADDRESS = "SERVER_ADDRESS";
+
+ /**
+ * The name of the property under which we store the number of the port
+ * where the server stored against the SERVER_ADDRESS property is expecting
+ * connections to be made via this protocol.
+ */
+ public static final String SERVER_PORT = "SERVER_PORT";
+
+ /**
+ * The name of the property under which we store the name of the transport
+ * protocol that needs to be used to access the server.
+ */
+ public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT";
+
+ /**
+ * The name of the property under which we store protocol the address of
+ * a protocol proxy.
+ */
+ public static final String PROXY_ADDRESS = "PROXY_ADDRESS";
+
+ /**
+ * The name of the property under which we store the number of the port
+ * where the proxy stored against the PROXY_ADDRESS property is expecting
+ * connections to be made via this protocol.
+ */
+ public static final String PROXY_PORT = "PROXY_PORT";
+
+ /**
+ * The name of the property which defines whether proxy is auto configured
+ * by the protocol by using known methods such as specific DNS queries.
+ */
+ public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";
+
+ /**
+ * The property indicating the preferred UDP and TCP
+ * port to bind to for clear communications.
+ */
+ public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME
+ = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";
+
+ /**
+ * The property indicating the preferred TLS (TCP)
+ * port to bind to for secure communications.
+ */
+ public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME
+ = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT";
+
+ /**
+ * The name of the property under which we store the the type of the proxy
+ * stored against the PROXY_ADDRESS property. Exact type values depend on
+ * protocols and among them are socks4, socks5, http and possibly others.
+ */
+ public static final String PROXY_TYPE = "PROXY_TYPE";
+
+ /**
+ * The name of the property under which we store the the username for the
+ * proxy stored against the PROXY_ADDRESS property.
+ */
+ public static final String PROXY_USERNAME = "PROXY_USERNAME";
+
+ /**
+ * The name of the property under which we store the the authorization name
+ * for the proxy stored against the PROXY_ADDRESS property.
+ */
+ public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";
+
+ /**
+ * The name of the property under which we store the password for the proxy
+ * stored against the PROXY_ADDRESS property.
+ */
+ public static final String PROXY_PASSWORD = "PROXY_PASSWORD";
+
+ /**
+ * The name of the property under which we store the name of the transport
+ * protocol that needs to be used to access the proxy.
+ */
+ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT";
+
+ /**
+ * The name of the property that indicates whether loose routing should be
+ * forced for all traffic in an account, rather than routing through an
+ * outbound proxy which is the default for Jitsi.
+ */
+ public static final String FORCE_PROXY_BYPASS = "FORCE_PROXY_BYPASS";
+
+ /**
+ * The name of the property under which we store the user preference for a
+ * transport protocol to use (i.e. tcp or udp).
+ */
+ public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT";
+
+ /**
+ * The name of the property under which we store whether we generate
+ * resource values or we just use the stored one.
+ */
+ public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";
+
+ /**
+ * The name of the property under which we store resources such as the
+ * jabber resource property.
+ */
+ public static final String RESOURCE = "RESOURCE";
+
+ /**
+ * The name of the property under which we store resource priority.
+ */
+ public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY";
+
+ /**
+ * The name of the property which defines that the call is encrypted by
+ * default
+ */
+ public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";
+
+ /**
+ * The name of the property that indicates the encryption protocols for this
+ * account.
+ */
+ public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";
+
+ /**
+ * The name of the property that indicates the status (enabed or disabled)
+ * encryption protocols for this account.
+ */
+ public static final String ENCRYPTION_PROTOCOL_STATUS
+ = "ENCRYPTION_PROTOCOL_STATUS";
+
+ /**
+ * The name of the property which defines if to include the ZRTP attribute
+ * to SIP/SDP
+ */
+ public static final String DEFAULT_SIPZRTP_ATTRIBUTE =
+ "DEFAULT_SIPZRTP_ATTRIBUTE";
+
+ /**
+ * The name of the property which defines the ID of the client TLS
+ * certificate configuration entry.
+ */
+ public static final String CLIENT_TLS_CERTIFICATE =
+ "CLIENT_TLS_CERTIFICATE";
+
+ /**
+ * The name of the property under which we store the boolean value
+ * indicating if the user name should be automatically changed if the
+ * specified name already exists. This property is meant to be used by IRC
+ * implementations.
+ */
+ public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME";
+
+ /**
+ * The name of the property under which we store the boolean value
+ * indicating if a password is required. Initially this property is meant to
+ * be used by IRC implementations.
+ */
+ public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED";
+
+ /**
+ * The name of the property under which we store if the presence is enabled.
+ */
+ public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED";
+
+ /**
+ * The name of the property under which we store if the p2p mode for SIMPLE
+ * should be forced.
+ */
+ public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE";
+
+ /**
+ * The name of the property under which we store the offline contact polling
+ * period for SIMPLE.
+ */
+ public static final String POLLING_PERIOD = "POLLING_PERIOD";
+
+ /**
+ * The name of the property under which we store the chosen default
+ * subscription expiration value for SIMPLE.
+ */
+ public static final String SUBSCRIPTION_EXPIRATION
+ = "SUBSCRIPTION_EXPIRATION";
+
+ /**
+ * Indicates if the server address has been validated.
+ */
+ public static final String SERVER_ADDRESS_VALIDATED
+ = "SERVER_ADDRESS_VALIDATED";
+
+ /**
+ * Indicates if the server settings are over
+ */
+ public static final String IS_SERVER_OVERRIDDEN
+ = "IS_SERVER_OVERRIDDEN";
+ /**
+ * Indicates if the proxy address has been validated.
+ */
+ public static final String PROXY_ADDRESS_VALIDATED
+ = "PROXY_ADDRESS_VALIDATED";
+
+ /**
+ * Indicates the search strategy chosen for the DICT protocole.
+ */
+ public static final String STRATEGY = "STRATEGY";
+
+ /**
+ * Indicates a protocol that would not be shown in the user interface as an
+ * account.
+ */
+ public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN";
+
+ /**
+ * Indicates if the given account is the preferred account.
+ */
+ public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";
+
+ /**
+ * The name of the property that would indicate if a given account is
+ * currently enabled or disabled.
+ */
+ public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";
+
+ /**
+ * Indicates if ICE should be used.
+ */
+ public static final String IS_USE_ICE = "ICE_ENABLED";
+
+ /**
+ * Indicates if Google ICE should be used.
+ */
+ public static final String IS_USE_GOOGLE_ICE = "GTALK_ICE_ENABLED";
+
+ /**
+ * Indicates if STUN server should be automatically discovered.
+ */
+ public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";
+
+ /**
+ * Indicates if default STUN server would be used if no other STUN/TURN
+ * server are available.
+ */
+ public static final String USE_DEFAULT_STUN_SERVER
+ = "USE_DEFAULT_STUN_SERVER";
+
+ /**
+ * The name of the boolean account property which indicates whether Jitsi
+ * Videobridge is to be used, if available and supported, for conference
+ * calls.
+ */
+ public static final String USE_JITSI_VIDEO_BRIDGE
+ = "USE_JITSI_VIDEO_BRIDGE";
+
+ /**
+ * The property name prefix for all stun server properties. We generally use
+ * this prefix in conjunction with an index which is how we store multiple
+ * servers.
+ */
+ public static final String STUN_PREFIX = "STUN";
+
+ /**
+ * The base property name for address of additional STUN servers specified.
+ */
+ public static final String STUN_ADDRESS = "ADDRESS";
+
+ /**
+ * The base property name for port of additional STUN servers specified.
+ */
+ public static final String STUN_PORT = "PORT";
+
+ /**
+ * The base property name for username of additional STUN servers specified.
+ */
+ public static final String STUN_USERNAME = "USERNAME";
+
+ /**
+ * The base property name for password of additional STUN servers specified.
+ */
+ public static final String STUN_PASSWORD = "PASSWORD";
+
+ /**
+ * The base property name for the turn supported property of additional
+ * STUN servers specified.
+ */
+ public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";
+
+ /**
+ * Indicates if JingleNodes should be used with ICE.
+ */
+ public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";
+
+ /**
+ * Indicates if JingleNodes should be used with ICE.
+ */
+ public static final String AUTO_DISCOVER_JINGLE_NODES
+ = "AUTO_DISCOVER_JINGLE_NODES";
+
+ /**
+ * Indicates if JingleNodes should use buddies to search for nodes.
+ */
+ public static final String JINGLE_NODES_SEARCH_BUDDIES
+ = "JINGLE_NODES_SEARCH_BUDDIES";
+
+ /**
+ * Indicates if UPnP should be used with ICE.
+ */
+ public static final String IS_USE_UPNP = "UPNP_ENABLED";
+
+ /**
+ * Indicates if we allow non-TLS connection.
+ */
+ public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";
+
+ /**
+ * Enable notifications for new voicemail messages.
+ */
+ public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";
+
+ /**
+ * Address used to reach voicemail box, by services able to
+ * subscribe for voicemail new messages notifications.
+ */
+ public static final String VOICEMAIL_URI = "VOICEMAIL_URI";
+
+ /**
+ * Address used to call to hear your messages stored on the server
+ * for your voicemail.
+ */
+ public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";
+
+ /**
+ * Indicates if calling is disabled for a certain account.
+ */
+ public static final String IS_CALLING_DISABLED_FOR_ACCOUNT
+ = "CALLING_DISABLED";
+
+ /**
+ * Indicates if desktop streaming/sharing is disabled for a certain account.
+ */
+ public static final String IS_DESKTOP_STREAMING_DISABLED
+ = "DESKTOP_STREAMING_DISABLED";
+
+ /**
+ * The sms default server address.
+ */
+ public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";
+
+ /**
+ * Keep-alive method used by the protocol.
+ */
+ public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";
+
+ /**
+ * The interval for keep-alives if any.
+ */
+ public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
+
+ /**
+ * The name of the property holding DTMF method.
+ */
+ public static final String DTMF_METHOD = "DTMF_METHOD";
+
+ /**
+ * The minimal DTMF tone duration.
+ */
+ public static final String DTMF_MINIMAL_TONE_DURATION
+ = "DTMF_MINIMAL_TONE_DURATION";
+
+ /**
+ * Paranoia mode when turned on requires all calls to be secure and
+ * indicated as such.
+ */
+ public static final String MODE_PARANOIA = "MODE_PARANOIA";
+
+ /**
+ * The name of the "override encodings" property
+ */
+ public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";
+
+ /**
+ * The prefix used to store account encoding properties
+ */
+ public static final String ENCODING_PROP_PREFIX = "Encodings";
+
+ /**
+ * An account property to provide a connected account to check for
+ * its status. Used when the current provider need to reject calls
+ * but is missing presence operation set and need to check other
+ * provider for status.
+ */
+ public static final String CUSAX_PROVIDER_ACCOUNT_PROP
+ = "cusax.XMPP_ACCOUNT_ID";
+
+ /**
+ * The <code>BundleContext</code> containing (or to contain) the service
+ * registration of this factory.
+ */
+ private final BundleContext bundleContext;
+
+ /**
+ * The name of the protocol this factory registers its
+ * <code>ProtocolProviderService</code>s with and to be placed in the
+ * properties of the accounts created by this factory.
+ */
+ private final String protocolName;
+
+ /**
+ * The table that we store our accounts in.
+ * <p>
+ * TODO Synchronize the access to the field which may in turn be better
+ * achieved by also hiding it from protected into private access.
+ * </p>
+ */
+ protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts
+ = new Hashtable<AccountID, ServiceRegistration>();
+
+ /**
+ * The name of the property that indicates the AVP type.
+ * <ul>
+ * <li>{@link #SAVP_OFF}</li>
+ * <li>{@link #SAVP_MANDATORY}</li>
+ * <li>{@link #SAVP_OPTIONAL}</li>
+ * </ul>
+ */
+ public static final String SAVP_OPTION = "SAVP_OPTION";
+
+ /**
+ * Always use RTP/AVP
+ */
+ public static final int SAVP_OFF = 0;
+
+ /**
+ * Always use RTP/SAVP
+ */
+ public static final int SAVP_MANDATORY = 1;
+
+ /**
+ * Sends two media description, with RTP/SAVP being first.
+ */
+ public static final int SAVP_OPTIONAL = 2;
+
+ /**
+ * The name of the property that defines the enabled SDES cipher suites.
+ * Enabled suites are listed as CSV by their RFC name.
+ */
+ public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";
+
+ /**
+ * Creates a new <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param bundleContext the bundle context reference of the service
+ * @param protocolName the name of the protocol
+ */
+ protected ProtocolProviderFactory(BundleContext bundleContext,
+ String protocolName)
+ {
+ this.bundleContext = bundleContext;
+ this.protocolName = protocolName;
+ }
+
+ /**
+ * Gets the <code>BundleContext</code> containing (or to contain) the
+ * service registration of this factory.
+ *
+ * @return the <code>BundleContext</code> containing (or to contain) the
+ * service registration of this factory
+ */
+ public BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * <tt>context</tt> BundleContext parameter. Note that account
+ * registration is persistent and accounts that are registered during
+ * a particular sip-communicator session would be automatically reloaded
+ * during all following sessions until they are removed through the
+ * removeAccount method.
+ *
+ * @param userID the user identifier uniquely representing the newly
+ * created account within the protocol namespace.
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ * @return the AccountID of the newly created account.
+ * @throws java.lang.IllegalArgumentException if userID does not correspond
+ * to an identifier in the context of the underlying protocol or if
+ * accountProperties does not contain a complete set of account installation
+ * properties.
+ * @throws java.lang.IllegalStateException if the account has already been
+ * installed.
+ * @throws java.lang.NullPointerException if any of the arguments is null.
+ */
+ public abstract AccountID installAccount(String userID,
+ Map<String, String> accountProperties)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ NullPointerException;
+
+
+ /**
+ * Modifies the account corresponding to the specified accountID. This
+ * method is meant to be used to change properties of already existing
+ * accounts. Note that if the given accountID doesn't correspond to any
+ * registered account this method would do nothing.
+ *
+ * @param protocolProvider the protocol provider service corresponding to
+ * the modified account.
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ *
+ * @throws java.lang.NullPointerException if any of the arguments is null.
+ */
+ public abstract void modifyAccount(
+ ProtocolProviderService protocolProvider,
+ Map<String, String> accountProperties)
+ throws NullPointerException;
+
+ /**
+ * Returns a copy of the list containing the <tt>AccountID</tt>s of all
+ * accounts currently registered in this protocol provider.
+ * @return a copy of the list containing the <tt>AccountID</tt>s of all
+ * accounts currently registered in this protocol provider.
+ */
+ public ArrayList<AccountID> getRegisteredAccounts()
+ {
+ synchronized (registeredAccounts)
+ {
+ return new ArrayList<AccountID>(registeredAccounts.keySet());
+ }
+ }
+
+ /**
+ * Returns the ServiceReference for the protocol provider corresponding to
+ * the specified accountID or null if the accountID is unknown.
+ * @param accountID the accountID of the protocol provider we'd like to get
+ * @return a ServiceReference object to the protocol provider with the
+ * specified account id and null if the account id is unknown to the
+ * provider factory.
+ */
+ public ServiceReference getProviderForAccount(AccountID accountID)
+ {
+ ServiceRegistration registration;
+
+ synchronized (registeredAccounts)
+ {
+ registration =
+ registeredAccounts.get(accountID);
+ }
+
+ try
+ {
+ return (registration == null) ? null : registration.getReference();
+ }
+ catch (IllegalStateException ise)
+ {
+ synchronized (registeredAccounts)
+ {
+ registeredAccounts.remove(accountID);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Removes the specified account from the list of accounts that this
+ * provider factory is handling. If the specified accountID is unknown to
+ * the ProtocolProviderFactory, the call has no effect and false is
+ * returned. This method is persistent in nature and once called the account
+ * corresponding to the specified ID will not be loaded during future runs
+ * of the project.
+ *
+ * @param accountID the ID of the account to remove.
+ * @return true if an account with the specified ID existed and was removed
+ * and false otherwise.
+ */
+ public boolean uninstallAccount(AccountID accountID)
+ {
+ // Unregister the protocol provider.
+ ServiceReference serRef = getProviderForAccount(accountID);
+
+ boolean wasAccountExisting = false;
+
+ // If the protocol provider service is registered, first unregister the
+ // service.
+ if (serRef != null)
+ {
+ BundleContext bundleContext = getBundleContext();
+ ProtocolProviderService protocolProvider =
+ (ProtocolProviderService) bundleContext.getService(serRef);
+
+ try
+ {
+ protocolProvider.unregister();
+ }
+ catch (OperationFailedException ex)
+ {
+ logger
+ .error("Failed to unregister protocol provider for account: "
+ + accountID + " caused by: " + ex);
+ }
+ }
+
+ ServiceRegistration registration;
+
+ synchronized (registeredAccounts)
+ {
+ registration = registeredAccounts.remove(accountID);
+ }
+
+ // first remove the stored account so when PP is unregistered we can
+ // distinguish between deleted or just disabled account
+ wasAccountExisting = removeStoredAccount(accountID);
+
+ if (registration != null)
+ {
+ // Kill the service.
+ registration.unregister();
+ }
+
+ return wasAccountExisting;
+ }
+
+ /**
+ * The method stores the specified account in the configuration service
+ * under the package name of the source factory. The restore and remove
+ * account methods are to be used to obtain access to and control the stored
+ * accounts.
+ * <p>
+ * In order to store all account properties, the method would create an
+ * entry in the configuration service corresponding (beginning with) the
+ * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
+ * (e.g. the current miliseconds.)
+ * </p>
+ *
+ * @param accountID the AccountID corresponding to the account that we would
+ * like to store.
+ */
+ protected void storeAccount(AccountID accountID)
+ {
+ this.storeAccount(accountID, true);
+ }
+
+ /**
+ * The method stores the specified account in the configuration service
+ * under the package name of the source factory. The restore and remove
+ * account methods are to be used to obtain access to and control the stored
+ * accounts.
+ * <p>
+ * In order to store all account properties, the method would create an
+ * entry in the configuration service corresponding (beginning with) the
+ * <tt>sourceFactory</tt>'s package name and add to it a unique identifier
+ * (e.g. the current miliseconds.)
+ * </p>
+ *
+ * @param accountID the AccountID corresponding to the account that we would
+ * like to store.
+ * @param isModification if <tt>false</tt> there must be no such already
+ * loaded account, it <tt>true</tt> ist modification of an existing account.
+ * Usually we use this method with <tt>false</tt> in method installAccount
+ * and with <tt>true</tt> or the overridden method in method
+ * modifyAccount.
+ */
+ protected void storeAccount(AccountID accountID, boolean isModification)
+ {
+ if(!isModification
+ && getAccountManager().getStoredAccounts().contains(accountID))
+ {
+ throw new IllegalStateException(
+ "An account for id " + accountID.getUserID()
+ + " was already loaded!");
+ }
+
+ try
+ {
+ getAccountManager().storeAccount(this, accountID);
+ }
+ catch (OperationFailedException ofex)
+ {
+ throw new UndeclaredThrowableException(ofex);
+ }
+ }
+
+ /**
+ * Saves the password for the specified account after scrambling it a bit so
+ * that it is not visible from first sight. (The method remains highly
+ * insecure).
+ *
+ * @param accountID the AccountID for the account whose password we're
+ * storing
+ * @param password the password itself
+ *
+ * @throws IllegalArgumentException if no account corresponding to
+ * <code>accountID</code> has been previously stored
+ */
+ public void storePassword(AccountID accountID, String password)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ storePassword(getBundleContext(), accountID, password);
+ }
+ catch (OperationFailedException ofex)
+ {
+ throw new UndeclaredThrowableException(ofex);
+ }
+ }
+
+ /**
+ * Saves the password for the specified account after scrambling it a bit
+ * so that it is not visible from first sight (Method remains highly
+ * insecure).
+ * <p>
+ * TODO Delegate the implementation to {@link AccountManager} because it
+ * knows the format in which the password (among the other account
+ * properties) is to be saved.
+ * </p>
+ *
+ * @param bundleContext a currently valid bundle context.
+ * @param accountID the <tt>AccountID</tt> of the account whose password is
+ * to be stored
+ * @param password the password to be stored
+ *
+ * @throws IllegalArgumentException if no account corresponding to
+ * <tt>accountID</tt> has been previously stored.
+ * @throws OperationFailedException if anything goes wrong while storing the
+ * specified <tt>password</tt>
+ */
+ protected void storePassword(BundleContext bundleContext,
+ AccountID accountID,
+ String password)
+ throws IllegalArgumentException,
+ OperationFailedException
+ {
+ String accountPrefix
+ = findAccountPrefix(
+ bundleContext,
+ accountID,
+ getFactoryImplPackageName());
+
+ if (accountPrefix == null)
+ {
+ throw
+ new IllegalArgumentException(
+ "No previous records found for account ID: "
+ + accountID.getAccountUniqueID()
+ + " in package"
+ + getFactoryImplPackageName());
+ }
+
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ if (!credentialsStorage.storePassword(accountPrefix, password))
+ {
+ throw
+ new OperationFailedException(
+ "CredentialsStorageService failed to storePassword",
+ OperationFailedException.GENERAL_ERROR);
+ }
+
+ // Update password property also in the AccountID
+ // to prevent it from being removed during account reload
+ // in some cases.
+ accountID.setPassword(password);
+
+ }
+
+ /**
+ * Returns the password last saved for the specified account.
+ *
+ * @param accountID the AccountID for the account whose password we're
+ * looking for
+ *
+ * @return a String containing the password for the specified accountID
+ */
+ public String loadPassword(AccountID accountID)
+ {
+ return loadPassword(getBundleContext(), accountID);
+ }
+
+ /**
+ * Returns the password last saved for the specified account.
+ * <p>
+ * TODO Delegate the implementation to {@link AccountManager} because it
+ * knows the format in which the password (among the other account
+ * properties) was saved.
+ * </p>
+ *
+ * @param bundleContext a currently valid bundle context.
+ * @param accountID the AccountID for the account whose password we're
+ * looking for..
+ *
+ * @return a String containing the password for the specified accountID.
+ */
+ protected String loadPassword(BundleContext bundleContext,
+ AccountID accountID)
+ {
+ String accountPrefix = findAccountPrefix(
+ bundleContext, accountID, getFactoryImplPackageName());
+
+ if (accountPrefix == null)
+ return null;
+
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ return credentialsStorage.loadPassword(accountPrefix);
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * <tt>context</tt> BundleContext parameter. This method has a persistent
+ * effect. Once created the resulting account will remain installed until
+ * removed through the uninstallAccount method.
+ *
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ * @return the AccountID of the newly loaded account
+ */
+ public AccountID loadAccount(Map<String, String> accountProperties)
+ {
+ AccountID accountID = createAccount(accountProperties);
+
+ loadAccount(accountID);
+
+ return accountID;
+ }
+
+ /**
+ * Creates a protocol provider for the given <tt>accountID</tt> and
+ * registers it in the bundle context. This method has a persistent
+ * effect. Once created the resulting account will remain installed until
+ * removed through the uninstallAccount method.
+ *
+ * @param accountID the account identifier
+ * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is
+ * successfully loaded, otherwise returns <tt>false</tt>
+ */
+ public boolean loadAccount(AccountID accountID)
+ {
+ // Need to obtain the original user id property, instead of calling
+ // accountID.getUserID(), because this method could return a modified
+ // version of the user id property.
+ String userID = accountID
+ .getAccountPropertyString(ProtocolProviderFactory.USER_ID);
+
+ ProtocolProviderService service = createService(userID, accountID);
+
+ Dictionary<String, String> properties = new Hashtable<String, String>();
+ properties.put(PROTOCOL, protocolName);
+ properties.put(USER_ID, userID);
+
+ ServiceRegistration serviceRegistration =
+ bundleContext.registerService(ProtocolProviderService.class
+ .getName(), service, properties);
+
+ if (serviceRegistration == null)
+ return false;
+ else
+ {
+ synchronized (registeredAccounts)
+ {
+ registeredAccounts.put(accountID, serviceRegistration);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Unloads the account corresponding to the given <tt>accountID</tt>.
+ * Unregisters the corresponding protocol provider, but keeps the account in
+ * contrast to the uninstallAccount method.
+ *
+ * @param accountID the account identifier
+ * @return true if an account with the specified ID existed and was unloaded
+ * and false otherwise.
+ */
+ public boolean unloadAccount(AccountID accountID)
+ {
+ // Unregister the protocol provider.
+ ServiceReference serRef = getProviderForAccount(accountID);
+
+ if (serRef == null)
+ {
+ return false;
+ }
+
+ BundleContext bundleContext = getBundleContext();
+ ProtocolProviderService protocolProvider =
+ (ProtocolProviderService) bundleContext.getService(serRef);
+
+ try
+ {
+ protocolProvider.unregister();
+ }
+ catch (OperationFailedException ex)
+ {
+ logger
+ .error("Failed to unregister protocol provider for account : "
+ + accountID + " caused by: " + ex);
+ }
+
+ ServiceRegistration registration;
+
+ synchronized (registeredAccounts)
+ {
+ registration = registeredAccounts.remove(accountID);
+ }
+ if (registration == null)
+ {
+ return false;
+ }
+
+ // Kill the service.
+ registration.unregister();
+
+ return true;
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties.
+ *
+ * @param accountProperties a set of protocol (or implementation) specific
+ * properties defining the new account.
+ * @return the AccountID of the newly created account
+ */
+ public AccountID createAccount(Map<String, String> accountProperties)
+ {
+ BundleContext bundleContext = getBundleContext();
+ if (bundleContext == null)
+ throw new NullPointerException(
+ "The specified BundleContext was null");
+
+ if (accountProperties == null)
+ throw new NullPointerException(
+ "The specified property map was null");
+
+ String userID = accountProperties.get(USER_ID);
+ if (userID == null)
+ throw new NullPointerException(
+ "The account properties contained no user id.");
+
+ String protocolName = getProtocolName();
+ if (!accountProperties.containsKey(PROTOCOL))
+ accountProperties.put(PROTOCOL, protocolName);
+
+ return createAccountID(userID, accountProperties);
+ }
+
+ /**
+ * Creates a new <code>AccountID</code> instance with a specific user ID to
+ * represent a given set of account properties.
+ * <p>
+ * The method is a pure factory allowing implementers to specify the runtime
+ * type of the created <code>AccountID</code> and customize the instance.
+ * The returned <code>AccountID</code> will later be associated with a
+ * <code>ProtocolProviderService</code> by the caller (e.g. using
+ * {@link #createService(String, AccountID)}).
+ * </p>
+ *
+ * @param userID the user ID of the new instance
+ * @param accountProperties the set of properties to be represented by the
+ * new instance
+ * @return a new <code>AccountID</code> instance with the specified user ID
+ * representing the given set of account properties
+ */
+ protected abstract AccountID createAccountID(
+ String userID, Map<String, String> accountProperties);
+
+ /**
+ * Gets the name of the protocol this factory registers its
+ * <code>ProtocolProviderService</code>s with and to be placed in the
+ * properties of the accounts created by this factory.
+ *
+ * @return the name of the protocol this factory registers its
+ * <code>ProtocolProviderService</code>s with and to be placed in
+ * the properties of the accounts created by this factory
+ */
+ public String getProtocolName()
+ {
+ return protocolName;
+ }
+
+ /**
+ * Initializes a new <code>ProtocolProviderService</code> instance with a
+ * specific user ID to represent a specific <code>AccountID</code>.
+ * <p>
+ * The method is a pure factory allowing implementers to specify the runtime
+ * type of the created <code>ProtocolProviderService</code> and customize
+ * the instance. The caller will later register the returned service with
+ * the <code>BundleContext</code> of this factory.
+ * </p>
+ *
+ * @param userID the user ID to initialize the new instance with
+ * @param accountID the <code>AccountID</code> to be represented by the new
+ * instance
+ * @return a new <code>ProtocolProviderService</code> instance with the
+ * specific user ID representing the specified
+ * <code>AccountID</code>
+ */
+ protected abstract ProtocolProviderService createService(String userID,
+ AccountID accountID);
+
+ /**
+ * Removes the account with <tt>accountID</tt> from the set of accounts
+ * that are persistently stored inside the configuration service.
+ *
+ * @param accountID the AccountID of the account to remove.
+ *
+ * @return true if an account has been removed and false otherwise.
+ */
+ protected boolean removeStoredAccount(AccountID accountID)
+ {
+ return getAccountManager().removeStoredAccount(this, accountID);
+ }
+
+ /**
+ * Returns the prefix for all persistently stored properties of the account
+ * with the specified id.
+ * @param bundleContext a currently valid bundle context.
+ * @param accountID the AccountID of the account whose properties we're
+ * looking for.
+ * @param sourcePackageName a String containing the package name of the
+ * concrete factory class that extends us.
+ * @return a String indicating the ConfigurationService property name
+ * prefix under which all account properties are stored or null if no
+ * account corresponding to the specified id was found.
+ */
+ public static String findAccountPrefix(BundleContext bundleContext,
+ AccountID accountID,
+ String sourcePackageName)
+ {
+ ServiceReference confReference
+ = bundleContext.getServiceReference(
+ ConfigurationService.class.getName());
+ ConfigurationService configurationService
+ = (ConfigurationService) bundleContext.getService(confReference);
+
+ //first retrieve all accounts that we've registered
+ List<String> storedAccounts =
+ configurationService.getPropertyNamesByPrefix(sourcePackageName,
+ true);
+
+ //find an account with the corresponding id.
+ for (String accountRootPropertyName : storedAccounts)
+ {
+ //unregister the account in the configuration service.
+ //all the properties must have been registered in the following
+ //hierarchy:
+ //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
+ String accountUID = configurationService.getString(
+ accountRootPropertyName //node id
+ + "." + ACCOUNT_UID); // propname
+
+ if (accountID.getAccountUniqueID().equals(accountUID))
+ {
+ return accountRootPropertyName;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the name of the package that we're currently running in (i.e.
+ * the name of the package containing the proto factory that extends us).
+ *
+ * @return a String containing the package name of the concrete factory
+ * class that extends us.
+ */
+ private String getFactoryImplPackageName()
+ {
+ String className = getClass().getName();
+
+ return className.substring(0, className.lastIndexOf('.'));
+ }
+
+ /**
+ * Prepares the factory for bundle shutdown.
+ */
+ public void stop()
+ {
+ if (logger.isTraceEnabled())
+ logger.trace("Preparing to stop all protocol providers of" + this);
+
+ synchronized (registeredAccounts)
+ {
+ for (Enumeration<ServiceRegistration> registrations =
+ registeredAccounts.elements(); registrations.hasMoreElements();)
+ {
+ ServiceRegistration reg = registrations.nextElement();
+
+ stop(reg);
+
+ reg.unregister();
+ }
+
+ registeredAccounts.clear();
+ }
+ }
+
+ /**
+ * Shuts down the <code>ProtocolProviderService</code> representing an
+ * account registered with this factory.
+ *
+ * @param registeredAccount the <code>ServiceRegistration</code> of the
+ * <code>ProtocolProviderService</code> representing an account
+ * registered with this factory
+ */
+ protected void stop(ServiceRegistration registeredAccount)
+ {
+ ProtocolProviderService protocolProviderService =
+ (ProtocolProviderService) getBundleContext().getService(
+ registeredAccount.getReference());
+
+ protocolProviderService.shutdown();
+ }
+
+ /**
+ * Get the <tt>AccountManager</tt> of the protocol.
+ *
+ * @return <tt>AccountManager</tt> of the protocol
+ */
+ private AccountManager getAccountManager()
+ {
+ BundleContext bundleContext = getBundleContext();
+ ServiceReference serviceReference =
+ bundleContext.getServiceReference(AccountManager.class.getName());
+
+ return (AccountManager) bundleContext.getService(serviceReference);
+ }
+
+
+ /**
+ * Finds registered <tt>ProtocolProviderFactory</tt> for given
+ * <tt>protocolName</tt>.
+ * @param bundleContext the OSGI bundle context that will be used.
+ * @param protocolName the protocol name.
+ * @return Registered <tt>ProtocolProviderFactory</tt> for given protocol
+ * name or <tt>null</tt> if no provider was found.
+ */
+ static public ProtocolProviderFactory getProtocolProviderFactory(
+ BundleContext bundleContext,
+ String protocolName)
+ {
+ ServiceReference[] serRefs = null;
+
+ String osgiFilter =
+ "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
+
+ try
+ {
+ serRefs =
+ bundleContext.getServiceReferences(
+ ProtocolProviderFactory.class.getName(), osgiFilter);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ logger.error(ex);
+ return null;
+ }
+
+ return (ProtocolProviderFactory) bundleContext.getService(serRefs[0]);
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java
index 2b33ea2..9acb782 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java
@@ -1,616 +1,616 @@
-/*
- * 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.service.protocol.media;
-
-import java.beans.*;
-import java.lang.ref.*;
-import java.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.device.*;
-import org.jitsi.util.*;
-import org.jitsi.util.event.*;
-
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Extends <tt>CallConference</tt> to represent the media-specific information
- * associated with the telephony conference-related state of a
- * <tt>MediaAwareCall</tt>.
- *
- * @author Lyubomir Marinov
- */
-public class MediaAwareCallConference
- extends CallConference
-{
- /**
- * The <tt>PropertyChangeListener</tt> which will listen to the
- * <tt>MediaService</tt> about <tt>PropertyChangeEvent</tt>s.
- */
- private static WeakPropertyChangeListener
- mediaServicePropertyChangeListener;
-
- /**
- * The <tt>MediaDevice</tt>s indexed by <tt>MediaType</tt> ordinal which are
- * to be used by this telephony conference for media capture and/or
- * playback. If the <tt>MediaDevice</tt> for a specific <tt>MediaType</tt>
- * is <tt>null</tt>,
- * {@link MediaService#getDefaultDevice(MediaType, MediaUseCase)} is called.
- */
- private final MediaDevice[] devices;
-
- /**
- * The <tt>MediaDevice</tt>s which implement media mixing on the respective
- * <tt>MediaDevice</tt> in {@link #devices} for the purposes of this
- * telephony conference.
- */
- private final MediaDevice[] mixers;
-
- /**
- * The <tt>VolumeControl</tt> implementation which is to control the volume
- * (level) of the audio played back the telephony conference represented by
- * this instance.
- */
- private final VolumeControl outputVolumeControl
- = new BasicVolumeControl(
- VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
-
- /**
- * The <tt>PropertyChangeListener</tt> which listens to sources of
- * <tt>PropertyChangeEvent</tt>s on behalf of this instance.
- */
- private final PropertyChangeListener propertyChangeListener
- = new PropertyChangeListener()
- {
- public void propertyChange(PropertyChangeEvent ev)
- {
- MediaAwareCallConference.this.propertyChange(ev);
- }
- };
-
- /**
- * The <tt>RTPTranslator</tt> which forwards video RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus.
- */
- private RTPTranslator videoRTPTranslator;
-
- /**
- * Initializes a new <tt>MediaAwareCallConference</tt> instance.
- */
- public MediaAwareCallConference()
- {
- this(false);
- }
-
- /**
- * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to
- * optionally utilize the Jitsi VideoBridge server-side telephony
- * conferencing technology.
- *
- * @param jitsiVideoBridge <tt>true</tt> if the telephony conference
- * represented by the new instance is to utilize the Jitsi VideoBridge
- * server-side telephony conferencing technology; otherwise, <tt>false</tt>
- */
- public MediaAwareCallConference(boolean jitsiVideoBridge)
- {
- super(jitsiVideoBridge);
-
- int mediaTypeCount = MediaType.values().length;
-
- devices = new MediaDevice[mediaTypeCount];
- mixers = new MediaDevice[mediaTypeCount];
-
- /*
- * Listen to the MediaService in order to reflect changes in the user's
- * selection with respect to the default media device.
- */
- addMediaServicePropertyChangeListener(propertyChangeListener);
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the current <tt>MediaService</tt>
- * implementation. The implementation adds a <tt>WeakReference</tt> to the
- * specified <tt>listener</tt> because <tt>MediaAwareCallConference</tt>
- * is unable to determine when the <tt>PropertyChangeListener</tt> is to be
- * removed.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to add
- */
- private static synchronized void addMediaServicePropertyChangeListener(
- PropertyChangeListener listener)
- {
- if (mediaServicePropertyChangeListener == null)
- {
- final MediaService mediaService
- = ProtocolMediaActivator.getMediaService();
-
- if (mediaService != null)
- {
- mediaServicePropertyChangeListener
- = new WeakPropertyChangeListener()
- {
- @Override
- protected void addThisToNotifier()
- {
- mediaService.addPropertyChangeListener(this);
- }
-
- @Override
- protected void removeThisFromNotifier()
- {
- mediaService.removePropertyChangeListener(this);
- }
- };
- }
- }
- if (mediaServicePropertyChangeListener != null)
- {
- mediaServicePropertyChangeListener.addPropertyChangeListener(
- listener);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * If this telephony conference switches from being a conference focus to
- * not being such, disposes of the mixers used by this instance when it was
- * a conference focus
- */
- @Override
- protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
- {
- /*
- * If this telephony conference switches from being a conference
- * focus to not being one, dispose of the mixers used when it was a
- * conference focus.
- */
- if (oldValue && !newValue)
- {
- Arrays.fill(mixers, null);
- if (videoRTPTranslator != null)
- {
- videoRTPTranslator.dispose();
- videoRTPTranslator = null;
- }
- }
-
- super.conferenceFocusChanged(oldValue, newValue);
- }
-
- /**
- * {@inheritDoc}
- *
- * Disposes of <tt>this.videoRTPTranslator</tt> if the removed <tt>Call</tt>
- * was the last <tt>Call</tt> in this <tt>CallConference</tt>.
- *
- * @param call the <tt>Call</tt> which has been removed from the list of
- * <tt>Call</tt>s participating in this telephony conference.
- */
- @Override
- protected void callRemoved(Call call)
- {
- super.callRemoved(call);
-
- if (getCallCount() == 0 && (videoRTPTranslator != null))
- {
- videoRTPTranslator.dispose();
- videoRTPTranslator = null;
- }
- }
-
- /**
- * Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback
- * of media of the specified <tt>MediaType</tt> and is the default choice of
- * the user with respect to such a <tt>MediaDevice</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> in which the retrieved
- * <tt>MediaDevice</tt> is to capture and/or play back media
- * @param useCase the <tt>MediaUseCase</tt> associated with the intended
- * utilization of the <tt>MediaDevice</tt> to be retrieved
- * @return a <tt>MediaDevice</tt> which is capable of capture and/or
- * playback of media of the specified <tt>mediaType</tt> and is the default
- * choice of the user with respect to such a <tt>MediaDevice</tt>
- */
- public MediaDevice getDefaultDevice(
- MediaType mediaType,
- MediaUseCase useCase)
- {
- int mediaTypeIndex = mediaType.ordinal();
- MediaDevice device = devices[mediaTypeIndex];
- MediaService mediaService = ProtocolMediaActivator.getMediaService();
-
- if (device == null)
- device = mediaService.getDefaultDevice(mediaType, useCase);
-
- /*
- * Make sure that the device is capable of mixing in order to support
- * conferencing and call recording.
- */
- if (device != null)
- {
- MediaDevice mixer = mixers[mediaTypeIndex];
-
- if (mixer == null)
- {
- switch (mediaType)
- {
- case AUDIO:
- /*
- * TODO AudioMixer leads to very poor audio quality on
- * Android so do not use it unless it is really really
- * necessary.
- */
- if ((!OSUtils.IS_ANDROID || isConferenceFocus())
- /*
- * We can use the AudioMixer only if the device is
- * able to capture (because the AudioMixer will push
- * when the capture device pushes).
- */
- && device.getDirection().allowsSending())
- {
- mixer = mediaService.createMixer(device);
- }
- break;
-
- case VIDEO:
- if (isConferenceFocus())
- mixer = mediaService.createMixer(device);
- break;
- }
-
- mixers[mediaTypeIndex] = mixer;
- }
-
- if (mixer != null)
- device = mixer;
- }
-
- return device;
- }
-
- /**
- * Gets the <tt>VolumeControl</tt> which controls the volume (level) of the
- * audio played back in the telephony conference represented by this
- * instance.
- *
- * @return the <tt>VolumeControl</tt> which controls the volume (level) of
- * the audio played back in the telephony conference represented by this
- * instance
- */
- public VolumeControl getOutputVolumeControl()
- {
- return outputVolumeControl;
- }
-
- /**
- * Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus.
- *
- * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
- * RTP and RTCP traffic is to be forwarded between
- * @return the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus
- */
- public RTPTranslator getRTPTranslator(MediaType mediaType)
- {
- RTPTranslator rtpTranslator = null;
-
- /*
- * XXX A mixer is created for audio even when the local peer is not a
- * conference focus in order to enable additional functionality.
- * Similarly, the videoRTPTranslator is created even when the local peer
- * is not a conference focus in order to enable the local peer to turn
- * into a conference focus at a later time. More specifically,
- * MediaStreamImpl is unable to accommodate an RTPTranslator after it
- * has created its RTPManager. Yet again like the audio mixer, we'd
- * better not try to use it on Android at this time because of
- * performance issues that might arise.
- */
- if (MediaType.VIDEO.equals(mediaType)
- && (!OSUtils.IS_ANDROID || isConferenceFocus()))
- {
- if (videoRTPTranslator == null)
- {
- videoRTPTranslator
- = ProtocolMediaActivator
- .getMediaService()
- .createRTPTranslator();
- }
- rtpTranslator = videoRTPTranslator;
- }
- return rtpTranslator;
- }
-
- /**
- * Notifies this <tt>MediaAwareCallConference</tt> about changes in the
- * values of the properties of sources of <tt>PropertyChangeEvent</tt>s. For
- * example, this instance listens to changes of the value of
- * {@link MediaService#DEFAULT_DEVICE} which represents the user's choice
- * with respect to the default audio device.
- *
- * @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the
- * property which had its value changed and the old and new values of that
- * property
- */
- private void propertyChange(PropertyChangeEvent ev)
- {
- String propertyName = ev.getPropertyName();
-
- if (MediaService.DEFAULT_DEVICE.equals(propertyName))
- {
- Object source = ev.getSource();
-
- if (source instanceof MediaService)
- {
- /*
- * XXX We only support changing the default audio device at the
- * time of this writing.
- */
- int mediaTypeIndex = MediaType.AUDIO.ordinal();
- MediaDevice mixer = mixers[mediaTypeIndex];
- MediaDevice oldValue
- = (mixer instanceof MediaDeviceWrapper)
- ? ((MediaDeviceWrapper) mixer).getWrappedDevice()
- : null;
- MediaDevice newValue = devices[mediaTypeIndex];
-
- if (newValue == null)
- {
- newValue
- = ProtocolMediaActivator
- .getMediaService()
- .getDefaultDevice(
- MediaType.AUDIO,
- MediaUseCase.ANY);
- }
-
- /*
- * XXX If MediaService#getDefaultDevice(MediaType, MediaUseCase)
- * above returns null and its earlier return value was not null,
- * we will not notify of an actual change in the value of the
- * user's choice with respect to the default audio device.
- */
- if (oldValue != newValue)
- {
- mixers[mediaTypeIndex] = null;
- firePropertyChange(
- MediaAwareCall.DEFAULT_DEVICE,
- oldValue, newValue);
- }
- }
- }
- }
-
- /**
- * Sets the <tt>MediaDevice</tt> to be used by this telephony conference for
- * capture and/or playback of media of a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the media which is to be
- * captured and/or played back by the specified <tt>device</tt>
- * @param device the <tt>MediaDevice</tt> to be used by this telephony
- * conference for capture and/or playback of media of the specified
- * <tt>mediaType</tt>
- */
- void setDevice(MediaType mediaType, MediaDevice device)
- {
- int mediaTypeIndex = mediaType.ordinal();
- MediaDevice oldValue = devices[mediaTypeIndex];
-
- /*
- * XXX While we know the old and the new master/wrapped devices, we
- * are not sure whether the mixer has been used. Anyway, we have to
- * report different values in order to have PropertyChangeSupport
- * really fire an event.
- */
- MediaDevice mixer = mixers[mediaTypeIndex];
-
- if (mixer instanceof MediaDeviceWrapper)
- oldValue = ((MediaDeviceWrapper) mixer).getWrappedDevice();
-
- MediaDevice newValue = devices[mediaTypeIndex] = device;
-
- if (oldValue != newValue)
- {
- mixers[mediaTypeIndex] = null;
- firePropertyChange(
- MediaAwareCall.DEFAULT_DEVICE,
- oldValue, newValue);
- }
- }
-
- /**
- * Implements a <tt>PropertyChangeListener</tt> which weakly references and
- * delegates to specific <tt>PropertyChangeListener</tt>s and automatically
- * adds itself to and removes itself from a specific
- * <tt>PropertyChangeNotifier</tt> depending on whether there are
- * <tt>PropertyChangeListener</tt>s to delegate to. Thus enables listening
- * to a <tt>PropertyChangeNotifier</tt> by invoking
- * {@link PropertyChangeNotifier#addPropertyChangeListener(
- * PropertyChangeListener)} without
- * {@link PropertyChangeNotifier#removePropertyChangeListener(
- * PropertyChangeListener)}.
- */
- private static class WeakPropertyChangeListener
- implements PropertyChangeListener
- {
- /**
- * The indicator which determines whether this
- * <tt>PropertyChangeListener</tt> has been added to {@link #notifier}.
- */
- private boolean added = false;
-
- /**
- * The list of <tt>PropertyChangeListener</tt>s which are to be notified
- * about <tt>PropertyChangeEvent</tt>s fired by {@link #notifier}.
- */
- private final List<WeakReference<PropertyChangeListener>> listeners
- = new LinkedList<WeakReference<PropertyChangeListener>>();
-
- /**
- * The <tt>PropertyChangeNotifier</tt> this instance is to listen to
- * about <tt>PropertyChangeEvent</tt>s which are to be forwarded to
- * {@link #listeners}.
- */
- private final PropertyChangeNotifier notifier;
-
- /**
- * Initializes a new <tt>WeakPropertyChangeListener</tt> instance.
- */
- protected WeakPropertyChangeListener()
- {
- this(null);
- }
-
- /**
- * Initializes a new <tt>WeakPropertyChangeListener</tt> instance which
- * is to listen to a specific <tt>PropertyChangeNotifier</tt>.
- *
- * @param notifier the <tt>PropertyChangeNotifier</tt> the new instance
- * is to listen to
- */
- public WeakPropertyChangeListener(PropertyChangeNotifier notifier)
- {
- this.notifier = notifier;
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to the list of
- * <tt>PropertyChangeListener</tt>s to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the
- * <tt>PropertyChangeNotifier</tt> associated with this instance.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to add
- */
- public synchronized void addPropertyChangeListener(
- PropertyChangeListener listener)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
- boolean add = true;
-
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if (l == null)
- i.remove();
- else if (l.equals(listener))
- add = false;
- }
- if (add
- && listeners.add(
- new WeakReference<PropertyChangeListener>(listener))
- && !this.added)
- {
- addThisToNotifier();
- this.added = true;
- }
- }
-
- /**
- * Adds this as a <tt>PropertyChangeListener</tt> to {@link #notifier}.
- */
- protected void addThisToNotifier()
- {
- if (notifier != null)
- notifier.addPropertyChangeListener(this);
- }
-
- /**
- * {@inheritDoc}
- *
- * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
- * {@link #notifier}.
- */
- public void propertyChange(PropertyChangeEvent ev)
- {
- PropertyChangeListener[] ls;
- int n;
-
- synchronized (this)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
-
- ls = new PropertyChangeListener[listeners.size()];
- n = 0;
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if (l == null)
- i.remove();
- else
- ls[n++] = l;
- }
- if ((n == 0) && this.added)
- {
- removeThisFromNotifier();
- this.added = false;
- }
- }
-
- if (n != 0)
- {
- for (PropertyChangeListener l : ls)
- {
- if (l == null)
- break;
- else
- l.propertyChange(ev);
- }
- }
- }
-
- /**
- * Removes a specific <tt>PropertyChangeListener</tt> from the list of
- * <tt>PropertyChangeListener</tt>s to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the
- * <tt>PropertyChangeNotifier</tt> associated with this instance.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to remove
- */
- @SuppressWarnings("unused")
- public synchronized void removePropertyChangeListener(
- PropertyChangeListener listener)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
-
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if ((l == null) || l.equals(listener))
- i.remove();
- }
- if (this.added && (listeners.size() == 0))
- {
- removeThisFromNotifier();
- this.added = false;
- }
- }
-
- /**
- * Removes this as a <tt>PropertyChangeListener</tt> from
- * {@link #notifier}.
- */
- protected void removeThisFromNotifier()
- {
- if (notifier != null)
- notifier.removePropertyChangeListener(this);
- }
- }
-}
+/*
+ * 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.service.protocol.media;
+
+import java.beans.*;
+import java.lang.ref.*;
+import java.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.device.*;
+import org.jitsi.util.*;
+import org.jitsi.util.event.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Extends <tt>CallConference</tt> to represent the media-specific information
+ * associated with the telephony conference-related state of a
+ * <tt>MediaAwareCall</tt>.
+ *
+ * @author Lyubomir Marinov
+ */
+public class MediaAwareCallConference
+ extends CallConference
+{
+ /**
+ * The <tt>PropertyChangeListener</tt> which will listen to the
+ * <tt>MediaService</tt> about <tt>PropertyChangeEvent</tt>s.
+ */
+ private static WeakPropertyChangeListener
+ mediaServicePropertyChangeListener;
+
+ /**
+ * The <tt>MediaDevice</tt>s indexed by <tt>MediaType</tt> ordinal which are
+ * to be used by this telephony conference for media capture and/or
+ * playback. If the <tt>MediaDevice</tt> for a specific <tt>MediaType</tt>
+ * is <tt>null</tt>,
+ * {@link MediaService#getDefaultDevice(MediaType, MediaUseCase)} is called.
+ */
+ private final MediaDevice[] devices;
+
+ /**
+ * The <tt>MediaDevice</tt>s which implement media mixing on the respective
+ * <tt>MediaDevice</tt> in {@link #devices} for the purposes of this
+ * telephony conference.
+ */
+ private final MediaDevice[] mixers;
+
+ /**
+ * The <tt>VolumeControl</tt> implementation which is to control the volume
+ * (level) of the audio played back the telephony conference represented by
+ * this instance.
+ */
+ private final VolumeControl outputVolumeControl
+ = new BasicVolumeControl(
+ VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
+
+ /**
+ * The <tt>PropertyChangeListener</tt> which listens to sources of
+ * <tt>PropertyChangeEvent</tt>s on behalf of this instance.
+ */
+ private final PropertyChangeListener propertyChangeListener
+ = new PropertyChangeListener()
+ {
+ public void propertyChange(PropertyChangeEvent ev)
+ {
+ MediaAwareCallConference.this.propertyChange(ev);
+ }
+ };
+
+ /**
+ * The <tt>RTPTranslator</tt> which forwards video RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus.
+ */
+ private RTPTranslator videoRTPTranslator;
+
+ /**
+ * Initializes a new <tt>MediaAwareCallConference</tt> instance.
+ */
+ public MediaAwareCallConference()
+ {
+ this(false);
+ }
+
+ /**
+ * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to
+ * optionally utilize the Jitsi Videobridge server-side telephony
+ * conferencing technology.
+ *
+ * @param jitsiVideobridge <tt>true</tt> if the telephony conference
+ * represented by the new instance is to utilize the Jitsi Videobridge
+ * server-side telephony conferencing technology; otherwise, <tt>false</tt>
+ */
+ public MediaAwareCallConference(boolean jitsiVideobridge)
+ {
+ super(jitsiVideobridge);
+
+ int mediaTypeCount = MediaType.values().length;
+
+ devices = new MediaDevice[mediaTypeCount];
+ mixers = new MediaDevice[mediaTypeCount];
+
+ /*
+ * Listen to the MediaService in order to reflect changes in the user's
+ * selection with respect to the default media device.
+ */
+ addMediaServicePropertyChangeListener(propertyChangeListener);
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to be notified about
+ * <tt>PropertyChangeEvent</tt>s fired by the current <tt>MediaService</tt>
+ * implementation. The implementation adds a <tt>WeakReference</tt> to the
+ * specified <tt>listener</tt> because <tt>MediaAwareCallConference</tt>
+ * is unable to determine when the <tt>PropertyChangeListener</tt> is to be
+ * removed.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to add
+ */
+ private static synchronized void addMediaServicePropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ if (mediaServicePropertyChangeListener == null)
+ {
+ final MediaService mediaService
+ = ProtocolMediaActivator.getMediaService();
+
+ if (mediaService != null)
+ {
+ mediaServicePropertyChangeListener
+ = new WeakPropertyChangeListener()
+ {
+ @Override
+ protected void addThisToNotifier()
+ {
+ mediaService.addPropertyChangeListener(this);
+ }
+
+ @Override
+ protected void removeThisFromNotifier()
+ {
+ mediaService.removePropertyChangeListener(this);
+ }
+ };
+ }
+ }
+ if (mediaServicePropertyChangeListener != null)
+ {
+ mediaServicePropertyChangeListener.addPropertyChangeListener(
+ listener);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If this telephony conference switches from being a conference focus to
+ * not being such, disposes of the mixers used by this instance when it was
+ * a conference focus
+ */
+ @Override
+ protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
+ {
+ /*
+ * If this telephony conference switches from being a conference
+ * focus to not being one, dispose of the mixers used when it was a
+ * conference focus.
+ */
+ if (oldValue && !newValue)
+ {
+ Arrays.fill(mixers, null);
+ if (videoRTPTranslator != null)
+ {
+ videoRTPTranslator.dispose();
+ videoRTPTranslator = null;
+ }
+ }
+
+ super.conferenceFocusChanged(oldValue, newValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Disposes of <tt>this.videoRTPTranslator</tt> if the removed <tt>Call</tt>
+ * was the last <tt>Call</tt> in this <tt>CallConference</tt>.
+ *
+ * @param call the <tt>Call</tt> which has been removed from the list of
+ * <tt>Call</tt>s participating in this telephony conference.
+ */
+ @Override
+ protected void callRemoved(Call call)
+ {
+ super.callRemoved(call);
+
+ if (getCallCount() == 0 && (videoRTPTranslator != null))
+ {
+ videoRTPTranslator.dispose();
+ videoRTPTranslator = null;
+ }
+ }
+
+ /**
+ * Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback
+ * of media of the specified <tt>MediaType</tt> and is the default choice of
+ * the user with respect to such a <tt>MediaDevice</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> in which the retrieved
+ * <tt>MediaDevice</tt> is to capture and/or play back media
+ * @param useCase the <tt>MediaUseCase</tt> associated with the intended
+ * utilization of the <tt>MediaDevice</tt> to be retrieved
+ * @return a <tt>MediaDevice</tt> which is capable of capture and/or
+ * playback of media of the specified <tt>mediaType</tt> and is the default
+ * choice of the user with respect to such a <tt>MediaDevice</tt>
+ */
+ public MediaDevice getDefaultDevice(
+ MediaType mediaType,
+ MediaUseCase useCase)
+ {
+ int mediaTypeIndex = mediaType.ordinal();
+ MediaDevice device = devices[mediaTypeIndex];
+ MediaService mediaService = ProtocolMediaActivator.getMediaService();
+
+ if (device == null)
+ device = mediaService.getDefaultDevice(mediaType, useCase);
+
+ /*
+ * Make sure that the device is capable of mixing in order to support
+ * conferencing and call recording.
+ */
+ if (device != null)
+ {
+ MediaDevice mixer = mixers[mediaTypeIndex];
+
+ if (mixer == null)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ /*
+ * TODO AudioMixer leads to very poor audio quality on
+ * Android so do not use it unless it is really really
+ * necessary.
+ */
+ if ((!OSUtils.IS_ANDROID || isConferenceFocus())
+ /*
+ * We can use the AudioMixer only if the device is
+ * able to capture (because the AudioMixer will push
+ * when the capture device pushes).
+ */
+ && device.getDirection().allowsSending())
+ {
+ mixer = mediaService.createMixer(device);
+ }
+ break;
+
+ case VIDEO:
+ if (isConferenceFocus())
+ mixer = mediaService.createMixer(device);
+ break;
+ }
+
+ mixers[mediaTypeIndex] = mixer;
+ }
+
+ if (mixer != null)
+ device = mixer;
+ }
+
+ return device;
+ }
+
+ /**
+ * Gets the <tt>VolumeControl</tt> which controls the volume (level) of the
+ * audio played back in the telephony conference represented by this
+ * instance.
+ *
+ * @return the <tt>VolumeControl</tt> which controls the volume (level) of
+ * the audio played back in the telephony conference represented by this
+ * instance
+ */
+ public VolumeControl getOutputVolumeControl()
+ {
+ return outputVolumeControl;
+ }
+
+ /**
+ * Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
+ * RTP and RTCP traffic is to be forwarded between
+ * @return the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
+ * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
+ * this telephony conference when the local peer is acting as a conference
+ * focus
+ */
+ public RTPTranslator getRTPTranslator(MediaType mediaType)
+ {
+ RTPTranslator rtpTranslator = null;
+
+ /*
+ * XXX A mixer is created for audio even when the local peer is not a
+ * conference focus in order to enable additional functionality.
+ * Similarly, the videoRTPTranslator is created even when the local peer
+ * is not a conference focus in order to enable the local peer to turn
+ * into a conference focus at a later time. More specifically,
+ * MediaStreamImpl is unable to accommodate an RTPTranslator after it
+ * has created its RTPManager. Yet again like the audio mixer, we'd
+ * better not try to use it on Android at this time because of
+ * performance issues that might arise.
+ */
+ if (MediaType.VIDEO.equals(mediaType)
+ && (!OSUtils.IS_ANDROID || isConferenceFocus()))
+ {
+ if (videoRTPTranslator == null)
+ {
+ videoRTPTranslator
+ = ProtocolMediaActivator
+ .getMediaService()
+ .createRTPTranslator();
+ }
+ rtpTranslator = videoRTPTranslator;
+ }
+ return rtpTranslator;
+ }
+
+ /**
+ * Notifies this <tt>MediaAwareCallConference</tt> about changes in the
+ * values of the properties of sources of <tt>PropertyChangeEvent</tt>s. For
+ * example, this instance listens to changes of the value of
+ * {@link MediaService#DEFAULT_DEVICE} which represents the user's choice
+ * with respect to the default audio device.
+ *
+ * @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the
+ * property which had its value changed and the old and new values of that
+ * property
+ */
+ private void propertyChange(PropertyChangeEvent ev)
+ {
+ String propertyName = ev.getPropertyName();
+
+ if (MediaService.DEFAULT_DEVICE.equals(propertyName))
+ {
+ Object source = ev.getSource();
+
+ if (source instanceof MediaService)
+ {
+ /*
+ * XXX We only support changing the default audio device at the
+ * time of this writing.
+ */
+ int mediaTypeIndex = MediaType.AUDIO.ordinal();
+ MediaDevice mixer = mixers[mediaTypeIndex];
+ MediaDevice oldValue
+ = (mixer instanceof MediaDeviceWrapper)
+ ? ((MediaDeviceWrapper) mixer).getWrappedDevice()
+ : null;
+ MediaDevice newValue = devices[mediaTypeIndex];
+
+ if (newValue == null)
+ {
+ newValue
+ = ProtocolMediaActivator
+ .getMediaService()
+ .getDefaultDevice(
+ MediaType.AUDIO,
+ MediaUseCase.ANY);
+ }
+
+ /*
+ * XXX If MediaService#getDefaultDevice(MediaType, MediaUseCase)
+ * above returns null and its earlier return value was not null,
+ * we will not notify of an actual change in the value of the
+ * user's choice with respect to the default audio device.
+ */
+ if (oldValue != newValue)
+ {
+ mixers[mediaTypeIndex] = null;
+ firePropertyChange(
+ MediaAwareCall.DEFAULT_DEVICE,
+ oldValue, newValue);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the <tt>MediaDevice</tt> to be used by this telephony conference for
+ * capture and/or playback of media of a specific <tt>MediaType</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the media which is to be
+ * captured and/or played back by the specified <tt>device</tt>
+ * @param device the <tt>MediaDevice</tt> to be used by this telephony
+ * conference for capture and/or playback of media of the specified
+ * <tt>mediaType</tt>
+ */
+ void setDevice(MediaType mediaType, MediaDevice device)
+ {
+ int mediaTypeIndex = mediaType.ordinal();
+ MediaDevice oldValue = devices[mediaTypeIndex];
+
+ /*
+ * XXX While we know the old and the new master/wrapped devices, we
+ * are not sure whether the mixer has been used. Anyway, we have to
+ * report different values in order to have PropertyChangeSupport
+ * really fire an event.
+ */
+ MediaDevice mixer = mixers[mediaTypeIndex];
+
+ if (mixer instanceof MediaDeviceWrapper)
+ oldValue = ((MediaDeviceWrapper) mixer).getWrappedDevice();
+
+ MediaDevice newValue = devices[mediaTypeIndex] = device;
+
+ if (oldValue != newValue)
+ {
+ mixers[mediaTypeIndex] = null;
+ firePropertyChange(
+ MediaAwareCall.DEFAULT_DEVICE,
+ oldValue, newValue);
+ }
+ }
+
+ /**
+ * Implements a <tt>PropertyChangeListener</tt> which weakly references and
+ * delegates to specific <tt>PropertyChangeListener</tt>s and automatically
+ * adds itself to and removes itself from a specific
+ * <tt>PropertyChangeNotifier</tt> depending on whether there are
+ * <tt>PropertyChangeListener</tt>s to delegate to. Thus enables listening
+ * to a <tt>PropertyChangeNotifier</tt> by invoking
+ * {@link PropertyChangeNotifier#addPropertyChangeListener(
+ * PropertyChangeListener)} without
+ * {@link PropertyChangeNotifier#removePropertyChangeListener(
+ * PropertyChangeListener)}.
+ */
+ private static class WeakPropertyChangeListener
+ implements PropertyChangeListener
+ {
+ /**
+ * The indicator which determines whether this
+ * <tt>PropertyChangeListener</tt> has been added to {@link #notifier}.
+ */
+ private boolean added = false;
+
+ /**
+ * The list of <tt>PropertyChangeListener</tt>s which are to be notified
+ * about <tt>PropertyChangeEvent</tt>s fired by {@link #notifier}.
+ */
+ private final List<WeakReference<PropertyChangeListener>> listeners
+ = new LinkedList<WeakReference<PropertyChangeListener>>();
+
+ /**
+ * The <tt>PropertyChangeNotifier</tt> this instance is to listen to
+ * about <tt>PropertyChangeEvent</tt>s which are to be forwarded to
+ * {@link #listeners}.
+ */
+ private final PropertyChangeNotifier notifier;
+
+ /**
+ * Initializes a new <tt>WeakPropertyChangeListener</tt> instance.
+ */
+ protected WeakPropertyChangeListener()
+ {
+ this(null);
+ }
+
+ /**
+ * Initializes a new <tt>WeakPropertyChangeListener</tt> instance which
+ * is to listen to a specific <tt>PropertyChangeNotifier</tt>.
+ *
+ * @param notifier the <tt>PropertyChangeNotifier</tt> the new instance
+ * is to listen to
+ */
+ public WeakPropertyChangeListener(PropertyChangeNotifier notifier)
+ {
+ this.notifier = notifier;
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to the list of
+ * <tt>PropertyChangeListener</tt>s to be notified about
+ * <tt>PropertyChangeEvent</tt>s fired by the
+ * <tt>PropertyChangeNotifier</tt> associated with this instance.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to add
+ */
+ public synchronized void addPropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ Iterator<WeakReference<PropertyChangeListener>> i
+ = listeners.iterator();
+ boolean add = true;
+
+ while (i.hasNext())
+ {
+ PropertyChangeListener l = i.next().get();
+
+ if (l == null)
+ i.remove();
+ else if (l.equals(listener))
+ add = false;
+ }
+ if (add
+ && listeners.add(
+ new WeakReference<PropertyChangeListener>(listener))
+ && !this.added)
+ {
+ addThisToNotifier();
+ this.added = true;
+ }
+ }
+
+ /**
+ * Adds this as a <tt>PropertyChangeListener</tt> to {@link #notifier}.
+ */
+ protected void addThisToNotifier()
+ {
+ if (notifier != null)
+ notifier.addPropertyChangeListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
+ * {@link #notifier}.
+ */
+ public void propertyChange(PropertyChangeEvent ev)
+ {
+ PropertyChangeListener[] ls;
+ int n;
+
+ synchronized (this)
+ {
+ Iterator<WeakReference<PropertyChangeListener>> i
+ = listeners.iterator();
+
+ ls = new PropertyChangeListener[listeners.size()];
+ n = 0;
+ while (i.hasNext())
+ {
+ PropertyChangeListener l = i.next().get();
+
+ if (l == null)
+ i.remove();
+ else
+ ls[n++] = l;
+ }
+ if ((n == 0) && this.added)
+ {
+ removeThisFromNotifier();
+ this.added = false;
+ }
+ }
+
+ if (n != 0)
+ {
+ for (PropertyChangeListener l : ls)
+ {
+ if (l == null)
+ break;
+ else
+ l.propertyChange(ev);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>PropertyChangeListener</tt> from the list of
+ * <tt>PropertyChangeListener</tt>s to be notified about
+ * <tt>PropertyChangeEvent</tt>s fired by the
+ * <tt>PropertyChangeNotifier</tt> associated with this instance.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to remove
+ */
+ @SuppressWarnings("unused")
+ public synchronized void removePropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ Iterator<WeakReference<PropertyChangeListener>> i
+ = listeners.iterator();
+
+ while (i.hasNext())
+ {
+ PropertyChangeListener l = i.next().get();
+
+ if ((l == null) || l.equals(listener))
+ i.remove();
+ }
+ if (this.added && (listeners.size() == 0))
+ {
+ removeThisFromNotifier();
+ this.added = false;
+ }
+ }
+
+ /**
+ * Removes this as a <tt>PropertyChangeListener</tt> from
+ * {@link #notifier}.
+ */
+ protected void removeThisFromNotifier()
+ {
+ if (notifier != null)
+ notifier.removePropertyChangeListener(this);
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
index 59c885c..59b8798 100644
--- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
+++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java
@@ -1,1251 +1,1251 @@
-/*
- * 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.service.protocol.media;
-
-import java.beans.*;
-import java.util.*;
-
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.event.*;
-import org.jitsi.service.protocol.event.*;
-
-/**
- * A utility class implementing media control code shared between current
- * telephony implementations. This class is only meant for use by protocol
- * implementations and should/could not be accessed by bundles that are simply
- * using the telephony functionalities.
- *
- * @param <T> the peer extension class like for example <tt>CallSipImpl</tt>
- * or <tt>CallJabberImpl</tt>
- * @param <U> the media handler extension class like for example
- * <tt>CallPeerMediaHandlerSipImpl</tt> or
- * <tt>CallPeerMediaHandlerJabberImpl</tt>
- * @param <V> the provider extension class like for example
- * <tt>ProtocolProviderServiceSipImpl</tt> or
- * <tt>ProtocolProviderServiceJabberImpl</tt>
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Boris Grozev
- */
-public abstract class MediaAwareCallPeer
- <T extends MediaAwareCall<?, ?, V>,
- U extends CallPeerMediaHandler<?>,
- V extends ProtocolProviderService>
- extends AbstractCallPeer<T, V>
- implements SrtpListener,
- CallPeerConferenceListener,
- CsrcAudioLevelListener,
- SimpleAudioLevelListener
-{
- /**
- * The <tt>Logger</tt> used by the <tt>MediaAwareCallPeer</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(MediaAwareCallPeer.class);
-
- /**
- * The call this peer belongs to.
- */
- private T call;
-
- /**
- * The listeners registered for level changes in the audio of participants
- * that this peer might be mixing and that we are not directly communicating
- * with.
- */
- private final List<ConferenceMembersSoundLevelListener>
- conferenceMembersSoundLevelListeners
- = new ArrayList<ConferenceMembersSoundLevelListener>();
-
- /**
- * A byte array containing the image/photo representing the call peer.
- */
- private byte[] image;
-
- /**
- * The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>always
- * corresponds to exactly one instance of <tt>CallPeerMediaHandler</tt> and
- * both classes are only separated for reasons of readability.
- */
- private U mediaHandler;
-
- /**
- * The <tt>PropertyChangeListener</tt> which listens to
- * {@link CallPeerMediaHandler} for changes in the values of its properties.
- */
- private PropertyChangeListener mediaHandlerPropertyChangeListener;
-
- /**
- * A string uniquely identifying the peer.
- */
- private String peerID;
-
- /**
- * The protocol provider that this peer belongs to.
- */
- private final V protocolProvider;
-
- /**
- * The list of <tt>SoundLevelListener</tt>s interested in level changes in
- * the audio we are getting from the remote peer.
- * <p>
- * It is implemented as a copy-on-write storage because the number of
- * additions and removals of <tt>SoundLevelListener</tt>s is expected to be
- * far smaller than the number of audio level changes. The access to it is
- * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
- * </p>
- */
- private List<SoundLevelListener> streamSoundLevelListeners;
-
- /**
- * The <tt>Object</tt> to synchronize the access to
- * {@link #streamSoundLevelListeners}.
- */
- private final Object streamSoundLevelListenersSyncRoot = new Object();
-
- /**
- * The <tt>List</tt> of <tt>PropertyChangeListener</tt>s listening to this
- * <tt>CallPeer</tt> for changes in the values of its properties related to
- * video.
- */
- private final List<PropertyChangeListener> videoPropertyChangeListeners
- = new LinkedList<PropertyChangeListener>();
-
- /**
- * Represents the last Conference Information (RFC4575) document sent to
- * this <tt>CallPeer</tt>. This is always a document with state "full", even
- * if the last document actually sent was a "partial"
- */
- private ConferenceInfoDocument lastConferenceInfoSent = null;
-
- /**
- * The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
- * a Conference Information (RFC4575) document was last sent to this
- * <tt>CallPeer</tt>.
- */
- private long lastConferenceInfoSentTimestamp = -1;
-
- /**
- * The last Conference Information (RFC4575) document sent to us by this
- * <tt>CallPeer</tt>. This is always a document with state "full", which is
- * only gets updated by "partial" or "deleted" documents.
- */
- private ConferenceInfoDocument lastConferenceInfoReceived = null;
-
- /**
- * Whether a conference-info document has been scheduled to be sent to this
- * <tt>CallPeer</tt>
- */
- private boolean confInfoScheduled = false;
-
- /**
- * Synchronization object for confInfoScheduled
- */
- private final Object confInfoScheduledSyncRoot = new Object();
-
- /**
- * Creates a new call peer with address <tt>peerAddress</tt>.
- *
- * @param owningCall the call that contains this call peer.
- */
- public MediaAwareCallPeer(T owningCall)
- {
- this.call = owningCall;
- this.protocolProvider = owningCall.getProtocolProvider();
-
- // create the uid
- this.peerID
- = String.valueOf(System.currentTimeMillis())
- + String.valueOf(hashCode());
-
- // we listen for events when the call will become focus or not
- // of a conference so we will add or remove our sound level listeners
- super.addCallPeerConferenceListener(this);
- }
-
- /**
- * Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
- * of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
- * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
- */
- public void addConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
- {
- /*
- * XXX The uses of the method at the time of this writing rely on being
- * able to add a null listener so make it a no-op here.
- */
- if (listener == null)
- return;
-
- synchronized (conferenceMembersSoundLevelListeners)
- {
- if (conferenceMembersSoundLevelListeners.size() == 0)
- {
- // if this is the first listener that's being registered with
- // us, we also need to register ourselves as a CSRC audio level
- // listener with the media handler.
- getMediaHandler().setCsrcAudioLevelListener(this);
- }
- conferenceMembersSoundLevelListeners.add(listener);
- }
- }
-
- /**
- * Adds a specific <tt>SoundLevelListener</tt> to the list of listeners
- * interested in and notified about changes in the sound level of the audio
- * sent by the remote party. When the first listener is being registered
- * the method also registers its single listener with the media handler so
- * that it would receive level change events and delegate them to the
- * listeners that have registered with us.
- *
- * @param listener the <tt>SoundLevelListener</tt> to add
- */
- public void addStreamSoundLevelListener(SoundLevelListener listener)
- {
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- if ((streamSoundLevelListeners == null)
- || streamSoundLevelListeners.isEmpty())
- {
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- if (isJitsiVideoBridge())
- {
- /*
- * When the local user/peer has organized a telephony
- * conference utilizing the Jitsi VideoBridge server-side
- * technology, the server will calculate the audio levels
- * and not the client.
- */
- mediaHandler.setCsrcAudioLevelListener(this);
- }
- else
- {
- /*
- * If this is the first listener that's being registered
- * with us, we also need to register ourselves as an audio
- * level listener with the media handler. We do this so that
- * audio levels would only be calculated if anyone is
- * interested in receiving them.
- */
- mediaHandler.setStreamAudioLevelListener(this);
- }
- }
-
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate without
- * ConcurrentModificationExceptions.
- */
- streamSoundLevelListeners
- = (streamSoundLevelListeners == null)
- ? new ArrayList<SoundLevelListener>()
- : new ArrayList<SoundLevelListener>(
- streamSoundLevelListeners);
- streamSoundLevelListeners.add(listener);
- }
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
- * their values.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to be notified
- * when the properties associated with the specified <tt>Call</tt> change
- * their values
- */
- public void addVideoPropertyChangeListener(PropertyChangeListener listener)
- {
- if (listener == null)
- throw new NullPointerException("listener");
-
- synchronized (videoPropertyChangeListeners)
- {
- /*
- * The video is part of the media-related functionality and thus it
- * is the responsibility of mediaHandler. So listen to mediaHandler
- * for video-related property changes and re-fire them as
- * originating from this instance.
- */
- if (!videoPropertyChangeListeners.contains(listener)
- && videoPropertyChangeListeners.add(listener)
- && (mediaHandlerPropertyChangeListener == null))
- {
- mediaHandlerPropertyChangeListener
- = new PropertyChangeListener()
- {
- public void propertyChange(PropertyChangeEvent event)
- {
- Iterable<PropertyChangeListener> listeners;
-
- synchronized (videoPropertyChangeListeners)
- {
- listeners
- = new LinkedList<PropertyChangeListener>(
- videoPropertyChangeListeners);
- }
-
- PropertyChangeEvent thisEvent
- = new PropertyChangeEvent(
- this,
- event.getPropertyName(),
- event.getOldValue(),
- event.getNewValue());
-
- for (PropertyChangeListener listener : listeners)
- listener.propertyChange(thisEvent);
- }
- };
- getMediaHandler()
- .addPropertyChangeListener(
- mediaHandlerPropertyChangeListener);
- }
- }
- }
-
- /**
- * Notified by its very majesty the media service about changes in the audio
- * level of the stream coming from this peer, the method generates the
- * corresponding events and delivers them to the listeners that have
- * registered here.
- *
- * @param newLevel the new audio level of the audio stream received from the
- * remote peer
- */
- public void audioLevelChanged(int newLevel)
- {
- /*
- * If we're in a conference in which this CallPeer is the focus and
- * we're the only member in it besides the focus, we will not receive
- * audio levels in the RTP and our media will instead measure the audio
- * levels of the received media. In order to make the UI oblivious of
- * the difference, we have to translate the event to the appropriate
- * type of listener.
- *
- * We may end up in a conference call with 0 members if the server
- * for some reason doesn't support sip conference (our subscribes
- * doesn't go to the focus of the conference) and so we must
- * pass the sound levels measured on the stream so we can see
- * the stream activity of the call.
- */
- int conferenceMemberCount = getConferenceMemberCount();
-
- if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3))
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
- return;
- }
- }
-
- fireStreamSoundLevelChanged(newLevel);
- }
-
-
- /**
- * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
- * Delivers the received audio levels to the
- * {@link ConferenceMembersSoundLevelListener}s registered with this
- * <tt>MediaAwareCallPeer</tt>..
- *
- * @param audioLevels the levels that we need to dispatch to all registered
- * <tt>ConferenceMemberSoundLevelListeners</tt>.
- */
- public void audioLevelsReceived(long[] audioLevels)
- {
- /*
- * When the local user/peer has organized a telephony conference
- * utilizing the Jitsi VideoBridge server-side technology, the server
- * will calculate the audio levels and not the client.
- */
- if (isJitsiVideoBridge())
- {
- long audioRemoteSSRC
- = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
-
- if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
- {
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- if (audioLevels[i] == audioRemoteSSRC)
- {
- fireStreamSoundLevelChanged((int) audioLevels[i + 1]);
- break;
- }
- }
- }
- }
-
- if (getConferenceMemberCount() == 0)
- return;
-
- Map<ConferenceMember, Integer> levelsMap
- = new HashMap<ConferenceMember, Integer>();
-
- for (int i = 0; i < audioLevels.length; i += 2)
- {
- ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
-
- if (mmbr != null)
- levelsMap.put(mmbr, (int) audioLevels[i + 1]);
- }
-
- synchronized (conferenceMembersSoundLevelListeners)
- {
- int conferenceMemberSoundLevelListenerCount
- = conferenceMembersSoundLevelListeners.size();
-
- if (conferenceMemberSoundLevelListenerCount > 0)
- {
- ConferenceMembersSoundLevelEvent ev
- = new ConferenceMembersSoundLevelEvent(this, levelsMap);
-
- for (int i = 0;
- i < conferenceMemberSoundLevelListenerCount;
- i++)
- {
- conferenceMembersSoundLevelListeners
- .get(i)
- .soundLevelChanged(ev);
- }
- }
- }
- }
-
- /**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerAdded(CallPeerEvent evt) {}
-
- /**
- * Does nothing.
- * @param evt the event.
- */
- public void callPeerRemoved(CallPeerEvent evt) {}
-
- /**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceFocusChanged(CallPeerConferenceEvent)}.
- *
- * @param evt ignored
- */
- public void conferenceFocusChanged(CallPeerConferenceEvent evt)
- {
- }
-
- /**
- * Called when this peer becomes a mixer. The method add removes this class
- * as the stream audio level listener for the media coming from this peer
- * because the levels it delivers no longer represent the level of a
- * particular member. The method also adds this class as a member (CSRC)
- * audio level listener.
- *
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the newly add member.
- */
- public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
- {
- if (getConferenceMemberCount() > 2)
- {
- /*
- * This peer is now a conference focus with more than three
- * participants. It means that this peer is mixing and sending us
- * audio for at least two separate participants. We therefore need
- * to switch from stream to CSRC level listening.
- */
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(null);
- mediaHandler.setCsrcAudioLevelListener(this);
- }
- }
-
- /**
- * Dummy implementation of {@link CallPeerConferenceListener
- * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}.
- *
- * @param ev the event
- */
- public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {};
-
- /**
- * Called when this peer stops being a mixer. The method add removes this
- * class as the stream audio level listener for the media coming from this
- * peer because the levels it delivers no longer represent the level of a
- * particular member. The method also adds this class as a member (CSRC)
- * audio level listener.
- *
- * @param conferenceEvent the event containing information (that we don't
- * really use) on the freshly removed member.
- */
- public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
- {
- if (getConferenceMemberCount() < 3)
- {
- /*
- * This call peer is no longer mixing audio from multiple sources
- * since there's only us and her in the call. We therefore need to
- * switch from CSRC to stream level listening.
- */
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- mediaHandler.setStreamAudioLevelListener(this);
- mediaHandler.setCsrcAudioLevelListener(null);
- }
- }
-
- /**
- * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
- * the <tt>SoundLevelListener</tt>s interested in the changes of the audio
- * stream received from the remote peer i.e. in
- * {@link #streamSoundLevelListeners}.
- *
- * @param newLevel the new value of the sound level to notify
- * <tt>streamSoundLevelListeners</tt> about
- */
- private void fireStreamSoundLevelChanged(int newLevel)
- {
- List<SoundLevelListener> streamSoundLevelListeners;
-
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- /*
- * Since the streamAudioLevelListeners field of this
- * MediaAwareCallPeer is implemented as a copy-on-write storage,
- * just get a reference to it and it should be safe to iterate over it
- * without ConcurrentModificationExceptions.
- */
- streamSoundLevelListeners = this.streamSoundLevelListeners;
- }
-
- if (streamSoundLevelListeners != null)
- {
- /*
- * Iterate over streamAudioLevelListeners using an index rather than
- * an Iterator in order to try to reduce the number of allocations
- * (as the number of audio level changes is expected to be very
- * large).
- */
- int streamSoundLevelListenerCount
- = streamSoundLevelListeners.size();
-
- for(int i = 0; i < streamSoundLevelListenerCount; i++)
- {
- streamSoundLevelListeners.get(i).soundLevelChanged(
- this,
- newLevel);
- }
- }
- }
-
- /**
- * Returns a reference to the call that this peer belongs to. Calls
- * are created by underlying telephony protocol implementations.
- *
- * @return a reference to the call containing this peer.
- */
- @Override
- public T getCall()
- {
- return call;
- }
-
- /**
- * The method returns an image representation of the call peer if one is
- * available.
- *
- * @return byte[] a byte array containing the image or null if no image is
- * available.
- */
- public byte[] getImage()
- {
- return image;
- }
-
- /**
- * Returns a reference to the <tt>CallPeerMediaHandler</tt> used by this
- * peer. The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
- * always corresponds to exactly one instance of
- * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
- * reasons of readability.
- *
- * @return a reference to the <tt>CallPeerMediaHandler</tt> instance that
- * this peer uses for media related tips and tricks.
- */
- public U getMediaHandler()
- {
- return mediaHandler;
- }
-
- /**
- * Returns a unique identifier representing this peer.
- *
- * @return an identifier representing this call peer.
- */
- public String getPeerID()
- {
- return peerID;
- }
-
- /**
- * Returns the protocol provider that this peer belongs to.
- *
- * @return a reference to the <tt>ProtocolProviderService</tt> that this
- * peer belongs to.
- */
- @Override
- public V getProtocolProvider()
- {
- return protocolProvider;
- }
-
- /**
- * Determines whether this <tt>CallPeer</tt> is participating in a telephony
- * conference organized by the local user/peer utilizing the Jitsi
- * VideoBridge server-side technology.
- *
- * @return <tt>true</tt> if this <tt>CallPeer</tt> is participating in a
- * telephony conference organized by the local user/peer utilizing the Jitsi
- * VideoBridge server-side technology; otherwise, <tt>false</tt>
- */
- public final boolean isJitsiVideoBridge()
- {
- Call call = getCall();
-
- if (call != null)
- {
- CallConference conference = call.getConference();
-
- if (conference != null)
- return conference.isJitsiVideoBridge();
- }
- return false;
- }
-
- /**
- * Determines whether we are currently streaming video toward whoever this
- * <tt>MediaAwareCallPeer</tt> represents.
- *
- * @return <tt>true</tt> if we are currently streaming video toward this
- * <tt>CallPeer</tt> and <tt>false</tt> otherwise.
- */
- public boolean isLocalVideoStreaming()
- {
- return getMediaHandler().isLocalVideoTransmissionEnabled();
- }
-
- /**
- * Determines whether the audio stream (if any) being sent to this
- * peer is mute.
- *
- * @return <tt>true</tt> if an audio stream is being sent to this
- * peer and it is currently mute; <tt>false</tt>, otherwise
- */
- @Override
- public boolean isMute()
- {
- return getMediaHandler().isMute();
- }
-
- /**
- * Logs <tt>message</tt> and <tt>cause</tt> and sets this <tt>peer</tt>'s
- * state to <tt>CallPeerState.FAILED</tt>
- *
- * @param message a message to log and display to the user.
- * @param throwable the exception that cause the error we are logging
- */
- public void logAndFail(String message, Throwable throwable)
- {
- logger.error(message, throwable);
- setState(CallPeerState.FAILED, message);
- }
-
- /**
- * Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
- * status of our media handler.
- */
- public void reevalLocalHoldStatus()
- {
- CallPeerState state = getState();
- boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
-
- if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
- {
- if (!locallyOnHold)
- setState(CallPeerState.CONNECTED);
- }
- else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
- {
- if (!locallyOnHold)
- setState(CallPeerState.ON_HOLD_REMOTELY);
- }
- else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
- {
- if (locallyOnHold)
- setState(CallPeerState.ON_HOLD_MUTUALLY);
- }
- else if (locallyOnHold)
- {
- setState(CallPeerState.ON_HOLD_LOCALLY);
- }
- }
-
- /**
- * Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
- * status of our media handler.
- */
- public void reevalRemoteHoldStatus()
- {
- boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
-
- CallPeerState state = getState();
- if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
- {
- if (remotelyOnHold)
- setState(CallPeerState.ON_HOLD_MUTUALLY);
- }
- else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
- {
- if (!remotelyOnHold)
- setState(CallPeerState.ON_HOLD_LOCALLY);
- }
- else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
- {
- if (!remotelyOnHold)
- setState(CallPeerState.CONNECTED);
- }
- else if (remotelyOnHold)
- {
- setState(CallPeerState.ON_HOLD_REMOTELY);
- }
- }
-
- /**
- * Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
- * list of listeners interested in and notified about changes in conference
- * members sound level.
- *
- * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
- * remove
- */
- public void removeConferenceMembersSoundLevelListener(
- ConferenceMembersSoundLevelListener listener)
- {
- synchronized (conferenceMembersSoundLevelListeners)
- {
- if (conferenceMembersSoundLevelListeners.remove(listener)
- && (conferenceMembersSoundLevelListeners.size() == 0))
- {
- // if this was the last listener then we also remove ourselves
- // as a CSRC audio level listener from the handler so that we
- // don't have to create new events and maps for something no one
- // is interested in.
- getMediaHandler().setCsrcAudioLevelListener(null);
- }
- }
- }
-
- /**
- * Removes a specific <tt>SoundLevelListener</tt> of the list of
- * listeners interested in and notified about changes in stream sound level
- * related information.
- *
- * @param listener the <tt>SoundLevelListener</tt> to remove
- */
- public void removeStreamSoundLevelListener(SoundLevelListener listener)
- {
- synchronized (streamSoundLevelListenersSyncRoot)
- {
- /*
- * Implement streamAudioLevelListeners as a copy-on-write storage so
- * that iterators over it can iterate over it without
- * ConcurrentModificationExceptions.
- */
- if (streamSoundLevelListeners != null)
- {
- streamSoundLevelListeners
- = new ArrayList<SoundLevelListener>(
- streamSoundLevelListeners);
- if (streamSoundLevelListeners.remove(listener)
- && streamSoundLevelListeners.isEmpty())
- streamSoundLevelListeners = null;
- }
-
- if ((streamSoundLevelListeners == null)
- || streamSoundLevelListeners.isEmpty())
- {
- // if this was the last listener then we also need to remove
- // ourselves as an audio level so that audio levels would only
- // be calculated if anyone is interested in receiving them.
- getMediaHandler().setStreamAudioLevelListener(null);
- }
- }
- }
-
- /**
- * Removes a specific <tt>PropertyChangeListener</tt> from the list of
- * listeners which get notified when the properties (e.g.
- * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
- * their values.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to no longer be
- * notified when the properties associated with the specified <tt>Call</tt>
- * change their values
- */
- public void removeVideoPropertyChangeListener(
- PropertyChangeListener listener)
- {
- if (listener != null)
- synchronized (videoPropertyChangeListeners)
- {
- /*
- * The video is part of the media-related functionality and thus
- * it is the responsibility of mediaHandler. So we're listening
- * to mediaHandler for video-related property changes and w're
- * re-firing them as originating from this instance. Make sure
- * that we're not listening to mediaHandler if noone is
- * interested in video-related property changes originating from
- * this instance.
- */
- if (videoPropertyChangeListeners.remove(listener)
- && videoPropertyChangeListeners.isEmpty()
- && (mediaHandlerPropertyChangeListener != null))
- {
-// getMediaHandler()
-// .removePropertyChangeListener(
-// mediaHandlerPropertyChangeListener);
- mediaHandlerPropertyChangeListener = null;
- }
- }
- }
-
- /**
- * Sets the security message associated with a failure/warning or
- * information coming from the encryption protocol.
- *
- * @param messageType the type of the message.
- * @param i18nMessage the message
- * @param severity severity level
- */
- public void securityMessageReceived(
- String messageType,
- String i18nMessage,
- int severity)
- {
- fireCallPeerSecurityMessageEvent(messageType,
- i18nMessage,
- severity);
- }
-
- /**
- * Indicates that the other party has timeouted replying to our
- * offer to secure the connection.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- * @param sender the security controller that caused the event
- */
- public void securityNegotiationStarted(
- MediaType mediaType,
- SrtpControl sender)
- {
- fireCallPeerSecurityNegotiationStartedEvent(
- new CallPeerSecurityNegotiationStartedEvent(
- this,
- toSessionType(mediaType),
- sender));
- }
-
- /**
- * Indicates that the other party has timeouted replying to our
- * offer to secure the connection.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- */
- public void securityTimeout(MediaType mediaType)
- {
- fireCallPeerSecurityTimeoutEvent(
- new CallPeerSecurityTimeoutEvent(
- this,
- toSessionType(mediaType)));
- }
-
- /**
- * Sets the security status to OFF for this call peer.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- */
- public void securityTurnedOff(MediaType mediaType)
- {
- // If this event has been triggered because of a call end event and the
- // call is already ended we don't need to alert the user for
- // security off.
- if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED))
- {
- fireCallPeerSecurityOffEvent(
- new CallPeerSecurityOffEvent(
- this,
- toSessionType(mediaType)));
- }
- }
-
- /**
- * Sets the security status to ON for this call peer.
- *
- * @param mediaType the <tt>MediaType</tt> of the call session
- * @param cipher the cipher
- * @param sender the security controller that caused the event
- */
- public void securityTurnedOn(
- MediaType mediaType,
- String cipher,
- SrtpControl sender)
- {
- getMediaHandler().startSrtpMultistream(sender);
- fireCallPeerSecurityOnEvent(
- new CallPeerSecurityOnEvent(
- this,
- toSessionType(mediaType),
- cipher,
- sender));
- }
-
- /**
- * Sets the call containing this peer.
- *
- * @param call the call that this call peer is participating in.
- */
- public void setCall(T call)
- {
- this.call = call;
- }
-
- /**
- * Sets the byte array containing an image representation (photo or picture)
- * of the call peer.
- *
- * @param image a byte array containing the image
- */
- public void setImage(byte[] image)
- {
- byte[] oldImage = getImage();
- this.image = image;
-
- //Fire the Event
- fireCallPeerChangeEvent(
- CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
- oldImage,
- image);
- }
-
- /**
- * Modifies the local media setup to reflect the requested setting for the
- * streaming of the local video and then re-invites the peer represented by
- * this class using a corresponding SDP description..
- *
- * @param allowed <tt>true</tt> if local video transmission is allowed and
- * <tt>false</tt> otherwise.
- *
- * @throws OperationFailedException if video initialization fails.
- */
- public void setLocalVideoAllowed(boolean allowed)
- throws OperationFailedException
- {
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
- {
- // Modify the local media setup to reflect the requested setting for
- // the streaming of the local video.
- mediaHandler.setLocalVideoTransmissionEnabled(allowed);
- }
- }
-
- /**
- * Sets a reference to the <tt>CallPeerMediaHandler</tt> used by this
- * peer. The media handler class handles all media management for a single
- * <tt>CallPeer</tt>. This includes initializing and configuring streams,
- * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
- * always corresponds to exactly one instance of
- * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
- * reasons of readability.
- *
- * @param mediaHandler a reference to the <tt>CallPeerMediaHandler</tt>
- * instance that this peer uses for media related tips and tricks.
- */
- protected void setMediaHandler(U mediaHandler)
- {
- this.mediaHandler = mediaHandler;
- }
-
- /**
- * Sets the mute property for this call peer.
- *
- * @param newMuteValue the new value of the mute property for this call peer
- */
- @Override
- public void setMute(boolean newMuteValue)
- {
- getMediaHandler().setMute(newMuteValue);
- super.setMute(newMuteValue);
- }
-
- /**
- * Sets the String that serves as a unique identifier of this
- * CallPeer.
- * @param peerID the ID of this call peer.
- */
- public void setPeerID(String peerID)
- {
- this.peerID = peerID;
- }
-
- /**
- * Overrides the parent set state method in order to make sure that we
- * close our media handler whenever we enter a disconnected state.
- *
- * @param newState the <tt>CallPeerState</tt> that we are about to enter and
- * that we pass to our predecessor.
- * @param reason a reason phrase explaining the state (e.g. if newState
- * indicates a failure) and that we pass to our predecessor.
- * @param reasonCode the code for the reason of the state change.
- */
- @Override
- public void setState(CallPeerState newState, String reason, int reasonCode)
- {
- // synchronized to mediaHandler if there are currently jobs of
- // initializing, configuring and starting streams (method processAnswer
- // of CallPeerMediaHandler) we won't set and fire the current state
- // to Disconnected. Before closing the mediaHandler is setting the state
- // in order to deliver states as quick as possible.
- CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
-
- synchronized(mediaHandler)
- {
- try
- {
- super.setState(newState, reason, reasonCode);
- }
- finally
- {
- // make sure whatever happens to close the media
- if (CallPeerState.DISCONNECTED.equals(newState)
- || CallPeerState.FAILED.equals(newState))
- mediaHandler.close();
- }
- }
- }
-
- /**
- * Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
- * @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
- */
- public ConferenceInfoDocument getLastConferenceInfoSent()
- {
- return lastConferenceInfoSent;
- }
-
- /**
- * Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
- * <tt>CallPeer</tt>.
- * @param confInfo the document to set.
- */
- public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
- {
- lastConferenceInfoSent = confInfo;
- }
-
- /**
- * Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- * @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- */
- public long getLastConferenceInfoSentTimestamp()
- {
- return lastConferenceInfoSentTimestamp;
- }
-
- /**
- * Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
- * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
- * <tt>CallPeer</tt>.
- * @param newTimestamp the time to set
- */
- public void setLastConferenceInfoSentTimestamp(long newTimestamp)
- {
- lastConferenceInfoSentTimestamp = newTimestamp;
- }
-
- /**
- * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- */
- public ConferenceInfoDocument getLastConferenceInfoReceived()
- {
- return lastConferenceInfoReceived;
- }
-
- /**
- * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
- * <tt>CallPeer</tt>.
- */
- public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
- {
- lastConferenceInfoReceived = confInfo;
- }
-
- /**
- * Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
- * sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
- * a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
- * @return
- */
- public int getLastConferenceInfoReceivedVersion()
- {
- return (lastConferenceInfoReceived == null)
- ? -1
- : lastConferenceInfoReceived.getVersion();
- }
-
- /**
- * Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
- * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
- * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
- * element corresponding to this <tt>CallPeer</tt>)
- *
- * @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
- * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
- * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
- * element corresponding to this <tt>CallPeer</tt>)
- */
- public abstract String getEntity();
-
- /**
- * Check whether a conference-info document is scheduled to be sent to
- * this <tt>CallPeer</tt> (i.e. there is a thread which will eventually
- * (after sleeping a certain amount of time) trigger a document to be sent)
- * @return <tt>true</tt> if there is a conference-info document scheduled
- * to be sent to this <tt>CallPeer</tt> and <tt>false</tt> otherwise.
- */
- public boolean isConfInfoScheduled()
- {
- synchronized (confInfoScheduledSyncRoot)
- {
- return confInfoScheduled;
- }
- }
-
- /**
- * Sets the property which indicates whether a conference-info document
- * is scheduled to be sent to this <tt>CallPeer</tt>.
- * @param confInfoScheduled
- */
- public void setConfInfoScheduled(boolean confInfoScheduled)
- {
- synchronized (confInfoScheduledSyncRoot)
- {
- this.confInfoScheduled = confInfoScheduled;
- }
- }
-
- /**
- * Returns the direction of the session for media of type <tt>mediaType</tt>
- * that we have with this <tt>CallPeer</tt>. This is the direction of the
- * session negotiated in the signaling protocol, and it may or may not
- * coincide with the direction of the media stream.
- * For example, if we are the focus of a videobridge conference and another
- * peer is sending video to us, we have a <tt>RECVONLY</tt> video stream,
- * but <tt>SENDONLY</tt> or <tt>SENDRECV</tt> (Jingle) sessions with the
- * rest of the conference members.
- * Should always return non-null.
- *
- * @param mediaType the <tt>MediaType</tt> to use
- * @return Returns the direction of the session for media of type
- * <tt>mediaType</tt> that we have with this <tt>CallPeer</tt>.
- */
- public abstract MediaDirection getDirection(MediaType mediaType);
-
- /**
- * {@inheritDoc}
- *
- * When a <tt>ConferenceMember</tt> is removed from a conference with a
- * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore,
- * if the <tt>ConferenceMember</tt> had an associated video SSRC, the stream
- * isn't be removed until it times out, leaving a blank video container in
- * the interface for a few seconds.
- * TODO: This works around the problem by removing the
- * <tt>ConferenceMember</tt>'s <tt>ReceiveStream</tt> when the
- * <tt>ConferenceMember</tt> is removed. The proper solution is to ensure
- * that RTCP BYEs are sent whenever necessary, and when it is deployed this
- * code should be removed.
- *
- * @param conferenceMember a <tt>ConferenceMember</tt> to be removed from
- * the list of <tt>ConferenceMember</tt> reported by this peer. If the
- * specified <tt>ConferenceMember</tt> is no contained in the list, no event
- */
- @Override
- public void removeConferenceMember(ConferenceMember conferenceMember)
- {
- MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO);
- if (videoStream != null)
- videoStream.removeReceiveStreamForSsrc(
- conferenceMember.getVideoSsrc());
-
- super.removeConferenceMember(conferenceMember);
- }
-
- /**
- * Converts a specific <tt>MediaType</tt> into a <tt>sessionType</tt> value
- * in the terms of the <tt>CallPeerSecurityStatusEvent</tt> class.
- *
- * @param mediaType the <tt>MediaType</tt> to be converted
- * @return the <tt>sessionType</tt> value in the terms of the
- * <tt>CallPeerSecurityStatusEvent</tt> class that is equivalent to the
- * specified <tt>mediaType</tt>
- */
- private static int toSessionType(MediaType mediaType)
- {
- switch (mediaType)
- {
- case AUDIO:
- return CallPeerSecurityStatusEvent.AUDIO_SESSION;
- case VIDEO:
- return CallPeerSecurityStatusEvent.VIDEO_SESSION;
- default:
- throw new IllegalArgumentException("mediaType");
- }
- }
-}
+/*
+ * 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.service.protocol.media;
+
+import java.beans.*;
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.neomedia.*;
+import org.jitsi.service.neomedia.event.*;
+import org.jitsi.service.protocol.event.*;
+
+/**
+ * A utility class implementing media control code shared between current
+ * telephony implementations. This class is only meant for use by protocol
+ * implementations and should/could not be accessed by bundles that are simply
+ * using the telephony functionalities.
+ *
+ * @param <T> the peer extension class like for example <tt>CallSipImpl</tt>
+ * or <tt>CallJabberImpl</tt>
+ * @param <U> the media handler extension class like for example
+ * <tt>CallPeerMediaHandlerSipImpl</tt> or
+ * <tt>CallPeerMediaHandlerJabberImpl</tt>
+ * @param <V> the provider extension class like for example
+ * <tt>ProtocolProviderServiceSipImpl</tt> or
+ * <tt>ProtocolProviderServiceJabberImpl</tt>
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Boris Grozev
+ */
+public abstract class MediaAwareCallPeer
+ <T extends MediaAwareCall<?, ?, V>,
+ U extends CallPeerMediaHandler<?>,
+ V extends ProtocolProviderService>
+ extends AbstractCallPeer<T, V>
+ implements SrtpListener,
+ CallPeerConferenceListener,
+ CsrcAudioLevelListener,
+ SimpleAudioLevelListener
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>MediaAwareCallPeer</tt> class and its
+ * instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(MediaAwareCallPeer.class);
+
+ /**
+ * The call this peer belongs to.
+ */
+ private T call;
+
+ /**
+ * The listeners registered for level changes in the audio of participants
+ * that this peer might be mixing and that we are not directly communicating
+ * with.
+ */
+ private final List<ConferenceMembersSoundLevelListener>
+ conferenceMembersSoundLevelListeners
+ = new ArrayList<ConferenceMembersSoundLevelListener>();
+
+ /**
+ * A byte array containing the image/photo representing the call peer.
+ */
+ private byte[] image;
+
+ /**
+ * The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>always
+ * corresponds to exactly one instance of <tt>CallPeerMediaHandler</tt> and
+ * both classes are only separated for reasons of readability.
+ */
+ private U mediaHandler;
+
+ /**
+ * The <tt>PropertyChangeListener</tt> which listens to
+ * {@link CallPeerMediaHandler} for changes in the values of its properties.
+ */
+ private PropertyChangeListener mediaHandlerPropertyChangeListener;
+
+ /**
+ * A string uniquely identifying the peer.
+ */
+ private String peerID;
+
+ /**
+ * The protocol provider that this peer belongs to.
+ */
+ private final V protocolProvider;
+
+ /**
+ * The list of <tt>SoundLevelListener</tt>s interested in level changes in
+ * the audio we are getting from the remote peer.
+ * <p>
+ * It is implemented as a copy-on-write storage because the number of
+ * additions and removals of <tt>SoundLevelListener</tt>s is expected to be
+ * far smaller than the number of audio level changes. The access to it is
+ * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}.
+ * </p>
+ */
+ private List<SoundLevelListener> streamSoundLevelListeners;
+
+ /**
+ * The <tt>Object</tt> to synchronize the access to
+ * {@link #streamSoundLevelListeners}.
+ */
+ private final Object streamSoundLevelListenersSyncRoot = new Object();
+
+ /**
+ * The <tt>List</tt> of <tt>PropertyChangeListener</tt>s listening to this
+ * <tt>CallPeer</tt> for changes in the values of its properties related to
+ * video.
+ */
+ private final List<PropertyChangeListener> videoPropertyChangeListeners
+ = new LinkedList<PropertyChangeListener>();
+
+ /**
+ * Represents the last Conference Information (RFC4575) document sent to
+ * this <tt>CallPeer</tt>. This is always a document with state "full", even
+ * if the last document actually sent was a "partial"
+ */
+ private ConferenceInfoDocument lastConferenceInfoSent = null;
+
+ /**
+ * The time (as obtained by <tt>System.currentTimeMillis()</tt>) at which
+ * a Conference Information (RFC4575) document was last sent to this
+ * <tt>CallPeer</tt>.
+ */
+ private long lastConferenceInfoSentTimestamp = -1;
+
+ /**
+ * The last Conference Information (RFC4575) document sent to us by this
+ * <tt>CallPeer</tt>. This is always a document with state "full", which is
+ * only gets updated by "partial" or "deleted" documents.
+ */
+ private ConferenceInfoDocument lastConferenceInfoReceived = null;
+
+ /**
+ * Whether a conference-info document has been scheduled to be sent to this
+ * <tt>CallPeer</tt>
+ */
+ private boolean confInfoScheduled = false;
+
+ /**
+ * Synchronization object for confInfoScheduled
+ */
+ private final Object confInfoScheduledSyncRoot = new Object();
+
+ /**
+ * Creates a new call peer with address <tt>peerAddress</tt>.
+ *
+ * @param owningCall the call that contains this call peer.
+ */
+ public MediaAwareCallPeer(T owningCall)
+ {
+ this.call = owningCall;
+ this.protocolProvider = owningCall.getProtocolProvider();
+
+ // create the uid
+ this.peerID
+ = String.valueOf(System.currentTimeMillis())
+ + String.valueOf(hashCode());
+
+ // we listen for events when the call will become focus or not
+ // of a conference so we will add or remove our sound level listeners
+ super.addCallPeerConferenceListener(this);
+ }
+
+ /**
+ * Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
+ * of listeners interested in and notified about changes in conference
+ * members sound level.
+ *
+ * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
+ * @throws NullPointerException if <tt>listener</tt> is <tt>null</tt>
+ */
+ public void addConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
+ {
+ /*
+ * XXX The uses of the method at the time of this writing rely on being
+ * able to add a null listener so make it a no-op here.
+ */
+ if (listener == null)
+ return;
+
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ if (conferenceMembersSoundLevelListeners.size() == 0)
+ {
+ // if this is the first listener that's being registered with
+ // us, we also need to register ourselves as a CSRC audio level
+ // listener with the media handler.
+ getMediaHandler().setCsrcAudioLevelListener(this);
+ }
+ conferenceMembersSoundLevelListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a specific <tt>SoundLevelListener</tt> to the list of listeners
+ * interested in and notified about changes in the sound level of the audio
+ * sent by the remote party. When the first listener is being registered
+ * the method also registers its single listener with the media handler so
+ * that it would receive level change events and delegate them to the
+ * listeners that have registered with us.
+ *
+ * @param listener the <tt>SoundLevelListener</tt> to add
+ */
+ public void addStreamSoundLevelListener(SoundLevelListener listener)
+ {
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ if (isJitsiVideobridge())
+ {
+ /*
+ * When the local user/peer has organized a telephony
+ * conference utilizing the Jitsi Videobridge server-side
+ * technology, the server will calculate the audio levels
+ * and not the client.
+ */
+ mediaHandler.setCsrcAudioLevelListener(this);
+ }
+ else
+ {
+ /*
+ * If this is the first listener that's being registered
+ * with us, we also need to register ourselves as an audio
+ * level listener with the media handler. We do this so that
+ * audio levels would only be calculated if anyone is
+ * interested in receiving them.
+ */
+ mediaHandler.setStreamAudioLevelListener(this);
+ }
+ }
+
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate without
+ * ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners
+ = (streamSoundLevelListeners == null)
+ ? new ArrayList<SoundLevelListener>()
+ : new ArrayList<SoundLevelListener>(
+ streamSoundLevelListeners);
+ streamSoundLevelListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a specific <tt>PropertyChangeListener</tt> to the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
+ * their values.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to be notified
+ * when the properties associated with the specified <tt>Call</tt> change
+ * their values
+ */
+ public void addVideoPropertyChangeListener(PropertyChangeListener listener)
+ {
+ if (listener == null)
+ throw new NullPointerException("listener");
+
+ synchronized (videoPropertyChangeListeners)
+ {
+ /*
+ * The video is part of the media-related functionality and thus it
+ * is the responsibility of mediaHandler. So listen to mediaHandler
+ * for video-related property changes and re-fire them as
+ * originating from this instance.
+ */
+ if (!videoPropertyChangeListeners.contains(listener)
+ && videoPropertyChangeListeners.add(listener)
+ && (mediaHandlerPropertyChangeListener == null))
+ {
+ mediaHandlerPropertyChangeListener
+ = new PropertyChangeListener()
+ {
+ public void propertyChange(PropertyChangeEvent event)
+ {
+ Iterable<PropertyChangeListener> listeners;
+
+ synchronized (videoPropertyChangeListeners)
+ {
+ listeners
+ = new LinkedList<PropertyChangeListener>(
+ videoPropertyChangeListeners);
+ }
+
+ PropertyChangeEvent thisEvent
+ = new PropertyChangeEvent(
+ this,
+ event.getPropertyName(),
+ event.getOldValue(),
+ event.getNewValue());
+
+ for (PropertyChangeListener listener : listeners)
+ listener.propertyChange(thisEvent);
+ }
+ };
+ getMediaHandler()
+ .addPropertyChangeListener(
+ mediaHandlerPropertyChangeListener);
+ }
+ }
+ }
+
+ /**
+ * Notified by its very majesty the media service about changes in the audio
+ * level of the stream coming from this peer, the method generates the
+ * corresponding events and delivers them to the listeners that have
+ * registered here.
+ *
+ * @param newLevel the new audio level of the audio stream received from the
+ * remote peer
+ */
+ public void audioLevelChanged(int newLevel)
+ {
+ /*
+ * If we're in a conference in which this CallPeer is the focus and
+ * we're the only member in it besides the focus, we will not receive
+ * audio levels in the RTP and our media will instead measure the audio
+ * levels of the received media. In order to make the UI oblivious of
+ * the difference, we have to translate the event to the appropriate
+ * type of listener.
+ *
+ * We may end up in a conference call with 0 members if the server
+ * for some reason doesn't support sip conference (our subscribes
+ * doesn't go to the focus of the conference) and so we must
+ * pass the sound levels measured on the stream so we can see
+ * the stream activity of the call.
+ */
+ int conferenceMemberCount = getConferenceMemberCount();
+
+ if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3))
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel });
+ return;
+ }
+ }
+
+ fireStreamSoundLevelChanged(newLevel);
+ }
+
+
+ /**
+ * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}.
+ * Delivers the received audio levels to the
+ * {@link ConferenceMembersSoundLevelListener}s registered with this
+ * <tt>MediaAwareCallPeer</tt>..
+ *
+ * @param audioLevels the levels that we need to dispatch to all registered
+ * <tt>ConferenceMemberSoundLevelListeners</tt>.
+ */
+ public void audioLevelsReceived(long[] audioLevels)
+ {
+ /*
+ * When the local user/peer has organized a telephony conference
+ * utilizing the Jitsi Videobridge server-side technology, the server
+ * will calculate the audio levels and not the client.
+ */
+ if (isJitsiVideobridge())
+ {
+ long audioRemoteSSRC
+ = getMediaHandler().getRemoteSSRC(MediaType.AUDIO);
+
+ if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN)
+ {
+ for (int i = 0; i < audioLevels.length; i += 2)
+ {
+ if (audioLevels[i] == audioRemoteSSRC)
+ {
+ fireStreamSoundLevelChanged((int) audioLevels[i + 1]);
+ break;
+ }
+ }
+ }
+ }
+
+ if (getConferenceMemberCount() == 0)
+ return;
+
+ Map<ConferenceMember, Integer> levelsMap
+ = new HashMap<ConferenceMember, Integer>();
+
+ for (int i = 0; i < audioLevels.length; i += 2)
+ {
+ ConferenceMember mmbr = findConferenceMember(audioLevels[i]);
+
+ if (mmbr != null)
+ levelsMap.put(mmbr, (int) audioLevels[i + 1]);
+ }
+
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ int conferenceMemberSoundLevelListenerCount
+ = conferenceMembersSoundLevelListeners.size();
+
+ if (conferenceMemberSoundLevelListenerCount > 0)
+ {
+ ConferenceMembersSoundLevelEvent ev
+ = new ConferenceMembersSoundLevelEvent(this, levelsMap);
+
+ for (int i = 0;
+ i < conferenceMemberSoundLevelListenerCount;
+ i++)
+ {
+ conferenceMembersSoundLevelListeners
+ .get(i)
+ .soundLevelChanged(ev);
+ }
+ }
+ }
+ }
+
+ /**
+ * Does nothing.
+ * @param evt the event.
+ */
+ public void callPeerAdded(CallPeerEvent evt) {}
+
+ /**
+ * Does nothing.
+ * @param evt the event.
+ */
+ public void callPeerRemoved(CallPeerEvent evt) {}
+
+ /**
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceFocusChanged(CallPeerConferenceEvent)}.
+ *
+ * @param evt ignored
+ */
+ public void conferenceFocusChanged(CallPeerConferenceEvent evt)
+ {
+ }
+
+ /**
+ * Called when this peer becomes a mixer. The method add removes this class
+ * as the stream audio level listener for the media coming from this peer
+ * because the levels it delivers no longer represent the level of a
+ * particular member. The method also adds this class as a member (CSRC)
+ * audio level listener.
+ *
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the newly add member.
+ */
+ public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent)
+ {
+ if (getConferenceMemberCount() > 2)
+ {
+ /*
+ * This peer is now a conference focus with more than three
+ * participants. It means that this peer is mixing and sending us
+ * audio for at least two separate participants. We therefore need
+ * to switch from stream to CSRC level listening.
+ */
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ mediaHandler.setStreamAudioLevelListener(null);
+ mediaHandler.setCsrcAudioLevelListener(this);
+ }
+ }
+
+ /**
+ * Dummy implementation of {@link CallPeerConferenceListener
+ * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}.
+ *
+ * @param ev the event
+ */
+ public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {};
+
+ /**
+ * Called when this peer stops being a mixer. The method add removes this
+ * class as the stream audio level listener for the media coming from this
+ * peer because the levels it delivers no longer represent the level of a
+ * particular member. The method also adds this class as a member (CSRC)
+ * audio level listener.
+ *
+ * @param conferenceEvent the event containing information (that we don't
+ * really use) on the freshly removed member.
+ */
+ public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent)
+ {
+ if (getConferenceMemberCount() < 3)
+ {
+ /*
+ * This call peer is no longer mixing audio from multiple sources
+ * since there's only us and her in the call. We therefore need to
+ * switch from CSRC to stream level listening.
+ */
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ mediaHandler.setStreamAudioLevelListener(this);
+ mediaHandler.setCsrcAudioLevelListener(null);
+ }
+ }
+
+ /**
+ * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on
+ * the <tt>SoundLevelListener</tt>s interested in the changes of the audio
+ * stream received from the remote peer i.e. in
+ * {@link #streamSoundLevelListeners}.
+ *
+ * @param newLevel the new value of the sound level to notify
+ * <tt>streamSoundLevelListeners</tt> about
+ */
+ private void fireStreamSoundLevelChanged(int newLevel)
+ {
+ List<SoundLevelListener> streamSoundLevelListeners;
+
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ /*
+ * Since the streamAudioLevelListeners field of this
+ * MediaAwareCallPeer is implemented as a copy-on-write storage,
+ * just get a reference to it and it should be safe to iterate over it
+ * without ConcurrentModificationExceptions.
+ */
+ streamSoundLevelListeners = this.streamSoundLevelListeners;
+ }
+
+ if (streamSoundLevelListeners != null)
+ {
+ /*
+ * Iterate over streamAudioLevelListeners using an index rather than
+ * an Iterator in order to try to reduce the number of allocations
+ * (as the number of audio level changes is expected to be very
+ * large).
+ */
+ int streamSoundLevelListenerCount
+ = streamSoundLevelListeners.size();
+
+ for(int i = 0; i < streamSoundLevelListenerCount; i++)
+ {
+ streamSoundLevelListeners.get(i).soundLevelChanged(
+ this,
+ newLevel);
+ }
+ }
+ }
+
+ /**
+ * Returns a reference to the call that this peer belongs to. Calls
+ * are created by underlying telephony protocol implementations.
+ *
+ * @return a reference to the call containing this peer.
+ */
+ @Override
+ public T getCall()
+ {
+ return call;
+ }
+
+ /**
+ * The method returns an image representation of the call peer if one is
+ * available.
+ *
+ * @return byte[] a byte array containing the image or null if no image is
+ * available.
+ */
+ public byte[] getImage()
+ {
+ return image;
+ }
+
+ /**
+ * Returns a reference to the <tt>CallPeerMediaHandler</tt> used by this
+ * peer. The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
+ * always corresponds to exactly one instance of
+ * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
+ * reasons of readability.
+ *
+ * @return a reference to the <tt>CallPeerMediaHandler</tt> instance that
+ * this peer uses for media related tips and tricks.
+ */
+ public U getMediaHandler()
+ {
+ return mediaHandler;
+ }
+
+ /**
+ * Returns a unique identifier representing this peer.
+ *
+ * @return an identifier representing this call peer.
+ */
+ public String getPeerID()
+ {
+ return peerID;
+ }
+
+ /**
+ * Returns the protocol provider that this peer belongs to.
+ *
+ * @return a reference to the <tt>ProtocolProviderService</tt> that this
+ * peer belongs to.
+ */
+ @Override
+ public V getProtocolProvider()
+ {
+ return protocolProvider;
+ }
+
+ /**
+ * Determines whether this <tt>CallPeer</tt> is participating in a telephony
+ * conference organized by the local user/peer utilizing the Jitsi
+ * Videobridge server-side technology.
+ *
+ * @return <tt>true</tt> if this <tt>CallPeer</tt> is participating in a
+ * telephony conference organized by the local user/peer utilizing the Jitsi
+ * Videobridge server-side technology; otherwise, <tt>false</tt>
+ */
+ public final boolean isJitsiVideobridge()
+ {
+ Call call = getCall();
+
+ if (call != null)
+ {
+ CallConference conference = call.getConference();
+
+ if (conference != null)
+ return conference.isJitsiVideobridge();
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether we are currently streaming video toward whoever this
+ * <tt>MediaAwareCallPeer</tt> represents.
+ *
+ * @return <tt>true</tt> if we are currently streaming video toward this
+ * <tt>CallPeer</tt> and <tt>false</tt> otherwise.
+ */
+ public boolean isLocalVideoStreaming()
+ {
+ return getMediaHandler().isLocalVideoTransmissionEnabled();
+ }
+
+ /**
+ * Determines whether the audio stream (if any) being sent to this
+ * peer is mute.
+ *
+ * @return <tt>true</tt> if an audio stream is being sent to this
+ * peer and it is currently mute; <tt>false</tt>, otherwise
+ */
+ @Override
+ public boolean isMute()
+ {
+ return getMediaHandler().isMute();
+ }
+
+ /**
+ * Logs <tt>message</tt> and <tt>cause</tt> and sets this <tt>peer</tt>'s
+ * state to <tt>CallPeerState.FAILED</tt>
+ *
+ * @param message a message to log and display to the user.
+ * @param throwable the exception that cause the error we are logging
+ */
+ public void logAndFail(String message, Throwable throwable)
+ {
+ logger.error(message, throwable);
+ setState(CallPeerState.FAILED, message);
+ }
+
+ /**
+ * Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
+ * status of our media handler.
+ */
+ public void reevalLocalHoldStatus()
+ {
+ CallPeerState state = getState();
+ boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
+
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
+ {
+ if (!locallyOnHold)
+ setState(CallPeerState.CONNECTED);
+ }
+ else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
+ {
+ if (!locallyOnHold)
+ setState(CallPeerState.ON_HOLD_REMOTELY);
+ }
+ else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
+ {
+ if (locallyOnHold)
+ setState(CallPeerState.ON_HOLD_MUTUALLY);
+ }
+ else if (locallyOnHold)
+ {
+ setState(CallPeerState.ON_HOLD_LOCALLY);
+ }
+ }
+
+ /**
+ * Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
+ * status of our media handler.
+ */
+ public void reevalRemoteHoldStatus()
+ {
+ boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
+
+ CallPeerState state = getState();
+ if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
+ {
+ if (remotelyOnHold)
+ setState(CallPeerState.ON_HOLD_MUTUALLY);
+ }
+ else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
+ {
+ if (!remotelyOnHold)
+ setState(CallPeerState.ON_HOLD_LOCALLY);
+ }
+ else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
+ {
+ if (!remotelyOnHold)
+ setState(CallPeerState.CONNECTED);
+ }
+ else if (remotelyOnHold)
+ {
+ setState(CallPeerState.ON_HOLD_REMOTELY);
+ }
+ }
+
+ /**
+ * Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
+ * list of listeners interested in and notified about changes in conference
+ * members sound level.
+ *
+ * @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
+ * remove
+ */
+ public void removeConferenceMembersSoundLevelListener(
+ ConferenceMembersSoundLevelListener listener)
+ {
+ synchronized (conferenceMembersSoundLevelListeners)
+ {
+ if (conferenceMembersSoundLevelListeners.remove(listener)
+ && (conferenceMembersSoundLevelListeners.size() == 0))
+ {
+ // if this was the last listener then we also remove ourselves
+ // as a CSRC audio level listener from the handler so that we
+ // don't have to create new events and maps for something no one
+ // is interested in.
+ getMediaHandler().setCsrcAudioLevelListener(null);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>SoundLevelListener</tt> of the list of
+ * listeners interested in and notified about changes in stream sound level
+ * related information.
+ *
+ * @param listener the <tt>SoundLevelListener</tt> to remove
+ */
+ public void removeStreamSoundLevelListener(SoundLevelListener listener)
+ {
+ synchronized (streamSoundLevelListenersSyncRoot)
+ {
+ /*
+ * Implement streamAudioLevelListeners as a copy-on-write storage so
+ * that iterators over it can iterate over it without
+ * ConcurrentModificationExceptions.
+ */
+ if (streamSoundLevelListeners != null)
+ {
+ streamSoundLevelListeners
+ = new ArrayList<SoundLevelListener>(
+ streamSoundLevelListeners);
+ if (streamSoundLevelListeners.remove(listener)
+ && streamSoundLevelListeners.isEmpty())
+ streamSoundLevelListeners = null;
+ }
+
+ if ((streamSoundLevelListeners == null)
+ || streamSoundLevelListeners.isEmpty())
+ {
+ // if this was the last listener then we also need to remove
+ // ourselves as an audio level so that audio levels would only
+ // be calculated if anyone is interested in receiving them.
+ getMediaHandler().setStreamAudioLevelListener(null);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific <tt>PropertyChangeListener</tt> from the list of
+ * listeners which get notified when the properties (e.g.
+ * LOCAL_VIDEO_STREAMING) associated with this <tt>CallPeer</tt> change
+ * their values.
+ *
+ * @param listener the <tt>PropertyChangeListener</tt> to no longer be
+ * notified when the properties associated with the specified <tt>Call</tt>
+ * change their values
+ */
+ public void removeVideoPropertyChangeListener(
+ PropertyChangeListener listener)
+ {
+ if (listener != null)
+ synchronized (videoPropertyChangeListeners)
+ {
+ /*
+ * The video is part of the media-related functionality and thus
+ * it is the responsibility of mediaHandler. So we're listening
+ * to mediaHandler for video-related property changes and w're
+ * re-firing them as originating from this instance. Make sure
+ * that we're not listening to mediaHandler if noone is
+ * interested in video-related property changes originating from
+ * this instance.
+ */
+ if (videoPropertyChangeListeners.remove(listener)
+ && videoPropertyChangeListeners.isEmpty()
+ && (mediaHandlerPropertyChangeListener != null))
+ {
+// getMediaHandler()
+// .removePropertyChangeListener(
+// mediaHandlerPropertyChangeListener);
+ mediaHandlerPropertyChangeListener = null;
+ }
+ }
+ }
+
+ /**
+ * Sets the security message associated with a failure/warning or
+ * information coming from the encryption protocol.
+ *
+ * @param messageType the type of the message.
+ * @param i18nMessage the message
+ * @param severity severity level
+ */
+ public void securityMessageReceived(
+ String messageType,
+ String i18nMessage,
+ int severity)
+ {
+ fireCallPeerSecurityMessageEvent(messageType,
+ i18nMessage,
+ severity);
+ }
+
+ /**
+ * Indicates that the other party has timeouted replying to our
+ * offer to secure the connection.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ * @param sender the security controller that caused the event
+ */
+ public void securityNegotiationStarted(
+ MediaType mediaType,
+ SrtpControl sender)
+ {
+ fireCallPeerSecurityNegotiationStartedEvent(
+ new CallPeerSecurityNegotiationStartedEvent(
+ this,
+ toSessionType(mediaType),
+ sender));
+ }
+
+ /**
+ * Indicates that the other party has timeouted replying to our
+ * offer to secure the connection.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ */
+ public void securityTimeout(MediaType mediaType)
+ {
+ fireCallPeerSecurityTimeoutEvent(
+ new CallPeerSecurityTimeoutEvent(
+ this,
+ toSessionType(mediaType)));
+ }
+
+ /**
+ * Sets the security status to OFF for this call peer.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ */
+ public void securityTurnedOff(MediaType mediaType)
+ {
+ // If this event has been triggered because of a call end event and the
+ // call is already ended we don't need to alert the user for
+ // security off.
+ if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED))
+ {
+ fireCallPeerSecurityOffEvent(
+ new CallPeerSecurityOffEvent(
+ this,
+ toSessionType(mediaType)));
+ }
+ }
+
+ /**
+ * Sets the security status to ON for this call peer.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the call session
+ * @param cipher the cipher
+ * @param sender the security controller that caused the event
+ */
+ public void securityTurnedOn(
+ MediaType mediaType,
+ String cipher,
+ SrtpControl sender)
+ {
+ getMediaHandler().startSrtpMultistream(sender);
+ fireCallPeerSecurityOnEvent(
+ new CallPeerSecurityOnEvent(
+ this,
+ toSessionType(mediaType),
+ cipher,
+ sender));
+ }
+
+ /**
+ * Sets the call containing this peer.
+ *
+ * @param call the call that this call peer is participating in.
+ */
+ public void setCall(T call)
+ {
+ this.call = call;
+ }
+
+ /**
+ * Sets the byte array containing an image representation (photo or picture)
+ * of the call peer.
+ *
+ * @param image a byte array containing the image
+ */
+ public void setImage(byte[] image)
+ {
+ byte[] oldImage = getImage();
+ this.image = image;
+
+ //Fire the Event
+ fireCallPeerChangeEvent(
+ CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE,
+ oldImage,
+ image);
+ }
+
+ /**
+ * Modifies the local media setup to reflect the requested setting for the
+ * streaming of the local video and then re-invites the peer represented by
+ * this class using a corresponding SDP description..
+ *
+ * @param allowed <tt>true</tt> if local video transmission is allowed and
+ * <tt>false</tt> otherwise.
+ *
+ * @throws OperationFailedException if video initialization fails.
+ */
+ public void setLocalVideoAllowed(boolean allowed)
+ throws OperationFailedException
+ {
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed)
+ {
+ // Modify the local media setup to reflect the requested setting for
+ // the streaming of the local video.
+ mediaHandler.setLocalVideoTransmissionEnabled(allowed);
+ }
+ }
+
+ /**
+ * Sets a reference to the <tt>CallPeerMediaHandler</tt> used by this
+ * peer. The media handler class handles all media management for a single
+ * <tt>CallPeer</tt>. This includes initializing and configuring streams,
+ * generating SDP, handling ICE, etc. One instance of <tt>CallPeer</tt>
+ * always corresponds to exactly one instance of
+ * <tt>CallPeerMediaHandler</tt> and both classes are only separated for
+ * reasons of readability.
+ *
+ * @param mediaHandler a reference to the <tt>CallPeerMediaHandler</tt>
+ * instance that this peer uses for media related tips and tricks.
+ */
+ protected void setMediaHandler(U mediaHandler)
+ {
+ this.mediaHandler = mediaHandler;
+ }
+
+ /**
+ * Sets the mute property for this call peer.
+ *
+ * @param newMuteValue the new value of the mute property for this call peer
+ */
+ @Override
+ public void setMute(boolean newMuteValue)
+ {
+ getMediaHandler().setMute(newMuteValue);
+ super.setMute(newMuteValue);
+ }
+
+ /**
+ * Sets the String that serves as a unique identifier of this
+ * CallPeer.
+ * @param peerID the ID of this call peer.
+ */
+ public void setPeerID(String peerID)
+ {
+ this.peerID = peerID;
+ }
+
+ /**
+ * Overrides the parent set state method in order to make sure that we
+ * close our media handler whenever we enter a disconnected state.
+ *
+ * @param newState the <tt>CallPeerState</tt> that we are about to enter and
+ * that we pass to our predecessor.
+ * @param reason a reason phrase explaining the state (e.g. if newState
+ * indicates a failure) and that we pass to our predecessor.
+ * @param reasonCode the code for the reason of the state change.
+ */
+ @Override
+ public void setState(CallPeerState newState, String reason, int reasonCode)
+ {
+ // synchronized to mediaHandler if there are currently jobs of
+ // initializing, configuring and starting streams (method processAnswer
+ // of CallPeerMediaHandler) we won't set and fire the current state
+ // to Disconnected. Before closing the mediaHandler is setting the state
+ // in order to deliver states as quick as possible.
+ CallPeerMediaHandler<?> mediaHandler = getMediaHandler();
+
+ synchronized(mediaHandler)
+ {
+ try
+ {
+ super.setState(newState, reason, reasonCode);
+ }
+ finally
+ {
+ // make sure whatever happens to close the media
+ if (CallPeerState.DISCONNECTED.equals(newState)
+ || CallPeerState.FAILED.equals(newState))
+ mediaHandler.close();
+ }
+ }
+ }
+
+ /**
+ * Returns the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
+ * @return the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>. It is a document with state <tt>full</tt>
+ */
+ public ConferenceInfoDocument getLastConferenceInfoSent()
+ {
+ return lastConferenceInfoSent;
+ }
+
+ /**
+ * Sets the last <tt>ConferenceInfoDocument</tt> sent by us to this
+ * <tt>CallPeer</tt>.
+ * @param confInfo the document to set.
+ */
+ public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo)
+ {
+ lastConferenceInfoSent = confInfo;
+ }
+
+ /**
+ * Gets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ * @return the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ */
+ public long getLastConferenceInfoSentTimestamp()
+ {
+ return lastConferenceInfoSentTimestamp;
+ }
+
+ /**
+ * Sets the time (as obtained by <tt>System.currentTimeMillis()</tt>)
+ * at which we last sent a <tt>ConferenceInfoDocument</tt> to this
+ * <tt>CallPeer</tt>.
+ * @param newTimestamp the time to set
+ */
+ public void setLastConferenceInfoSentTimestamp(long newTimestamp)
+ {
+ lastConferenceInfoSentTimestamp = newTimestamp;
+ }
+
+ /**
+ * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ */
+ public ConferenceInfoDocument getLastConferenceInfoReceived()
+ {
+ return lastConferenceInfoReceived;
+ }
+
+ /**
+ * Gets the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ * @return the last <tt>ConferenceInfoDocument</tt> sent to us by this
+ * <tt>CallPeer</tt>.
+ */
+ public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo)
+ {
+ lastConferenceInfoReceived = confInfo;
+ }
+
+ /**
+ * Gets the <tt>version</tt> of the last <tt>ConferenceInfoDocument</tt>
+ * sent to us by this <tt>CallPeer</tt>, or -1 if we haven't (yet) received
+ * a <tt>ConferenceInformationDocument</tt> from this <tt>CallPeer</tt>.
+ * @return
+ */
+ public int getLastConferenceInfoReceivedVersion()
+ {
+ return (lastConferenceInfoReceived == null)
+ ? -1
+ : lastConferenceInfoReceived.getVersion();
+ }
+
+ /**
+ * Gets the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
+ * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
+ * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
+ * element corresponding to this <tt>CallPeer</tt>)
+ *
+ * @return the <tt>String</tt> to be used for this <tt>CallPeer</tt> when
+ * we describe it in a <tt>ConferenceInfoDocument</tt> (e.g. the
+ * <tt>entity</tt> key attribute which to use for the <tt>user</tt>
+ * element corresponding to this <tt>CallPeer</tt>)
+ */
+ public abstract String getEntity();
+
+ /**
+ * Check whether a conference-info document is scheduled to be sent to
+ * this <tt>CallPeer</tt> (i.e. there is a thread which will eventually
+ * (after sleeping a certain amount of time) trigger a document to be sent)
+ * @return <tt>true</tt> if there is a conference-info document scheduled
+ * to be sent to this <tt>CallPeer</tt> and <tt>false</tt> otherwise.
+ */
+ public boolean isConfInfoScheduled()
+ {
+ synchronized (confInfoScheduledSyncRoot)
+ {
+ return confInfoScheduled;
+ }
+ }
+
+ /**
+ * Sets the property which indicates whether a conference-info document
+ * is scheduled to be sent to this <tt>CallPeer</tt>.
+ * @param confInfoScheduled
+ */
+ public void setConfInfoScheduled(boolean confInfoScheduled)
+ {
+ synchronized (confInfoScheduledSyncRoot)
+ {
+ this.confInfoScheduled = confInfoScheduled;
+ }
+ }
+
+ /**
+ * Returns the direction of the session for media of type <tt>mediaType</tt>
+ * that we have with this <tt>CallPeer</tt>. This is the direction of the
+ * session negotiated in the signaling protocol, and it may or may not
+ * coincide with the direction of the media stream.
+ * For example, if we are the focus of a videobridge conference and another
+ * peer is sending video to us, we have a <tt>RECVONLY</tt> video stream,
+ * but <tt>SENDONLY</tt> or <tt>SENDRECV</tt> (Jingle) sessions with the
+ * rest of the conference members.
+ * Should always return non-null.
+ *
+ * @param mediaType the <tt>MediaType</tt> to use
+ * @return Returns the direction of the session for media of type
+ * <tt>mediaType</tt> that we have with this <tt>CallPeer</tt>.
+ */
+ public abstract MediaDirection getDirection(MediaType mediaType);
+
+ /**
+ * {@inheritDoc}
+ *
+ * When a <tt>ConferenceMember</tt> is removed from a conference with a
+ * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore,
+ * if the <tt>ConferenceMember</tt> had an associated video SSRC, the stream
+ * isn't be removed until it times out, leaving a blank video container in
+ * the interface for a few seconds.
+ * TODO: This works around the problem by removing the
+ * <tt>ConferenceMember</tt>'s <tt>ReceiveStream</tt> when the
+ * <tt>ConferenceMember</tt> is removed. The proper solution is to ensure
+ * that RTCP BYEs are sent whenever necessary, and when it is deployed this
+ * code should be removed.
+ *
+ * @param conferenceMember a <tt>ConferenceMember</tt> to be removed from
+ * the list of <tt>ConferenceMember</tt> reported by this peer. If the
+ * specified <tt>ConferenceMember</tt> is no contained in the list, no event
+ */
+ @Override
+ public void removeConferenceMember(ConferenceMember conferenceMember)
+ {
+ MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO);
+ if (videoStream != null)
+ videoStream.removeReceiveStreamForSsrc(
+ conferenceMember.getVideoSsrc());
+
+ super.removeConferenceMember(conferenceMember);
+ }
+
+ /**
+ * Converts a specific <tt>MediaType</tt> into a <tt>sessionType</tt> value
+ * in the terms of the <tt>CallPeerSecurityStatusEvent</tt> class.
+ *
+ * @param mediaType the <tt>MediaType</tt> to be converted
+ * @return the <tt>sessionType</tt> value in the terms of the
+ * <tt>CallPeerSecurityStatusEvent</tt> class that is equivalent to the
+ * specified <tt>mediaType</tt>
+ */
+ private static int toSessionType(MediaType mediaType)
+ {
+ switch (mediaType)
+ {
+ case AUDIO:
+ return CallPeerSecurityStatusEvent.AUDIO_SESSION;
+ case VIDEO:
+ return CallPeerSecurityStatusEvent.VIDEO_SESSION;
+ default:
+ throw new IllegalArgumentException("mediaType");
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java
index 31698a4..5bb4205 100644
--- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java
+++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java
@@ -1,765 +1,778 @@
-/*
- * 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.service.protocol.media;
-
-import java.net.*;
-
-import net.java.sip.communicator.service.netaddr.*;
-import net.java.sip.communicator.service.protocol.*;
-import net.java.sip.communicator.util.*;
-
-import org.ice4j.ice.*;
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.neomedia.*;
-
-/**
- * <tt>TransportManager</tt>s are responsible for allocating ports, gathering
- * local candidates and managing ICE whenever we are using it.
- *
- * @param <U> the peer extension class like for example <tt>CallPeerSipImpl</tt>
- * or <tt>CallPeerJabberImpl</tt>
- *
- * @author Emil Ivov
- * @author Lyubomir Marinov
- * @author Sebastien Vincent
- */
-public abstract class TransportManager<U extends MediaAwareCallPeer<?, ?, ?>>
-{
- /**
- * The <tt>Logger</tt> used by the <tt>TransportManager</tt>
- * class and its instances for logging output.
- */
- private static final Logger logger
- = Logger.getLogger(TransportManager.class);
-
- /**
- * The port tracker that we should use when binding generic media streams.
- * <p>
- * Initialized by {@link #initializePortNumbers()}.
- * </p>
- */
- private static PortTracker defaultPortTracker = new PortTracker(5000, 6000);
-
- /**
- * The port tracker that we should use when binding video media streams.
- * <p>
- * Potentially initialized by {@link #initializePortNumbers()} if the
- * necessary properties are set.
- * </p>
- */
- private static PortTracker videoPortTracker = null;
-
- /**
- * The port tracker that we should use when binding data channels.
- * <p>
- * Potentially initialized by {@link #initializePortNumbers()} if the
- * necessary properties are set
- * </p>
- */
- private static PortTracker dataChannelPortTracker = null;
-
- /**
- * The port tracker that we should use when binding data media streams.
- * <p>
- * Potentially initialized by {@link #initializePortNumbers()} if the
- * necessary properties are set
- * </p>
- */
- private static PortTracker audioPortTracker = null;
-
- /**
- * RTP audio DSCP configuration property name.
- */
- private static final String RTP_AUDIO_DSCP_PROPERTY =
- "net.java.sip.communicator.impl.protocol.RTP_AUDIO_DSCP";
-
- /**
- * RTP video DSCP configuration property name.
- */
- private static final String RTP_VIDEO_DSCP_PROPERTY =
- "net.java.sip.communicator.impl.protocol.RTP_VIDEO_DSCP";
-
- /**
- * Number of empty UDP packets to send for NAT hole punching.
- */
- private static final String HOLE_PUNCH_PKT_COUNT_PROPERTY =
- "net.java.sip.communicator.impl.protocol.HOLE_PUNCH_PKT_COUNT";
-
- /**
- * Number of empty UDP packets to send for NAT hole punching.
- */
- private static final int DEFAULT_HOLE_PUNCH_PKT_COUNT = 3;
-
- /**
- * The {@link MediaAwareCallPeer} whose traffic we will be taking care of.
- */
- private U callPeer;
-
- /**
- * The RTP/RTCP socket couples that this <tt>TransportManager</tt> uses to
- * send and receive media flows through indexed by <tt>MediaType</tt>
- * (ordinal).
- */
- private final StreamConnector[] streamConnectors
- = new StreamConnector[MediaType.values().length];
-
- /**
- * Creates a new instance of this transport manager, binding it to the
- * specified peer.
- *
- * @param callPeer the {@link MediaAwareCallPeer} whose traffic we will be
- * taking care of.
- */
- protected TransportManager(U callPeer)
- {
- this.callPeer = callPeer;
- }
-
- /**
- * Returns the <tt>StreamConnector</tt> instance that this media handler
- * should use for streams of the specified <tt>mediaType</tt>. The method
- * would also create a new <tt>StreamConnector</tt> if no connector has
- * been initialized for this <tt>mediaType</tt> yet or in case one
- * of its underlying sockets has been closed.
- *
- * @param mediaType the <tt>MediaType</tt> that we'd like to create a
- * connector for.
- * @return this media handler's <tt>StreamConnector</tt> for the specified
- * <tt>mediaType</tt>.
- *
- * @throws OperationFailedException in case we failed to initialize our
- * connector.
- */
- public StreamConnector getStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- int streamConnectorIndex = mediaType.ordinal();
- StreamConnector streamConnector
- = streamConnectors[streamConnectorIndex];
-
- if((streamConnector == null)
- || (streamConnector.getProtocol() == StreamConnector.Protocol.UDP))
- {
- DatagramSocket controlSocket;
-
- if((streamConnector == null)
- || streamConnector.getDataSocket().isClosed()
- || (((controlSocket = streamConnector.getControlSocket())
- != null)
- && controlSocket.isClosed()))
- {
- streamConnectors[streamConnectorIndex]
- = streamConnector
- = createStreamConnector(mediaType);
- }
- }
- else if(streamConnector.getProtocol() == StreamConnector.Protocol.TCP)
- {
- Socket controlTCPSocket;
-
- if(streamConnector.getDataTCPSocket().isClosed()
- || (((controlTCPSocket = streamConnector.getControlTCPSocket())
- != null)
- && controlTCPSocket.isClosed()))
- {
- streamConnectors[streamConnectorIndex]
- = streamConnector
- = createStreamConnector(mediaType);
- }
- }
- return streamConnector;
- }
-
- /**
- * Closes the existing <tt>StreamConnector</tt>, if any, associated with a
- * specific <tt>MediaType</tt> and removes its reference from this
- * <tt>TransportManager</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> associated with the
- * <tt>StreamConnector</tt> to close
- */
- public void closeStreamConnector(MediaType mediaType)
- {
- int index = mediaType.ordinal();
- StreamConnector streamConnector = streamConnectors[index];
-
- if (streamConnector != null)
- {
- closeStreamConnector(mediaType, streamConnector);
- streamConnectors[index] = null;
- }
- }
-
- /**
- * Closes a specific <tt>StreamConnector</tt> associated with a specific
- * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to
- * the specified <tt>streamConnector</tt>, it remains. Allows extenders to
- * override and perform additional customizations to the closing of the
- * specified <tt>streamConnector</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> associated with the specified
- * <tt>streamConnector</tt>
- * @param streamConnector the <tt>StreamConnector</tt> to be closed
- * @see #closeStreamConnector(MediaType)
- */
- protected void closeStreamConnector(
- MediaType mediaType,
- StreamConnector streamConnector)
- {
- /*
- * XXX The connected owns the sockets so it is important that it
- * decides whether to close them i.e. this TransportManager is not
- * allowed to explicitly close the sockets by itself.
- */
- streamConnector.close();
- }
-
- /**
- * Creates a media <tt>StreamConnector</tt>. The method takes into account
- * the minimum and maximum media port boundaries.
- *
- * @param mediaType the <tt>MediaType</tt> of the stream for which a new
- * <tt>StreamConnector</tt> is to be created
- * @return a new <tt>StreamConnector</tt>.
- *
- * @throws OperationFailedException if the binding of the sockets fails.
- */
- protected StreamConnector createStreamConnector(MediaType mediaType)
- throws OperationFailedException
- {
- NetworkAddressManagerService nam
- = ProtocolMediaActivator.getNetworkAddressManagerService();
- InetAddress intendedDestination = getIntendedDestination(getCallPeer());
- InetAddress localHostForPeer = nam.getLocalHost(intendedDestination);
-
- //make sure our port numbers reflect the configuration service settings
- initializePortNumbers();
-
- PortTracker portTracker = getPortTracker(mediaType);
-
- //create the RTP socket.
- DatagramSocket rtpSocket = null;
- try
- {
- rtpSocket = nam.createDatagramSocket(
- localHostForPeer, portTracker.getPort(),
- portTracker.getMinPort(), portTracker.getMaxPort());
- }
- catch (Exception exc)
- {
- throw new OperationFailedException(
- "Failed to allocate the network ports necessary for the call.",
- OperationFailedException.INTERNAL_ERROR, exc);
- }
-
- //make sure that next time we don't try to bind on occupied ports
- //also, refuse validation in case someone set the tracker range to 1
- portTracker.setNextPort( rtpSocket.getLocalPort() + 1, false);
-
- //create the RTCP socket, preferably on the port following our RTP one.
- DatagramSocket rtcpSocket = null;
- try
- {
- rtcpSocket = nam.createDatagramSocket(
- localHostForPeer, portTracker.getPort(),
- portTracker.getMinPort(), portTracker.getMaxPort());
- }
- catch (Exception exc)
- {
- throw new OperationFailedException(
- "Failed to allocate the network ports necessary for the call.",
- OperationFailedException.INTERNAL_ERROR,
- exc);
- }
-
- //make sure that next time we don't try to bind on occupied ports
- portTracker.setNextPort( rtcpSocket.getLocalPort() + 1);
-
- return new DefaultStreamConnector(rtpSocket, rtcpSocket);
- }
-
- /**
- * (Re)Sets the all the port allocators to reflect current values specified
- * in the <tt>ConfigurationService</tt>. Calling this method may very well
- * result in creating new port allocators or destroying exising ones.
- */
- protected static void initializePortNumbers()
- {
- //try the default tracker first
- ConfigurationService configuration
- = ProtocolMediaActivator.getConfigurationService();
- String minPortNumberStr = configuration.getString(
- OperationSetBasicTelephony.MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME);
-
- String maxPortNumberStr = configuration.getString(
- OperationSetBasicTelephony.MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME);
-
- //try to send the specified range. If there's no specified range in
- //configuration, we'll just leave the tracker as it is: [5000 to 6000]
- defaultPortTracker.tryRange(minPortNumberStr, maxPortNumberStr);
-
-
- //try the VIDEO tracker
- minPortNumberStr = configuration.getString(
- OperationSetBasicTelephony.MIN_VIDEO_PORT_NUMBER_PROPERTY_NAME);
-
- maxPortNumberStr = configuration.getString(
- OperationSetBasicTelephony.MAX_VIDEO_PORT_NUMBER_PROPERTY_NAME);
-
- //try to send the specified range. If there's no specified range in
- //configuration, we'll just leave this tracker to null
- videoPortTracker
- = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr);
-
-
- //try the AUDIO tracker
- minPortNumberStr = configuration.getString(
- OperationSetBasicTelephony.MIN_AUDIO_PORT_NUMBER_PROPERTY_NAME);
-
- maxPortNumberStr = configuration.getString(
- OperationSetBasicTelephony.MAX_AUDIO_PORT_NUMBER_PROPERTY_NAME);
-
- //try to send the specified range. If there's no specified range in
- //configuration, we'll just leave this tracker to null
- audioPortTracker
- = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr);
-
- //try the DATA CHANNEL tracker
- minPortNumberStr = configuration.getString(OperationSetBasicTelephony
- .MIN_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
-
- maxPortNumberStr = configuration.getString(OperationSetBasicTelephony
- .MAX_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
-
- //try to send the specified range. If there's no specified range in
- //configuration, we'll just leave this tracker to null
- dataChannelPortTracker
- = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr);
- }
-
- /**
- * Returns the <tt>InetAddress</tt> that we are using in one of our
- * <tt>StreamConnector</tt>s or, in case we don't have any connectors yet
- * the address returned by the our network address manager as the best local
- * address to use when contacting the <tt>CallPeer</tt> associated with this
- * <tt>MediaHandler</tt>. This method is primarily meant for use with the
- * o= and c= fields of a newly created session description. The point is
- * that we create our <tt>StreamConnector</tt>s when constructing the media
- * descriptions so we already have a specific local address assigned to them
- * at the time we get ready to create the c= and o= fields. It is therefore
- * better to try and return one of these addresses before trying the net
- * address manager again and running the slight risk of getting a different
- * address.
- *
- * @return an <tt>InetAddress</tt> that we use in one of the
- * <tt>StreamConnector</tt>s in this class.
- */
- public InetAddress getLastUsedLocalHost()
- {
- for (MediaType mediaType : MediaType.values())
- {
- StreamConnector streamConnector
- = streamConnectors[mediaType.ordinal()];
-
- if (streamConnector != null)
- return streamConnector.getDataSocket().getLocalAddress();
- }
-
- NetworkAddressManagerService nam
- = ProtocolMediaActivator.getNetworkAddressManagerService();
- InetAddress intendedDestination = getIntendedDestination(getCallPeer());
-
- return nam.getLocalHost(intendedDestination);
- }
-
- /**
- * Send empty UDP packets to target destination data/control ports
- * in order to open ports on NATs or and help RTP proxies latch onto our
- * RTP ports.
- *
- * @param target <tt>MediaStreamTarget</tt>
- * @param type the {@link MediaType} of the connector we'd like to send
- * the hole punching packet through.
- */
- public void sendHolePunchPacket(MediaStreamTarget target, MediaType type)
- {
- logger.info("Send NAT hole punch packets");
-
- //check how many hole punch packets we would be supposed to send:
- int packetCount = ProtocolMediaActivator.getConfigurationService()
- .getInt( HOLE_PUNCH_PKT_COUNT_PROPERTY,
- DEFAULT_HOLE_PUNCH_PKT_COUNT);
-
- if (packetCount < 0)
- packetCount = DEFAULT_HOLE_PUNCH_PKT_COUNT;
-
- try
- {
- StreamConnector connector = getStreamConnector(type);
-
- synchronized(connector)
- {
- if(connector.getProtocol() == StreamConnector.Protocol.TCP)
- return;
-
- DatagramSocket socket;
-
- //we may want to send more than one packet in case they get lost
- for(int i=0; i < packetCount; i++)
- {
- /* data port (RTP) */
- if((socket = connector.getDataSocket()) != null)
- {
- socket.send(
- new DatagramPacket(
- new byte[0],
- 0,
- target.getDataAddress().getAddress(),
- target.getDataAddress().getPort()));
- }
-
- /* control port (RTCP) */
- if((socket = connector.getControlSocket()) != null)
- {
- socket.send(
- new DatagramPacket(
- new byte[0],
- 0,
- target.getControlAddress().getAddress(),
- target.getControlAddress().getPort()));
- }
- }
- }
- }
- catch(Exception e)
- {
- logger.error("Error cannot send to remote peer", e);
- }
- }
-
- /**
- * Set traffic class (QoS) for the RTP socket.
- *
- * @param target <tt>MediaStreamTarget</tt>
- * @param type the {@link MediaType} of the connector we'd like to set
- * traffic class
- */
- protected void setTrafficClass(MediaStreamTarget target, MediaType type)
- {
- // get traffic class value for RTP audio/video
- int trafficClass = getDSCP(type);
-
- if(trafficClass <= 0)
- return;
-
- if (logger.isInfoEnabled())
- logger.info(
- "Set traffic class for " + type + " to " + trafficClass);
- try
- {
- StreamConnector connector = getStreamConnector(type);
-
- synchronized(connector)
- {
- if(connector.getProtocol() == StreamConnector.Protocol.TCP)
- {
- connector.getDataTCPSocket().setTrafficClass(trafficClass);
-
- Socket controlTCPSocket = connector.getControlTCPSocket();
-
- if (controlTCPSocket != null)
- controlTCPSocket.setTrafficClass(trafficClass);
- }
- else
- {
- /* data port (RTP) */
- connector.getDataSocket().setTrafficClass(trafficClass);
-
- /* control port (RTCP) */
- DatagramSocket controlSocket = connector.getControlSocket();
-
- if (controlSocket != null)
- controlSocket.setTrafficClass(trafficClass);
- }
- }
- }
- catch(Exception e)
- {
- logger.error(
- "Failed to set traffic class for " + type + " to "
- + trafficClass,
- e);
- }
- }
-
- /**
- * Gets the SIP traffic class associated with a specific <tt>MediaType</tt>
- * from the configuration.
- *
- * @param type the <tt>MediaType</tt> to get the associated SIP traffic
- * class of
- * @return the SIP traffic class associated with the specified
- * <tt>MediaType</tt> or <tt>0</tt> if not configured
- */
- private int getDSCP(MediaType type)
- {
- String dscpPropertyName;
-
- switch (type)
- {
- case AUDIO:
- dscpPropertyName = RTP_AUDIO_DSCP_PROPERTY;
- break;
- case VIDEO:
- dscpPropertyName = RTP_VIDEO_DSCP_PROPERTY;
- break;
- default:
- dscpPropertyName = null;
- break;
- }
-
- return
- (dscpPropertyName == null)
- ? 0
- : (ProtocolMediaActivator.getConfigurationService().getInt(
- dscpPropertyName,
- 0)
- << 2);
- }
-
- /**
- * Returns the <tt>InetAddress</tt> that is most likely to be used as a
- * next hop when contacting the specified <tt>destination</tt>. This is
- * an utility method that is used whenever we have to choose one of our
- * local addresses to put in the Via, Contact or (in the case of no
- * registrar accounts) From headers.
- *
- * @param peer the CallPeer that we would contact.
- *
- * @return the <tt>InetAddress</tt> that is most likely to be to be used
- * as a next hop when contacting the specified <tt>destination</tt>.
- *
- * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
- * host/ip/fqdn
- */
- protected abstract InetAddress getIntendedDestination(U peer);
-
- /**
- * Returns the {@link MediaAwareCallPeer} that this transport manager is
- * serving.
- *
- * @return the {@link MediaAwareCallPeer} that this transport manager is
- * serving.
- */
- public U getCallPeer()
- {
- return callPeer;
- }
-
- /**
- * Returns the port tracker that we are supposed to use when binding ports
- * for the specified {@link MediaType}.
- *
- * @param mediaType the media type that we want to obtain a locator for.
- *
- * @return the port tracker that we are supposed to use when binding ports
- * for the specified {@link MediaType}.
- */
- protected static PortTracker getPortTracker(MediaType mediaType)
- {
- if(MediaType.AUDIO == mediaType && audioPortTracker != null)
- return audioPortTracker;
-
- if(MediaType.VIDEO == mediaType && videoPortTracker != null)
- return videoPortTracker;
-
- return defaultPortTracker;
- }
-
- /**
- * Returns the port tracker that we are supposed to use when binding ports
- * for the {@link MediaType} indicated by the string param. If we do not
- * recognize the string as a valid media type, we simply return the default
- * port tracker.
- *
- * @param mediaTypeStr the name of the media type that we want to obtain a
- * locator for.
- *
- * @return the port tracker that we are supposed to use when binding ports
- * for the {@link MediaType} with the specified name or the default tracker
- * in case the name doesn't ring a bell.
- */
- protected static PortTracker getPortTracker(String mediaTypeStr)
- {
- try
- {
- MediaType mediaType = MediaType.parseString(mediaTypeStr);
-
- return getPortTracker(mediaType);
- }
- catch (Exception exc)
- {
- logger.info(
- "Returning default port tracker for unrecognized media type: "
- + mediaTypeStr);
-
- return defaultPortTracker;
- }
- }
-
- /**
- * Returns the extended type of the candidate selected if this transport
- * manager is using ICE.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return The extended type of the candidate selected if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- public abstract String getICECandidateExtendedType(String streamName);
-
- /**
- * Returns the current state of ICE processing.
- *
- * @return the current state of ICE processing if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- public abstract String getICEState();
-
- /**
- * Returns the ICE local host address.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local host address if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- public abstract InetSocketAddress getICELocalHostAddress(String streamName);
-
- /**
- * Returns the ICE remote host address.
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote host address if this transport
- * manager is using ICE. Otherwise, returns null.
- */
- public abstract InetSocketAddress getICERemoteHostAddress(
- String streamName);
-
- /**
- * Returns the ICE local reflexive address (server or peer reflexive).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local reflexive address. May be null if this transport
- * manager is not using ICE or if there is no reflexive address for the
- * local candidate used.
- */
- public abstract InetSocketAddress getICELocalReflexiveAddress(
- String streamName);
-
- /**
- * Returns the ICE remote reflexive address (server or peer reflexive).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote reflexive address. May be null if this transport
- * manager is not using ICE or if there is no reflexive address for the
- * remote candidate used.
- */
- public abstract InetSocketAddress getICERemoteReflexiveAddress(
- String streamName);
-
- /**
- * Returns the ICE local relayed address (server or peer relayed).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE local relayed address. May be null if this transport
- * manager is not using ICE or if there is no relayed address for the
- * local candidate used.
- */
- public abstract InetSocketAddress getICELocalRelayedAddress(
- String streamName);
-
- /**
- * Returns the ICE remote relayed address (server or peer relayed).
- *
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return the ICE remote relayed address. May be null if this transport
- * manager is not using ICE or if there is no relayed address for the
- * remote candidate used.
- */
- public abstract InetSocketAddress getICERemoteRelayedAddress(
- String streamName);
-
- /**
- * Returns the total harvesting time (in ms) for all harvesters.
- *
- * @return The total harvesting time (in ms) for all the harvesters. 0 if
- * the ICE agent is null, or if the agent has nevers harvested.
- */
- public abstract long getTotalHarvestingTime();
-
- /**
- * Returns the harvesting time (in ms) for the harvester given in parameter.
- *
- * @param harvesterName The class name if the harvester.
- *
- * @return The harvesting time (in ms) for the harvester given in parameter.
- * 0 if this harvester does not exists, if the ICE agent is null, or if the
- * agent has never harvested with this harvester.
- */
- public abstract long getHarvestingTime(String harvesterName);
-
- /**
- * Returns the number of harvesting for this agent.
- *
- * @return The number of harvesting for this agent.
- */
- public abstract int getNbHarvesting();
-
- /**
- * Returns the number of harvesting time for the harvester given in
- * parameter.
- *
- * @param harvesterName The class name if the harvester.
- *
- * @return The number of harvesting time for the harvester given in
- * parameter.
- */
- public abstract int getNbHarvesting(String harvesterName);
-
- /**
- * Returns the ICE candidate extended type selected by the given agent.
- *
- * @param iceAgent The ICE agent managing the ICE offer/answer exchange,
- * collecting and selecting the candidate.
- * @param streamName The stream name (AUDIO, VIDEO);
- *
- * @return The ICE candidate extended type selected by the given agent. null
- * if the iceAgent is null or if there is no candidate selected or
- * available.
- */
- public static String getICECandidateExtendedType(
- Agent iceAgent,
- String streamName)
- {
- if(iceAgent != null)
- {
- LocalCandidate localCandidate =
- iceAgent.getSelectedLocalCandidate(streamName);
- if(localCandidate != null)
- {
- return localCandidate.getExtendedType().toString();
- }
- }
- return null;
- }
-}
+/*
+ * 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.service.protocol.media;
+
+import java.net.*;
+
+import net.java.sip.communicator.service.netaddr.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+import org.ice4j.ice.*;
+import org.jitsi.service.configuration.*;
+import org.jitsi.service.neomedia.*;
+
+/**
+ * <tt>TransportManager</tt>s are responsible for allocating ports, gathering
+ * local candidates and managing ICE whenever we are using it.
+ *
+ * @param <U> the peer extension class like for example <tt>CallPeerSipImpl</tt>
+ * or <tt>CallPeerJabberImpl</tt>
+ *
+ * @author Emil Ivov
+ * @author Lyubomir Marinov
+ * @author Sebastien Vincent
+ */
+public abstract class TransportManager<U extends MediaAwareCallPeer<?, ?, ?>>
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>TransportManager</tt>
+ * class and its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(TransportManager.class);
+
+ /**
+ * The port tracker that we should use when binding generic media streams.
+ * <p>
+ * Initialized by {@link #initializePortNumbers()}.
+ * </p>
+ */
+ private static PortTracker defaultPortTracker = new PortTracker(5000, 6000);
+
+ /**
+ * The port tracker that we should use when binding video media streams.
+ * <p>
+ * Potentially initialized by {@link #initializePortNumbers()} if the
+ * necessary properties are set.
+ * </p>
+ */
+ private static PortTracker videoPortTracker = null;
+
+ /**
+ * The port tracker that we should use when binding data channels.
+ * <p>
+ * Potentially initialized by {@link #initializePortNumbers()} if the
+ * necessary properties are set
+ * </p>
+ */
+ private static PortTracker dataChannelPortTracker = null;
+
+ /**
+ * The port tracker that we should use when binding data media streams.
+ * <p>
+ * Potentially initialized by {@link #initializePortNumbers()} if the
+ * necessary properties are set
+ * </p>
+ */
+ private static PortTracker audioPortTracker = null;
+
+ /**
+ * RTP audio DSCP configuration property name.
+ */
+ private static final String RTP_AUDIO_DSCP_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.RTP_AUDIO_DSCP";
+
+ /**
+ * RTP video DSCP configuration property name.
+ */
+ private static final String RTP_VIDEO_DSCP_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.RTP_VIDEO_DSCP";
+
+ /**
+ * Number of empty UDP packets to send for NAT hole punching.
+ */
+ private static final String HOLE_PUNCH_PKT_COUNT_PROPERTY =
+ "net.java.sip.communicator.impl.protocol.HOLE_PUNCH_PKT_COUNT";
+
+ /**
+ * Number of empty UDP packets to send for NAT hole punching.
+ */
+ private static final int DEFAULT_HOLE_PUNCH_PKT_COUNT = 3;
+
+ /**
+ * The {@link MediaAwareCallPeer} whose traffic we will be taking care of.
+ */
+ private U callPeer;
+
+ /**
+ * The RTP/RTCP socket couples that this <tt>TransportManager</tt> uses to
+ * send and receive media flows through indexed by <tt>MediaType</tt>
+ * (ordinal).
+ */
+ private final StreamConnector[] streamConnectors
+ = new StreamConnector[MediaType.values().length];
+
+ /**
+ * Creates a new instance of this transport manager, binding it to the
+ * specified peer.
+ *
+ * @param callPeer the {@link MediaAwareCallPeer} whose traffic we will be
+ * taking care of.
+ */
+ protected TransportManager(U callPeer)
+ {
+ this.callPeer = callPeer;
+ }
+
+ /**
+ * Returns the <tt>StreamConnector</tt> instance that this media handler
+ * should use for streams of the specified <tt>mediaType</tt>. The method
+ * would also create a new <tt>StreamConnector</tt> if no connector has
+ * been initialized for this <tt>mediaType</tt> yet or in case one
+ * of its underlying sockets has been closed.
+ *
+ * @param mediaType the <tt>MediaType</tt> that we'd like to create a
+ * connector for.
+ * @return this media handler's <tt>StreamConnector</tt> for the specified
+ * <tt>mediaType</tt>.
+ *
+ * @throws OperationFailedException in case we failed to initialize our
+ * connector.
+ */
+ public StreamConnector getStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ int streamConnectorIndex = mediaType.ordinal();
+ StreamConnector streamConnector
+ = streamConnectors[streamConnectorIndex];
+
+ if((streamConnector == null)
+ || (streamConnector.getProtocol() == StreamConnector.Protocol.UDP))
+ {
+ DatagramSocket controlSocket;
+
+ if((streamConnector == null)
+ || streamConnector.getDataSocket().isClosed()
+ || (((controlSocket = streamConnector.getControlSocket())
+ != null)
+ && controlSocket.isClosed()))
+ {
+ streamConnectors[streamConnectorIndex]
+ = streamConnector
+ = createStreamConnector(mediaType);
+ }
+ }
+ else if(streamConnector.getProtocol() == StreamConnector.Protocol.TCP)
+ {
+ Socket controlTCPSocket;
+
+ if(streamConnector.getDataTCPSocket().isClosed()
+ || (((controlTCPSocket = streamConnector.getControlTCPSocket())
+ != null)
+ && controlTCPSocket.isClosed()))
+ {
+ streamConnectors[streamConnectorIndex]
+ = streamConnector
+ = createStreamConnector(mediaType);
+ }
+ }
+ return streamConnector;
+ }
+
+ /**
+ * Closes the existing <tt>StreamConnector</tt>, if any, associated with a
+ * specific <tt>MediaType</tt> and removes its reference from this
+ * <tt>TransportManager</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> associated with the
+ * <tt>StreamConnector</tt> to close
+ */
+ public void closeStreamConnector(MediaType mediaType)
+ {
+ int index = mediaType.ordinal();
+ StreamConnector streamConnector = streamConnectors[index];
+
+ if (streamConnector != null)
+ {
+ closeStreamConnector(mediaType, streamConnector);
+ streamConnectors[index] = null;
+ }
+ }
+
+ /**
+ * Closes a specific <tt>StreamConnector</tt> associated with a specific
+ * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to
+ * the specified <tt>streamConnector</tt>, it remains. Allows extenders to
+ * override and perform additional customizations to the closing of the
+ * specified <tt>streamConnector</tt>.
+ *
+ * @param mediaType the <tt>MediaType</tt> associated with the specified
+ * <tt>streamConnector</tt>
+ * @param streamConnector the <tt>StreamConnector</tt> to be closed
+ * @see #closeStreamConnector(MediaType)
+ */
+ protected void closeStreamConnector(
+ MediaType mediaType,
+ StreamConnector streamConnector)
+ {
+ /*
+ * XXX The connected owns the sockets so it is important that it
+ * decides whether to close them i.e. this TransportManager is not
+ * allowed to explicitly close the sockets by itself.
+ */
+ streamConnector.close();
+ }
+
+ /**
+ * Creates a media <tt>StreamConnector</tt> for a stream of a specific
+ * <tt>MediaType</tt>. The minimum and maximum of the media port boundaries
+ * are taken into account.
+ *
+ * @param mediaType the <tt>MediaType</tt> of the stream for which a
+ * <tt>StreamConnector</tt> is to be created
+ * @return a <tt>StreamConnector</tt> for the stream of the specified
+ * <tt>mediaType</tt>
+ * @throws OperationFailedException if the binding of the sockets fails
+ */
+ protected StreamConnector createStreamConnector(MediaType mediaType)
+ throws OperationFailedException
+ {
+ NetworkAddressManagerService nam
+ = ProtocolMediaActivator.getNetworkAddressManagerService();
+ InetAddress intendedDestination = getIntendedDestination(getCallPeer());
+ InetAddress localHostForPeer = nam.getLocalHost(intendedDestination);
+
+ //make sure our port numbers reflect the configuration service settings
+ initializePortNumbers();
+
+ PortTracker portTracker = getPortTracker(mediaType);
+
+ //create the RTP socket.
+ DatagramSocket rtpSocket = null;
+ try
+ {
+ rtpSocket = nam.createDatagramSocket(
+ localHostForPeer, portTracker.getPort(),
+ portTracker.getMinPort(), portTracker.getMaxPort());
+ }
+ catch (Exception exc)
+ {
+ throw new OperationFailedException(
+ "Failed to allocate the network ports necessary for the call.",
+ OperationFailedException.INTERNAL_ERROR, exc);
+ }
+
+ //make sure that next time we don't try to bind on occupied ports
+ //also, refuse validation in case someone set the tracker range to 1
+ portTracker.setNextPort( rtpSocket.getLocalPort() + 1, false);
+
+ //create the RTCP socket, preferably on the port following our RTP one.
+ DatagramSocket rtcpSocket = null;
+ try
+ {
+ rtcpSocket = nam.createDatagramSocket(
+ localHostForPeer, portTracker.getPort(),
+ portTracker.getMinPort(), portTracker.getMaxPort());
+ }
+ catch (Exception exc)
+ {
+ throw new OperationFailedException(
+ "Failed to allocate the network ports necessary for the call.",
+ OperationFailedException.INTERNAL_ERROR,
+ exc);
+ }
+
+ //make sure that next time we don't try to bind on occupied ports
+ portTracker.setNextPort( rtcpSocket.getLocalPort() + 1);
+
+ return new DefaultStreamConnector(rtpSocket, rtcpSocket);
+ }
+
+ /**
+ * (Re)Sets the all the port allocators to reflect current values specified
+ * in the <tt>ConfigurationService</tt>. Calling this method may very well
+ * result in creating new port allocators or destroying existing ones.
+ */
+ protected static void initializePortNumbers()
+ {
+ //try the default tracker first
+ ConfigurationService cfg
+ = ProtocolMediaActivator.getConfigurationService();
+ String minPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME);
+ String maxPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME);
+
+ //try to send the specified range. If there's no specified range in
+ //configuration, we'll just leave the tracker as it is: [5000 to 6000]
+ defaultPortTracker.tryRange(minPortNumberStr, maxPortNumberStr);
+
+ //try the VIDEO tracker
+ minPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_VIDEO_PORT_NUMBER_PROPERTY_NAME);
+ maxPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_VIDEO_PORT_NUMBER_PROPERTY_NAME);
+ //try to send the specified range. If there's no specified range in
+ //configuration, we'll just leave this tracker to null
+ videoPortTracker
+ = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr);
+
+ //try the AUDIO tracker
+ minPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_AUDIO_PORT_NUMBER_PROPERTY_NAME);
+ maxPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_AUDIO_PORT_NUMBER_PROPERTY_NAME);
+ //try to send the specified range. If there's no specified range in
+ //configuration, we'll just leave this tracker to null
+ audioPortTracker
+ = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr);
+
+ //try the DATA CHANNEL tracker
+ minPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MIN_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
+ maxPortNumberStr
+ = cfg.getString(
+ OperationSetBasicTelephony
+ .MAX_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME);
+
+ //try to send the specified range. If there's no specified range in
+ //configuration, we'll just leave this tracker to null
+ dataChannelPortTracker
+ = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr);
+ }
+
+ /**
+ * Returns the <tt>InetAddress</tt> that we are using in one of our
+ * <tt>StreamConnector</tt>s or, in case we don't have any connectors yet
+ * the address returned by the our network address manager as the best local
+ * address to use when contacting the <tt>CallPeer</tt> associated with this
+ * <tt>MediaHandler</tt>. This method is primarily meant for use with the
+ * o= and c= fields of a newly created session description. The point is
+ * that we create our <tt>StreamConnector</tt>s when constructing the media
+ * descriptions so we already have a specific local address assigned to them
+ * at the time we get ready to create the c= and o= fields. It is therefore
+ * better to try and return one of these addresses before trying the net
+ * address manager again and running the slight risk of getting a different
+ * address.
+ *
+ * @return an <tt>InetAddress</tt> that we use in one of the
+ * <tt>StreamConnector</tt>s in this class.
+ */
+ public InetAddress getLastUsedLocalHost()
+ {
+ for (MediaType mediaType : MediaType.values())
+ {
+ StreamConnector streamConnector
+ = streamConnectors[mediaType.ordinal()];
+
+ if (streamConnector != null)
+ return streamConnector.getDataSocket().getLocalAddress();
+ }
+
+ NetworkAddressManagerService nam
+ = ProtocolMediaActivator.getNetworkAddressManagerService();
+ InetAddress intendedDestination = getIntendedDestination(getCallPeer());
+
+ return nam.getLocalHost(intendedDestination);
+ }
+
+ /**
+ * Send empty UDP packets to target destination data/control ports
+ * in order to open ports on NATs or and help RTP proxies latch onto our
+ * RTP ports.
+ *
+ * @param target <tt>MediaStreamTarget</tt>
+ * @param type the {@link MediaType} of the connector we'd like to send
+ * the hole punching packet through.
+ */
+ public void sendHolePunchPacket(MediaStreamTarget target, MediaType type)
+ {
+ logger.info("Send NAT hole punch packets");
+
+ //check how many hole punch packets we would be supposed to send:
+ int packetCount = ProtocolMediaActivator.getConfigurationService()
+ .getInt( HOLE_PUNCH_PKT_COUNT_PROPERTY,
+ DEFAULT_HOLE_PUNCH_PKT_COUNT);
+
+ if (packetCount < 0)
+ packetCount = DEFAULT_HOLE_PUNCH_PKT_COUNT;
+
+ try
+ {
+ StreamConnector connector = getStreamConnector(type);
+
+ synchronized(connector)
+ {
+ if(connector.getProtocol() == StreamConnector.Protocol.TCP)
+ return;
+
+ DatagramSocket socket;
+
+ //we may want to send more than one packet in case they get lost
+ for(int i=0; i < packetCount; i++)
+ {
+ /* data port (RTP) */
+ if((socket = connector.getDataSocket()) != null)
+ {
+ socket.send(
+ new DatagramPacket(
+ new byte[0],
+ 0,
+ target.getDataAddress().getAddress(),
+ target.getDataAddress().getPort()));
+ }
+
+ /* control port (RTCP) */
+ if((socket = connector.getControlSocket()) != null)
+ {
+ socket.send(
+ new DatagramPacket(
+ new byte[0],
+ 0,
+ target.getControlAddress().getAddress(),
+ target.getControlAddress().getPort()));
+ }
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ logger.error("Error cannot send to remote peer", e);
+ }
+ }
+
+ /**
+ * Set traffic class (QoS) for the RTP socket.
+ *
+ * @param target <tt>MediaStreamTarget</tt>
+ * @param type the {@link MediaType} of the connector we'd like to set
+ * traffic class
+ */
+ protected void setTrafficClass(MediaStreamTarget target, MediaType type)
+ {
+ // get traffic class value for RTP audio/video
+ int trafficClass = getDSCP(type);
+
+ if(trafficClass <= 0)
+ return;
+
+ if (logger.isInfoEnabled())
+ logger.info(
+ "Set traffic class for " + type + " to " + trafficClass);
+ try
+ {
+ StreamConnector connector = getStreamConnector(type);
+
+ synchronized(connector)
+ {
+ if(connector.getProtocol() == StreamConnector.Protocol.TCP)
+ {
+ connector.getDataTCPSocket().setTrafficClass(trafficClass);
+
+ Socket controlTCPSocket = connector.getControlTCPSocket();
+
+ if (controlTCPSocket != null)
+ controlTCPSocket.setTrafficClass(trafficClass);
+ }
+ else
+ {
+ /* data port (RTP) */
+ connector.getDataSocket().setTrafficClass(trafficClass);
+
+ /* control port (RTCP) */
+ DatagramSocket controlSocket = connector.getControlSocket();
+
+ if (controlSocket != null)
+ controlSocket.setTrafficClass(trafficClass);
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ logger.error(
+ "Failed to set traffic class for " + type + " to "
+ + trafficClass,
+ e);
+ }
+ }
+
+ /**
+ * Gets the SIP traffic class associated with a specific <tt>MediaType</tt>
+ * from the configuration.
+ *
+ * @param type the <tt>MediaType</tt> to get the associated SIP traffic
+ * class of
+ * @return the SIP traffic class associated with the specified
+ * <tt>MediaType</tt> or <tt>0</tt> if not configured
+ */
+ private int getDSCP(MediaType type)
+ {
+ String dscpPropertyName;
+
+ switch (type)
+ {
+ case AUDIO:
+ dscpPropertyName = RTP_AUDIO_DSCP_PROPERTY;
+ break;
+ case VIDEO:
+ dscpPropertyName = RTP_VIDEO_DSCP_PROPERTY;
+ break;
+ default:
+ dscpPropertyName = null;
+ break;
+ }
+
+ return
+ (dscpPropertyName == null)
+ ? 0
+ : (ProtocolMediaActivator.getConfigurationService().getInt(
+ dscpPropertyName,
+ 0)
+ << 2);
+ }
+
+ /**
+ * Returns the <tt>InetAddress</tt> that is most likely to be used as a
+ * next hop when contacting the specified <tt>destination</tt>. This is
+ * an utility method that is used whenever we have to choose one of our
+ * local addresses to put in the Via, Contact or (in the case of no
+ * registrar accounts) From headers.
+ *
+ * @param peer the CallPeer that we would contact.
+ *
+ * @return the <tt>InetAddress</tt> that is most likely to be to be used
+ * as a next hop when contacting the specified <tt>destination</tt>.
+ *
+ * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
+ * host/ip/fqdn
+ */
+ protected abstract InetAddress getIntendedDestination(U peer);
+
+ /**
+ * Returns the {@link MediaAwareCallPeer} that this transport manager is
+ * serving.
+ *
+ * @return the {@link MediaAwareCallPeer} that this transport manager is
+ * serving.
+ */
+ public U getCallPeer()
+ {
+ return callPeer;
+ }
+
+ /**
+ * Returns the port tracker that we are supposed to use when binding ports
+ * for the specified {@link MediaType}.
+ *
+ * @param mediaType the media type that we want to obtain a locator for.
+ *
+ * @return the port tracker that we are supposed to use when binding ports
+ * for the specified {@link MediaType}.
+ */
+ protected static PortTracker getPortTracker(MediaType mediaType)
+ {
+ if (MediaType.AUDIO == mediaType)
+ {
+ if (audioPortTracker != null)
+ return audioPortTracker;
+ }
+ else if (MediaType.VIDEO == mediaType)
+ {
+ if (videoPortTracker != null)
+ return videoPortTracker;
+ }
+
+ return defaultPortTracker;
+ }
+
+ /**
+ * Returns the port tracker that we are supposed to use when binding ports
+ * for the {@link MediaType} indicated by the string param. If we do not
+ * recognize the string as a valid media type, we simply return the default
+ * port tracker.
+ *
+ * @param mediaTypeStr the name of the media type that we want to obtain a
+ * locator for.
+ *
+ * @return the port tracker that we are supposed to use when binding ports
+ * for the {@link MediaType} with the specified name or the default tracker
+ * in case the name doesn't ring a bell.
+ */
+ protected static PortTracker getPortTracker(String mediaTypeStr)
+ {
+ try
+ {
+ MediaType mediaType = MediaType.parseString(mediaTypeStr);
+
+ return getPortTracker(mediaType);
+ }
+ catch (Exception exc)
+ {
+ logger.info(
+ "Returning default port tracker for unrecognized media type: "
+ + mediaTypeStr);
+
+ return defaultPortTracker;
+ }
+ }
+
+ /**
+ * Returns the extended type of the candidate selected if this transport
+ * manager is using ICE.
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return The extended type of the candidate selected if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ public abstract String getICECandidateExtendedType(String streamName);
+
+ /**
+ * Returns the current state of ICE processing.
+ *
+ * @return the current state of ICE processing if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ public abstract String getICEState();
+
+ /**
+ * Returns the ICE local host address.
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE local host address if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ public abstract InetSocketAddress getICELocalHostAddress(String streamName);
+
+ /**
+ * Returns the ICE remote host address.
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE remote host address if this transport
+ * manager is using ICE. Otherwise, returns null.
+ */
+ public abstract InetSocketAddress getICERemoteHostAddress(
+ String streamName);
+
+ /**
+ * Returns the ICE local reflexive address (server or peer reflexive).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE local reflexive address. May be null if this transport
+ * manager is not using ICE or if there is no reflexive address for the
+ * local candidate used.
+ */
+ public abstract InetSocketAddress getICELocalReflexiveAddress(
+ String streamName);
+
+ /**
+ * Returns the ICE remote reflexive address (server or peer reflexive).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE remote reflexive address. May be null if this transport
+ * manager is not using ICE or if there is no reflexive address for the
+ * remote candidate used.
+ */
+ public abstract InetSocketAddress getICERemoteReflexiveAddress(
+ String streamName);
+
+ /**
+ * Returns the ICE local relayed address (server or peer relayed).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE local relayed address. May be null if this transport
+ * manager is not using ICE or if there is no relayed address for the
+ * local candidate used.
+ */
+ public abstract InetSocketAddress getICELocalRelayedAddress(
+ String streamName);
+
+ /**
+ * Returns the ICE remote relayed address (server or peer relayed).
+ *
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return the ICE remote relayed address. May be null if this transport
+ * manager is not using ICE or if there is no relayed address for the
+ * remote candidate used.
+ */
+ public abstract InetSocketAddress getICERemoteRelayedAddress(
+ String streamName);
+
+ /**
+ * Returns the total harvesting time (in ms) for all harvesters.
+ *
+ * @return The total harvesting time (in ms) for all the harvesters. 0 if
+ * the ICE agent is null, or if the agent has nevers harvested.
+ */
+ public abstract long getTotalHarvestingTime();
+
+ /**
+ * Returns the harvesting time (in ms) for the harvester given in parameter.
+ *
+ * @param harvesterName The class name if the harvester.
+ *
+ * @return The harvesting time (in ms) for the harvester given in parameter.
+ * 0 if this harvester does not exists, if the ICE agent is null, or if the
+ * agent has never harvested with this harvester.
+ */
+ public abstract long getHarvestingTime(String harvesterName);
+
+ /**
+ * Returns the number of harvesting for this agent.
+ *
+ * @return The number of harvesting for this agent.
+ */
+ public abstract int getNbHarvesting();
+
+ /**
+ * Returns the number of harvesting time for the harvester given in
+ * parameter.
+ *
+ * @param harvesterName The class name if the harvester.
+ *
+ * @return The number of harvesting time for the harvester given in
+ * parameter.
+ */
+ public abstract int getNbHarvesting(String harvesterName);
+
+ /**
+ * Returns the ICE candidate extended type selected by the given agent.
+ *
+ * @param iceAgent The ICE agent managing the ICE offer/answer exchange,
+ * collecting and selecting the candidate.
+ * @param streamName The stream name (AUDIO, VIDEO);
+ *
+ * @return The ICE candidate extended type selected by the given agent. null
+ * if the iceAgent is null or if there is no candidate selected or
+ * available.
+ */
+ public static String getICECandidateExtendedType(
+ Agent iceAgent,
+ String streamName)
+ {
+ if(iceAgent != null)
+ {
+ LocalCandidate localCandidate
+ = iceAgent.getSelectedLocalCandidate(streamName);
+
+ if(localCandidate != null)
+ return localCandidate.getExtendedType().toString();
+ }
+ return null;
+ }
+}