/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 * "isfocus" 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); } } }