aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/service/protocol/CallConference.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/sip/communicator/service/protocol/CallConference.java')
-rw-r--r--src/net/java/sip/communicator/service/protocol/CallConference.java1858
1 files changed, 929 insertions, 929 deletions
diff --git a/src/net/java/sip/communicator/service/protocol/CallConference.java b/src/net/java/sip/communicator/service/protocol/CallConference.java
index 890b0cb..b6752cd 100644
--- a/src/net/java/sip/communicator/service/protocol/CallConference.java
+++ b/src/net/java/sip/communicator/service/protocol/CallConference.java
@@ -1,4 +1,4 @@
-/*
+/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
@@ -15,931 +15,931 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-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()
- {
- @Override
- public void callPeerAdded(CallPeerEvent ev)
- {
- CallConference.this.onCallPeerEvent(ev);
- }
-
- @Override
- public void callPeerRemoved(CallPeerEvent ev)
- {
- CallConference.this.onCallPeerEvent(ev);
- }
-
- @Override
- 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);
- }
- }
-}
+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()
+ {
+ @Override
+ public void callPeerAdded(CallPeerEvent ev)
+ {
+ CallConference.this.onCallPeerEvent(ev);
+ }
+
+ @Override
+ public void callPeerRemoved(CallPeerEvent ev)
+ {
+ CallConference.this.onCallPeerEvent(ev);
+ }
+
+ @Override
+ 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);
+ }
+ }
+}